【问题标题】:Did I TDD this method well or is there a better way?我对这种方法进行了很好的 TDD 还是有更好的方法?
【发布时间】:2013-07-12 00:34:00
【问题描述】:

注意:我习惯于在 C# 代码中使用依赖注入, 但据我了解,像 Ruby 和 Python 这样的动态语言是 喜欢play-doh not LEGOs,因此不需要遵循使用 IoC 容器,尽管关于 IoC 模式是否仍然有用存在一些争论。在下面的代码中,我使用了 fudge 的 .patch 功能,它提供了模拟/存根代码所需的接缝。然而,代码的组件因此是耦合的。我不确定我喜欢这个。 This SO answer 还解释了动态语言中的耦合比静态语言更松散,但确实引用了该问题中的另一个答案,即不需要 IoC 工具,但不需要模式。所以一个附带的问题是,“我应该为此使用 DI 吗?”

我正在使用以下 python 框架:

  • Nose 用于单元测试
  • Fudge 用于伪造(存根、嘲笑等)

这是生成的生产代码:

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

现在我对这段代码进行了 TDD,但我通过以下测试进行了测试(这并不彻底):

@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)

这是更新后的测试以及明确的 cmets,以确保我遵循 Four-Phase Test 模式:

@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

虽然第二个例子更彻底,但这个测试是不是有点矫枉过正? TDD python代码有更好的方法吗?基本上,我正在寻找有关我如何使用 TDD 执行此操作的反馈,并且欢迎任何替代方法来编写测试代码或生产代码。

【问题讨论】:

  • 我没有看到测试该特定功能的优势。我认为测试一切只是浪费时间。像这样的简单功能不一定需要完整的测试;通常一个简单的doctest就足够了。顺便说一句:如果您的目标不是python with 可以避免测试对close 的调用,因为它们肯定会发生。
  • @Bakuriu,我已经更新了顶部的注释,而且,我是 python 新手,这就是我问这些问题的原因(我读到过关于 doctest在我写完这篇文章之后,但这是我第一次听说with。关于不测试“一切”如何使用 TDD 来实现 100% 的代码覆盖率?

标签: python mocking tdd stubbing


【解决方案1】:

想想这个函数做了什么,想想你实际负责什么。它看起来像:给定一些数据和文件名,以特定格式(fasta)将记录写入文件。您实际上并不负责 Python 文件 I/O 的工作,或 Bio.SeqIO 的工作方式。

您的第二个版本测试:

  1. 打开文件进行写入。
  2. 使用预期参数调用 Bio.SeqIO.write。
  3. 文件已关闭。

看起来不错。大部分都很简单,有些人可能会称其为矫枉过正,但 TDD 方法可以帮助提醒您执行诸如关闭文件之类的操作(很明显,但我们总是忘记这样的事情)。这些测试还可以防止将来更改 Bio.SeqIO.write 以期望不同的参数。您可以升级您的库版本并想知道您的程序为什么会中断,或者升级您的库版本,运行您的测试,然后知道为什么以及在哪里它会中断。

当然,您应该为无法打开文件的情况编写其他测试,或者 Bio.SeqIO.write 可能抛出的任何异常。

【讨论】:

  • 谢谢肖恩,我还在您发布此答案时更新了顶部的注释。这段代码没有使用 DI,应该是吗?
  • 我个人从来没有发现 DI 的用途,但这可能是因为我编写的代码类型。 DI 会在这里做什么?允许您使用 Bio.SeqIO 以外的其他东西来重写您的代码?这是一个可能的问题吗?如果您决定进行更改,是否需要大量重写?
  • 请参阅here,以及我的问题中我的注释中链接的 SO 答案。我现在对是否需要动态语言感到矛盾。将程序分解成小的独立组件听起来对我很有吸引力,因为我认为这种编程风格更容易遵循SOLID 原则。
  • 我认为这真的取决于范围比这个例子更大的东西,并且在某种程度上取决于个人风格。在编写 Python 时,我通常不需要 DI,但这并不意味着它在解决我的其他问题方面没有作用。当我们使用一种新的或不熟悉的语言时,我们不是都把我们现有的一些做法带过来了吗?如果您对 DI 感到满意,请使用它。也许你会发现你使用它的次数越来越少。这是彻底测试的另一大好处——当你想完全重构代码时,它是一个安全网!
猜你喜欢
  • 2014-11-21
  • 1970-01-01
  • 1970-01-01
  • 2016-09-17
  • 2016-07-11
  • 2011-07-03
  • 1970-01-01
  • 1970-01-01
  • 2011-04-14
相关资源
最近更新 更多