黑匣子单元测试

use*_*027 17 unit-testing

在我的上一个项目中,我们的单元测试几乎达到了100%cc,因此我们几乎没有任何错误.但是,由于单元测试必须是白盒(你必须模拟内部函数来获得你想要的结果,所以你的测试需要知道你的代码的内部结构)任何时候我们改变函数的实现,我们不得不也改变测试.请注意,我们没有更改函数的逻辑,只是实现.这非常耗时,感觉好像我们的工作方式不对.由于我们使用了所有适当的OOP准则(特别是封装),每次我们更改实现时,我们都不必更改其余的代码,但必须更改单元测试.感觉好像我们正在为测试服务,而不是为我们服务.

为了防止这种情况,我们中的一些人认为单元测试应该是黑盒测试.如果我们创建整个Domain的一个大模拟并为一个地方的每个类中的每个函数创建一个存根,并在每个单元测试中使用它,那么这是可能的.当然,如果特定的测试需要调用特定的内部函数(就像确保我们写入DB一样),我们可以覆盖我们的存根.

因此,每次我们更改函数的实现(比如添加或替换对帮助函数的调用)时,我们只需要更改我们的主要大模拟.即使我们确实需要改变一些单元测试,它仍然会比以前少得多.

其他人认为单元测试必须是White Box,因为不仅要确保你的应用程序在特定的地方写入数据库,你要确保你的应用程序不会在其他任何地方写入数据库,除非你特别期望它.虽然这是一个有效的观点,但我认为值得花时间编写白盒测试而不是黑盒测试.

总之,有两个问题:

  1. 您如何看待黑匣子单元测试的概念?

  2. 您如何看待我们希望实施该概念的方式?你有更好的想法吗?

Gui*_*ume 11

您需要不同类型的测试.

  • 单元测试应该是白盒测试,就像你做的那样

  • 集成测试(或系统测试),测试使用系统的实际实现及其与外部层(外部系统,数据库等)的通信的能力,外部层应该是黑盒式的,但每个都用于特定的功能(例如CRUD测试)

  • 验收测试应该完全是黑盒子并且由功能要求驱动(因为您的用户会对它们进行短语).尽可能端到端,不知道所选实现的内部.黑盒测试的教科书定义.

并且记住在大多数情况下代码覆盖是没有意义的.你需要一个高线覆盖(或方法覆盖,无论你的计数方法是什么),但这通常是不够的.您需要考虑的概念是功能覆盖:确保涵盖所有需求和逻辑路径.


k.m*_*k.m 6

结果我们几乎没有任何错误

如果你真的能够做到这一点,那么我认为你不应该做任何改变.

黑盒测试听起来在纸上吸引力,但事实是你几乎总是需要知道测试类的内部运作的部分.在提供输入,输出验证实际上仅适用于简单的情况.大多数情况下,您的测试需要至少具有一些测试方法的知识 - 它如何与外部协作者交互,它调用哪些方法,以什么顺序等等.

模拟和SOLID设计背后的整个想法是避免依赖实现更改导致其他类测试更改/失败的情况.相反,如果您更改测试方法的实现细节,那么应该更改它的实现细节测试.这没什么不常见的.

总的来说,如果你真的能够实现几乎没有错误,那么我会坚持这种方法.

  • @driushkin:我们谈论的是已经编写过测试和代码的情况.引用OP,*"请注意,我们没有改变函数的逻辑,只是实现"* - 这种改变**将导致需要更改测试,最有可能. (2认同)
  • @jimmy_keen 这正是“脆弱测试”的定义。如果你因为重构而需要改变你的测试,你有一个脆弱的测试,这就是设计味道。提取依赖确实打破了我的推理,即任何重构都不应以测试代码的任何更改结束,谢谢,但我可以想象需要提取依赖只是为了改进已经腐烂的代码库,因此您更改测试一次,不应再次更改此测试. 我不会争辩说,重构后测试代码不会改变,这是一个“目标”,而不是自然法则。 (2认同)

hij*_*ian 6

tl;博士版:

  1. 黑匣子单元测试正是如何进行单元测试的.
  2. 黑匣子单元测试正是如何进行单元测试的.正确的TDD实践就是这样做的.

完整版本.

绝对没有必要测试对象的私有方法.它也对代码覆盖率没有影响.

当你TDD一个类时,你编写测试来检查该类的行为.行为通过该类的公共方法表达.你永远不应该理解这些方法是如何真正实现的.Google人员描述了比以往任何时候都要好得多:http://googletesting.blogspot.ru/2013/08/testing-on-toilet-test-behavior-not.html

如果你做了通常的错误并且静态地依赖于其他实体类或者更糟糕的是,在来自不同应用层的类中,当你需要在测试中检查很多东西并准备一个时,你将发现自己处于这种情况是不可避免的.很多东西.为了解决这个问题,存在依赖注入原理和得墨忒耳定律.