单元测试 – 应该测试内部实现还是只测试公共行为?

给定软件在哪里…

>系统由几个子系统组成
>每个子系统由几个组件组成
>每个组件都使用许多类实现

…我喜欢编写每个子系统或组件的自动测试。

我不为组件的每个内部类写一个测试(除非每个类都贡献组件的公共功能,因此可以通过组件的公共API从外部测试/测试)。

当我重构一个组件的实现(我经常做,作为添加新功能的一部分),因此我不需要改变任何现有的自动化测试:因为测试只依赖于组件的公共API和公共API通常被扩展而不是被改变。

我认为这个政策与一个像Refactoring Test Code这样的文档形成对比,它说…

>“…单元测试…”
>“…系统中每个类的测试类…”
>“…测试代码/生产代码比…理想地考虑接近1:1 …的比率…”

…所有这些我认为我不同意(或至少不实践)。

我的问题是,如果你不同意我的政策,你能解释一下为什么吗?在什么情况下这种程度的测试不足?

综上所述:

>公共接口被测试(并重新测试),很少改变(它们被添加到但很少改变)
>内部API隐藏在公共API之后,可以在不重写测试公共API的测试用例的情况下进行更改

脚注:我的一些测试用例实际上是作为数据实现的。例如,UI的测试用例由包含各种用户输入和相应的预期系统输出的数据文件组成。测试系统意味着具有读取每个数据文件,将输入重放到系统中并且断言它获得相应的预期输出的测试代码。

虽然我很少需要更改测试代码(因为公共API通常添加到而不是改变),我发现我有时(例如每周两次)需要更改一些现有的数据文件。这可能发生在我更改系统输出以更好(即新功能改进现有输出),这可能导致现有测试失败(因为测试代码只尝试断言输出没有更改)。为了处理这些情况,我做以下:

>重新运行自动测试套件,它有一个特殊的运行时标志,告诉它不断言输出,而是将新输出捕获到一个新目录
>使用可视化差异工具来查看哪些输出数据文件(即哪些测试用例)已更改,并验证这些更改是否良好,以及新功能
>通过将新输出文件从新目录复制到运行测试用例的目录(覆盖旧测试),更新现有测试。

脚注:通过“组件”,我的意思是像“一个DLL”或“一个程序集”…足够大,以便在系统的架构或部署图上可见,通常使用几十或100类实现,其中公共API仅包括大约1个或少数几个接口…可以分配给一个开发者团队(其中不同的组件被分配给不同的团队)的公共API,并且因此根据Conway’s Law具有相对稳定的公共API。

脚注:文章Object-Oriented Testing: Myth and Reality说,

Myth: Black box testing is sufficient.
If you do a careful job of test case
design using the class interface or
specification, you can be assured that
the class has been fully exercised.
White-box testing (looking at a
method’s implementation to design
tests) violates the very concept of
encapsulation.

Reality: OO structure matters, part
II.
Many studies have shown that
black-box test suites thought to be
excruciatingly thorough by developers
only exercise from one-third to a half
of the statements (let alone paths or
states) in the implementation under
test. There are three reasons for
this. First, the inputs or states
selected typically exercise normal
paths, but don’t force all possible
paths/states. Second, black-box
testing alone cannot reveal surprises.
Suppose we’ve tested all of the
specified behaviors of the system
under test. To be confident there are
no unspecified behaviors we need to
know if any parts of the system have
not been exercised by the black-box
test suite. The only way this
information can be obtained is by code
instrumentation. Third, it is often
difficult to exercise exception and
error-handling without examination of
the source code.

我应该补充说,我正在做白盒子功能测试:我看到代码(在实现)和我写功能测试(驱动公共API)来锻炼各种代码分支(功能的实现的细节)。

最佳答案
我的做法是通过公共API / UI测试内部。如果一些内部代码不能从外面访问,那么我重构删除它。

转载注明原文:单元测试 – 应该测试内部实现还是只测试公共行为? - 代码日志