单元测试内部组件


14

您在多大程度上对类/模块/包/等的内部/私有组件进行单元测试?您是否对它们进行了测试,还是仅测试了与外界的接口?这些内部方法的一个示例是私有方法。

例如,想象一下递归下降解析器,它具有从一个中央过程调用的几个内部过程(函数/方法)。到外部世界的唯一接口是中央过程,该过程采用字符串并返回已解析的信息。其他过程解析字符串的不同部分,可以从中央过程或其他过程中调用它们。

当然,您应该通过使用示例字符串调用外部接口并将其与手动分析的输出进行比较来测试外部接口。但是其他程序呢?您是否会分别测试它们以检查它们是否正确解析了其子字符串?

我可以想到一些论点:

优点

  1. 测试越多越好,这可以帮助增加代码覆盖率
  2. 通过向外部接口提供输入,某些内部组件可能很难提供特定的输入(例如,边缘情况)
  3. 更清晰的测试。如果内部组件具有(已修复的)错误,则该组件的测试用例可以清楚地表明该错误位于该特定组件中

缺点

  1. 重构变得非常痛苦和耗时。要更改任何内容,即使外部接口的用户不受影响,也需要重写单元测试。
  2. 某些语言和测试框架不允许这样做

您对此有何看法?


Answers:


8

案例:一个“模块”(广义上讲,即具有公共接口的东西,也可能还有一些私有内部零件)在其内部具有一些复杂/复杂的逻辑。仅测试模块接口将是与模块内部结构相关的集成测试,因此,如果发现错误,则该测试将不会定位导致故障的确切内部零件/组件。

解决方案:将复杂的内部零件本身转换为模块,对其进行单元测试(如果它们本身过于复杂,则对它们重复这些步骤),然后导入到原始模块中。现在,您只有一组足够简单的模块,可以轻松地进行uniit测试(检查行为是否正确并修复错误)。

注意:

  • 更改模块合同时,无需对模块(以前)“子模块”的测试进行任何更改,除非“子模块”不再提供足以满足新合同/已更改合同的服务。

  • 没有必要公开任何内容,即将保留模块的合同并维护封装。

[更新]

在难以通过仅通过对象的公共接口输入对象的内部部分(我的意思是成员而不是私有导入的模块/包)来使其状态进入适当状态的情况下,测试一些智能的内部逻辑:

  • 只需拥有一些与朋友(用C ++术语)或程序包(Java)进行访问的测试代码,就可以从内部实际设置状态并根据需要测试行为。

    • 这不会再次破坏封装,同时可以方便地直接访问内部组件以进行测试-只需将测试作为“黑匣子”运行并在发行版本中进行编译即可。

并且列表布局似乎有点破损;(
mlvljr 2010年

1
好答案。在.NET中,您可以使用[assembly: InternalsVisibleTo("MyUnitTestAssembly")]属性AssemblyInfo.cs来测试内部。感觉就像在作弊。
没人

@rmx一旦满足所有必要条件,即使与实际作弊有共同点,也并非不作弊。但是,在现代主流语言中,模块间/模块内访问的主题确实有点虚构。
mlvljr

2

基于FSM的代码的方法与传统方法有所不同。它与此处描述的硬件测试(通常也是FSM)非常相似。

简而言之,您将创建一个测试输入序列(或一组测试输入序列),该序列不仅应产生一定的输出,而且在产生特定的“不良”输出时还应根据故障的性质来识别故障的组件。该方法具有很好的可扩展性,您花在测试设计上的时间越多,测试就越好。

这种测试更接近于所谓的“功能测试”,但它消除了您每次轻触实现时都需要更改测试的需求。


2

这得看情况 :-)。如果您遵循的是BDD(行为驱动开发)或ATDD(验收测试驱动开发)方法,那么对公共接口进行测试就可以了(只要您使用不同的输入进行详尽的测试即可)。实际上很重要。

但是,如果您希望该算法的一部分在某个时间范围内或沿着某个bigO曲线(例如nlogn)执行,那么可以测试各个部分是否重要。有些人将其称为传统的TDD /单元测试方法。

与所有内容一样,YMMV


1

将它分成多个部分具有功能意义,例如ParseQuotedString()ParseExpression()ParseStatement()ParseFile(),让他们所有的公共。您的语法变化如此之大以至于变得无关紧要的可能性有多大?


1
这种方法很容易导致封装较弱,并且接口更大,更难使用/理解。
2016年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.