Python 中的 TDD - 我们应该测试辅助函数吗?

TDD in Python - should we test helper functions?

Python 提出的一个理论问题,因为我们几乎可以访问任何我们想要的东西,即使它被强调为“私有”。

def main_function():
    _helper_function_()
    ...
    _other_helper_function()

使用 TDD,您遵循 Red-Green-Refactor 循环。测试现在看起来像这样:

def test_main_function_for_something_only_helper_function_does():
    # tedious setup
    ...
    main_function()

    assert something

问题是我的 main_function 设置步骤太多,我决定针对这些特定情况测试辅助函数:

from main_package import _helper_function

def test_helper_function_works_for_this_specific_input():
    # no tedious setup
    ...
    _helper_function_(some_input)

    assert helper function does exactly something I expect

但这似乎是一种不好的做法。我是否应该“知道”任何 inner/helper 函数?

我通过将部分移出到这些辅助函数中来重构主要函数,使其更具可读性。所以我重写了测试以实际测试这些较小的部分,并创建了另一个测试,主要功能确实调用了它们。这似乎也适得其反。

另一方面,我不喜欢很多挥之不去的 inner/helper 函数没有专门的单元测试的想法,只有主要函数的快乐路径。我想如果我在重构之前覆盖了原始功能,我的旧测试就足够好了。

此外,如果主要功能中断,这将意味着辅助功能的许多其他测试也会中断。

更好的做法是什么?

The problem is that my main_function had so much setup steps that I've decided to test the helper functions for those specific cases

太好了,这正是应该发生的事情(测试“驱使”您将整体分解成更容易测试的更小的部分)。

Should I even "know" about any inner/helper functions?

权衡。

是的,模块的部分意义在于它们提供 information hiding,允许您稍后更改代码的执行方式而不影响客户端,包括测试客户端。

但是直接测试内部模块也有好处;测试设计变得更简单,与 无关 细节的耦合更少。每个决定耦合的测试更少,这意味着当您需要更改其中一个时,爆炸半径更小。

我通常的想法是这样的:我应该知道有可测试的内部模块,我可以知道一个外部模块表现得像它耦合到一个内部模块,但我不一定知道外部模块耦合到内部模块。

assert X.result(A,B) == Y.sort([C,D,E])

如果你仔细看这个,你会发现它暗示 X.resultY.sort 今天有一些共同的要求,但它不一定保证 X.result 调用 Y.sort.

So I've rewritten tests to actually test these smaller parts and created another test that the main function indeed calls them. This also seems counter-productive.

A 有效,B 有效,C 有效,现在你正在为 f(A,B,C) 编写测试......是的,一切顺利侧身.

TDD 的预期结果是“有效的干净代码”(Jeffries);事情的真相是,您可以获得干净的代码,而无需编写世界上的每个测试。

测试在最有可能出现错误的代码中最为重要 - 我们只是将事物连接在一起的直线代码从红-绿-重构循环中受益的程度远不如具有大量条件的代码和分支。

There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies

对于“简单到明显没有缺陷”的代码段,一套自动化的程序员测试并不是什么大投资。找两个人进行人工审核,然后签字。

太多 private/helper 函数通常是缺少抽象的标志。

也许您应该考虑应用 'Extract class' 重构。此重构将解决您的困惑,因为私有成员最终将成为提取的 class.

的 public 成员

请注意,我在这里并不是建议为每个私人成员创建一个 class,而是要稍微尝试一下模型以找到更好的设计。