应该测试内部实现,还是仅测试公共行为?

Chr*_*isW 44 refactoring integration-testing automated-tests unit-testing code-coverage

给定软件......

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

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

我没有为组件的每个内部类编写测试(因为每个类都有助于组件的公共功能,因此可以通过组件的公共API从外部测试/测试).

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

我认为这个政策与重构测试代码这样的文件形成了鲜明的对比,后者说...

  • "......单元测试..."
  • "......系统中每个班级的测试班......"
  • "......测试代码/生产代码比率...理想地被认为接近1:1的比例......"

...所有这些我认为我不同意(或至少不练习).

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

综上所述:

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

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

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

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

脚注:通过"组件",我的意思是"一个DLL"或"一个组件"...这个大到足以在系统的体系结构或部署图上可见,通常使用数十个或100个类实现,以及因此与公共API只包含约1或接口少数......一些可能被分配到的开发商之一的团队(其中不同的组件被分配到不同的团队),并且将根据康威定律有一个相对稳定的公共API.


脚注:文章面向对象测试:神话与现实说,

神话:黑盒测试就足够了. 如果您使用类接口或规范仔细测试测试用例设计,则可以确保该类已经完全运用.白盒测试(查看方法的实现来设计测试)违反了封装的概念.

现实:OO结构很重要,第二部分.许多研究表明,开发人员认为黑盒测试套件非常彻底,只能在测试实施中使用三分之一到一半的语句(更不用说路径或状态)了.这有三个原因.首先,选择的输入或状态通常执行正常路径,但不强制所有可能的路径/状态.其次,单独的黑盒测试无法揭示惊喜.假设我们已经测试了被测系统的所有指定行为.为了确信没有未指明的行为,我们需要知道系统的任何部分是否未被黑盒测试套件执行.获取此信息的唯一方法是通过代码检测.第三,通常很难在不检查源代码的情况下执行异常和错误处理.

我应该补充一点,我正在进行白盒功能测试:我看到了代码(在实现中),我编写了功能测试(驱动公共API)来练习各种代码分支(功能实现的细节).

Phi*_*e F 30

答案非常简单:您正在描述功能测试,这是软件质量保证的重要组成部分.测试内部实现是单元测试,这是具有不同目标的软件QA的另一部分.这就是为什么你觉得人们不同意你的方法.

功能测试对于验证系统或子系统是否按预期执行操作非常重要.客户看到的任何东西都应该以这种方式进行测试.

单元测试用于检查您刚编写的10行代码是否符合预期要求.它使您对代码更有信心.

两者都是互补的.如果您在现有系统上工作,可能首先要进行功能测试.但是一旦添加代码,单元测试也是一个好主意.

  • 单元测试允许更精确地发现错误的来源.不,这不是浪费时间,因为有很多东西无法通过功能测试来正确测试,这仍然值得测试.通常,"难以模拟"错误对单元测试非常有用.我说的是所有那些返回NULL而不是有效指针的函数,网络连接丢失,配置文件无法读取的情况,......是的,你必须将它们与你的代码一起重构. (5认同)
  • 当我实现一个新功能时,我会通过功能测试来练习它(即实现新功能).为什么/何时可能是"单元测试的好主意"?功能测试不够吗?单元测试是不是浪费时间(例如,如果重构实现需要重新编写)?我很少编写一个单元测试:有一次我需要运行一个包含系统日期的类(通过等待实际系统日期改变来进行真正的功能测试是不方便的).另外,如果我是开发两个组件的人,... (2认同)

mou*_*iel 18

我的做法是通过公共API/UI测试内部.如果无法从外部访问某些内部代码,那么我会重构以删除它.


dar*_*rch 9

我没有在我面前拿到Lakos的副本,所以我不会仅仅指出他做得比我解释为什么测试在各个层面都很重要.

仅测试"公共行为"的问题是这样的测试为您提供的信息非常少.它会捕获许多错误(就像编译器会捕获许多错误一样),但不能告诉你错误在哪里.一个执行不良的单位长时间返回好的值然后在条件改变时停止这样做是很常见的; 如果该单位已经直接测试过,那么它实施得很糟糕的事实会更早显现出来.

最佳级别的测试粒度是单位级别.通过其接口为每个单元提供测试.这允许您验证和记录您对每个组件行为的看法,这反过来允许您通过仅测试它引入的新功能来测试相关代码,从而使测试保持简短和目标.作为奖励,它会使用他们正在测试的代码进行测试.

换句话说,只测试公共行为是正确的,只要你注意到每个公开可见的类都有公共行为.

  • 致ChrisW:关于你的观点#2 - >这正是你想要进行单元测试的原因.如果A类和B类的测试运行良好,但使用A和B的功能测试失败,则您知道这是一个交互问题.否则,你必须调查所有三种可能性(A有一个bug,B有一个bug,A + B不能很好地互相攻击) (4认同)

Ed *_*fer 8

到目前为止,对这个问题有很多很好的回答,但我想补充一些自己的笔记.作为序言:我是一家大公司的顾问,为大量客户提供技术解决方案.我这样说是因为根据我的经验,我们需要比大多数软件商店更彻底地测试(除了API开发人员).以下是我们为确保质量而采取的一些步骤:

  • 内部单元测试:
    开发人员应为他们编写的所有代码创建单元测试(读取:每个方法).单元测试应该包括正测试条件(我的方法是否工作?)和负测试条件(当我的一个必需参数为null时,该方法是否抛出ArgumentNullException?).我们通常使用CruiseControl.net等工具将这些测试合并到构建过程中
  • 系统测试/组装测试:
    有时这个步骤称为不同的,但这是我们开始测试公共功能的时候.一旦您知道所有单个单元按预期运行,您就会知道您的外部功能也以您认为应该的方式工作.这是功能验证的一种形式,因为目标是确定整个系统是否以其应有的方式工作.请注意,这不包括任何集成点.对于系统测试,您应该使用模拟接口而不是真实接口,以便您可以控制输出并围绕它构建测试用例.
  • 系统集成测试:
    在此过程的这个阶段,您希望将集成点连接到系统.例如,如果您使用的是信用卡处理系统,则需要在此阶段合并实时系统以验证它是否仍然有效.您可能希望对系统/装配测试执行类似的测试.
  • 功能验证测试:
    功能验证是指在系统中运行或使用API​​验证其是否按预期工作的用户.如果您已经构建了一个发票系统,那么您将在这个阶段从头到尾执行测试脚本,以确保一切都按照您的设计运行.这显然是这个过程中的一个关键阶段,因为它告诉你你是否已经完成了你的工作.
  • 认证测试:
    在这里,您将真实用户放在系统前面,然后让他们自己动手.理想情况下,您已经在某些时候与利益相关者测试了您的用户界面,但此阶段将告诉您目标受众是否喜欢您的产品.您可能已经听说过其他供应商称之为"发布候选人".如果在这个阶段一切顺利,你知道你很有可能进入生产阶段.认证测试应始终在您将用于生产的相同环境中执行(或至少在相同的环境中).

当然,我知道不是每个人都遵循这个过程,但如果你从头到尾看,它可以开始看到各个组件的好处.我没有包括构建验证测试之类的东西,因为它们发生在不同的时间线上(例如,每天).我个人认为单元测试至关重要,因为它们可以让您深入了解应用程序的哪个特定组件在哪个特定用例中失败.单元测试还可以帮助您确定哪些方法正常运行,这样您就不会花时间查看它们,以获得有关故障的更多信息.

当然,单元测试也可能是错误的,但是如果你根据功能/技术规范开发测试用例(你有一个,对吧?;)),你就不会有太多麻烦.

  • 我想我将这些步骤命名为"单元测试"(单元),"组件测试"(每个更大的组件),"集成测试"(几个组件),"系统测试"(整个系统)和"验收测试" (由客户和/或最终用户). (2认同)
  • 经过单元测试的软件可能比没有单元测试的软件便宜(因为在集成测试期间的调试可能比单元测试期间的调试效率更低且更昂贵); 或者它可能更昂贵(因为编写和维护单元测试以及功能测试本身就是额外的成本). (2认同)