Note: I'm used to using Dependency Injection with C# code, but from what I understand, dynamic languages like Ruby and Python are like play-doh not LEGOs, and thus don't need to follow use IoC containers, though there is some debate on if IoC patterns are still useful. In the code below I used fudge's .patch feature that provides the seams needed to mock/stub the code. However, the components of the code are thus coupled. I'm not sure I like this. This SO answer also explains that coupling in dynamic languages is looser than static ones, but does reference another answer in that question that says the tools for IoC are unneeded but the patterns are not. So a side question would be, "Should I have used DI for this?"

I'm using the following python frameworks:

  • Nose for unit testing
  • Fudge for fakes (stubs, mocking, etc)

Here is the resulting production code:

def to_fasta(seq_records, file_name):
    file_object = open(file_name, "w")
    Bio.SeqIO.write(seq_records, file_object, "fasta")
    file_object.close()

Now I did TDD this code, but I did it with the following test (which wasn't all the thorough):

@istest
@fudge.patch('__builtin__.open', 'Bio.SeqIO.write')
def to_fasta_writes_file(fake_open, fake_SeqIO):
    fake_open.is_a_stub()
    fake_SeqIO.expects_call()

    seq_records = build_expected_output_sequneces()
    file_path = "doesn't matter"

    to_fasta(seq_records, file_path)

Here is the updated test along with explicit comments to ensure I'm following the Four-Phase Test pattern:

@istest
@fudge.patch('__builtin__.open', 'Bio.SeqIO')
def to_fasta_writes_file(fake_open, fake_SeqIO):    
    # Setup
    seq_records = build_expected_output_sequneces()
    file_path = "doesn't matter"
    file_type = 'fasta'

    file_object = fudge.Fake('file').expects('close')

    (fake_open
        .expects_call()
        .with_args(file_path, 'w')
        .returns(file_object))

    (fake_SeqIO
         .is_callable()
         .expects("write")
         .with_args(seq_records, file_object, file_type))

    # Exercise
    to_fasta(seq_records, file_path)    

    # Verify (not needed due to '.patch')
    # Teardown

While the second example is more thorough, is this test overkill? Are there better ways to TDD python code? Basically, I'm looking for feedback on how I did with TDDing this operation, and would welcome any alternative ways to write either the test code or the production code.

有帮助吗?

解决方案

Think about what this function does and think about what you actually have responsibility for. It looks to me like: given some data and a file name, write the records in to the file in a particular format (fasta). You aren't actually responsible for the workings of Python file I/O, or how Bio.SeqIO works.

Your second version tests that:

  1. The file is opened for writing.
  2. That Bio.SeqIO.write is called with the expected parameters.
  3. The file is closed.

That looks pretty good. Most of this is simple, and some people may call it overkill, but the TDD approach can help remind you to do something like close the file (obvious, but we all forget stuff like that all the time). These tests also guard against such things as Bio.SeqIO.write being changed in the future to expect different parameters. You can either upgrade your version of the library and wonder why your program breaks, or upgrade your version of the library, run your tests, and know why and where it breaks.

Naturally you should write other tests for the case when you can't open the file, or any exceptions that Bio.SeqIO.write might throw.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top