什么是一个好的unit testing?

我相信你们中的大多数人正在写大量的自动化testing,并且在unit testing时也遇到了一些常见的陷阱。

我的问题是,你是否遵循任何行为准则来编写testing,以避免将来出现问题? 更具体地说: 好的unit testing特性是什么或者如何编写testing?

鼓励语言不可知论的build议。

让我从插入源代码开始 – 使用JUnit实现Java语义unit testing (还有一个与C#版本一起使用的版本,但是我有这个..大多数情况下它是不可知的。

好的testing应该是一个TRIP (首字母缩略词不够粘性 – 我在书中有一个打印输出的表格,我必须拉出来以确保我的确是这样的..)

  • 自动 :调用testing以及检查PASS / FAIL的结果应该是自动的
  • 彻底 :覆盖面; 虽然bug往往集中在代码中的某些区域,但要确保testing所有关键path和场景。如果必须知道未经testing的区域,请使用工具
  • 可重复性 :每次testing都应该产生相同的结果。 testing不应该依赖不可控制的参数。
  • 独立 :非常重要。
    • testing只能testing一件事 。 只要他们都testing一个特征/行为,多个断言都可以。 当一个testing失败时,应该确定问题的位置。
    • testing不应该依靠对方 – 孤立的。 没有关于testing执行顺序的假设。 确保在每次testing之前进行适当的设置/拆卸
  • 专业 :从长远来看,你将拥有和生产一样多的testing代码(如果不是更多的话),因此遵循与你的testing代码相同的良好devise标准。 充分考虑的方法 – 具有意图揭示名称的类别,不重复,具有良好名称的testing等等。

  • 好的testing也跑得 。 任何超过半秒的testing运行..需要被处理。 testing套件运行的时间越长,运行的频率越低。 开发者将尝试偷偷摸摸的进行更多的更改。如果有什么事情发生,那么需要花费更长时间才能确定哪个更改是罪魁祸首。

2010-08更新:

  • 可读 :这可以被认为是专业人士的一部分 – 但是不能太强调。 一个酸性testing将是find一个不属于你的团队的人,并要求他/她在几分钟内找出被testing的行为。 testing需要像生产代码一样维护,所以即使需要更多的努力,也可以轻松阅读。 testing应该是对称的(按照模式)和简洁(一次testing一个行为)。 使用一致的命名约定(例如TestDox风格)。 避免混淆与“附带细节”的testing..成为一个极简主义者。

除此之外,其他大多数都是减less低收益工作的指导方针:例如“不要testing你不拥有的代码”(例如第三方DLL)。 不要去testinggetter和setter。 密切关注成本收益比或缺陷概率。

  1. 不要写巨大的testing。 正如“unit testing”中的“单元”所表明的那样,尽可能地使每个单元都是primefaces的孤立的。 如果必须,请使用模拟对象创build前提条件,而不是手动重新创build过多的典型用户环境。
  2. 不要testing显然工作的东西。 避免从第三方供应商那里testing这些类,特别是那些提供你编码框架的核心API的供应商。例如,不要testing向供应商的Hashtable类添加项目。
  3. 考虑使用代码覆盖工具 (如NCover)来帮助发现尚未testing的边缘案例。
  4. 尝试在实施之前编写testing。 将testing看作更多的规范,您的实施将遵守。 参看 也是行为驱动的开发,是testing驱动开发的一个更具体的分支。
  5. 始终如一。 如果你只是为你的一些代码编写testing,那么它就没有用处。 如果你在一个团队里工作,而其他一些或所有的人不写testing,那也不是很有用。 说服自己和其他人testing的重要性(和节省时间的属性),或不要打扰。

这里的大部分答案似乎都是针对unit testing的最佳实践(时间,地点,原因和内容),而不是实际编写testing(如何)。 由于这个问题在“如何”这个部分看起来非常具体,我想我会从我在公司进行的“棕色包装”演示文稿中发布这个问题。

Womp的五个写作testing法则:


1.使用长的描述性testing方法名称。

- Map_DefaultConstructorShouldCreateEmptyGisMap() - ShouldAlwaysDelegateXMLCorrectlyToTheCustomHandlers() - Dog_Object_Should_Eat_Homework_Object_When_Hungry() 

2.以编配/动作/声明样式编写testing。

  • 虽然这个组织战略已经存在了一段时间,并称许多事情,但最近引入“AAA”的缩写是一个很好的方法。 使所有testing与AAA风格保持一致,便于阅读和维护。

3.始终用您的断言提供失败消息。

 Assert.That(x == 2 && y == 2, "An incorrect number of begin/end element processing events was raised by the XElementSerializer"); 
  • 一个简单而有益的做法,使你的亚军应用程序显而易见失败。 如果你没有提供信息,你通常会在你的失败输出中得到类似“Expected true,false”的错误信息,这使得你必须真正阅读testing来找出错误。

4.评论testing的原因 – 业务假设是什么?

  /// A layer cannot be constructed with a null gisLayer, as every function /// in the Layer class assumes that a valid gisLayer is present. [Test] public void ShouldNotAllowConstructionWithANullGisLayer() { } 
  • 这似乎是显而易见的,但这种做法将保护testing的完整性,使人们不了解testing背后的原因。 我看到许多testing被删除或修改,完全没有问题,只是因为这个人不理解testing正在validation的假设。
  • 如果testing是微不足道的,或者方法名称是足够描述性的,则可以允许closures注释。

5.每个testing必须总是恢复所触及的任何资源的状态

  • 尽可能使用模拟来避免处理真实的资源。
  • 清理必须在testing级别完成。 testing不能依赖执行的顺序。

记住这些目标(根据Meszaros的xUnit Test Patterns改编)

  • testing应该降低风险,而不是引入它。
  • testing应该很容易运行。
  • 随着系统的发展,testing应该易于维护

有些事情使这更容易:

  • testing应该只会因为一个原因而失败。
  • testing应该只testing一件事
  • 最小化testing依赖性(不依赖数据库,文件,UI等)

不要忘记,你也可以用你的xUnit框架进行集成testing, 但是要保持集成testing和unit testing的独立性

testing应该被隔离。 一个testing不应该依赖另一个。 更进一步,testing不应该依靠外部系统。 换句话说,请testing您的代码,而不是您的代码所依赖的代码。您可以将这些交互作为集成或functiontesting的一部分进行testing。

伟大的unit testing的一些属性:

  • 当一个testing失败的时候,应该立即明确问题出在哪里。 如果您必须使用debugging器来追踪问题,那么您的testing不够精细。 每个testing只有一个断言在这里有帮助。

  • 当你重构,没有testing应该失败。

  • testing应该运行得如此之快,以至于你毫不犹豫地运行它们。

  • 所有的testing应该总是通过; 没有非确定性的结果。

  • unit testing应该是很好的,就像你的生产代码一样。

@Alotor:如果你build议图书馆只在其外部API上进行unit testing,我不同意。 我想要每个类的unit testing,包括我不暴露给外部调用者的类。 (但是, 如果我觉得需要为私有方法编写testing,那么我需要重构。 )


编辑:有一个关于“每个testing一个断言”引起的重复的评论。 具体而言,如果您有一些代码来设置scheme,然后想要对其进行多重声明,但每个testing只有一个声明,则可能会在多个testing中重复设置。

我不采取这种方法。 相反,我使用每个场景的testing装置。 这是一个粗略的例子:

 [TestFixture] public class StackTests { [TestFixture] public class EmptyTests { Stack<int> _stack; [TestSetup] public void TestSetup() { _stack = new Stack<int>(); } [TestMethod] [ExpectedException (typeof(Exception))] public void PopFails() { _stack.Pop(); } [TestMethod] public void IsEmpty() { Assert(_stack.IsEmpty()); } } [TestFixture] public class PushedOneTests { Stack<int> _stack; [TestSetup] public void TestSetup() { _stack = new Stack<int>(); _stack.Push(7); } // Tests for one item on the stack... } } 

你所追求的是划定待测class的行为。

  1. validation预期的行为。
  2. validation错误情况。
  3. 覆盖类中的所有代码path。
  4. 锻炼class级内的所有成员function。

基本的目的是增加你对课堂行为的信心。

在重构你的代码时,这是特别有用的。 Martin Fowler在他的网站上有一篇关于testing的有趣文章 。

HTH。

干杯,

testing应该最初失败。 然后,你应该编写使他们通过的代码,否则你冒着编写被窃听的testing的风险,并总是通过。

我喜欢上面提到的“ 实用unit testing ”( Pragmatic Unit Testing)这本书的右边BICEP的缩写:

  • 好的 :结果是对的吗?
  • B :所有的条件都正确吗?
  • :我们可以检查我的逆向关系吗?
  • C :我们可以用其他方法来检查结果吗?
  • E :我们能否迫使发生错误?
  • P :边界内的性能特点?

就我个人而言,我觉得你可以通过检查你是否得到正确的结果(1 + 1应该返回2在附加函数中),尝试所有你能想到的边界条件(比如使用两个数字,大于add函数中的整数最大值)并强制错误条件(如networking故障)。

好的testing需要维护。

我还没有弄清楚如何在复杂的环境中做到这一点。

所有的教科书开始脱钩,因为你的代码库开始涉及到数百或数百万行的代码。

  • 团队互动爆炸
  • testing用例数量爆炸
  • 组件之间的相互作用爆炸。
  • 构build所有单位testing的时间成为构build时间的重要组成部分
  • API更改可能会波及到数百个testing用例。 即使生产代码改变很容易。
  • 将进程sorting到正确状态所需的事件数量增加,从而增加了testing执行时间。

良好的架构可以控制一些交互爆炸,但随着系统变得越来越复杂,自动化testing系统随之发展。

这是你开始不得不处理权衡的地方:

  • 只testing外部API,否则重构内部结果会导致重大的testing用例返工。
  • 每个testing的设置和拆卸变得更复杂,因为封装的子系统保持更多的状态。
  • 每晚编译和自动化testing执行增长到几个小时。
  • 增加编译和执行时间意味着devise者不会或不会运行所有的testing
  • 为了缩短testing执行时间,您可以考虑对testing进行sorting以减less设置和拆卸

您还需要决定:

你在哪里存储testing用例在你的代码库?

  • 你如何logging你的testing用例?
  • 可以重新使用testing夹具来保存testing用例维护吗?
  • 当夜间testing用例执行失败时会发生什么? 谁进行了分stream?
  • 你如何维护模拟对象? 如果你有20个模块都使用自己的模拟日志API的味道,快速改变API的涟漪。 testing用例不仅会改变,而且20个模拟对象也会改变。 这20个模块是由许多不同的团队多年撰写的。 它是一个经典的重用问题。
  • 个人和他们的团队了解自动化testing的价值,他们只是不喜欢其他团队如何做。 🙂

我可以永远继续下去,但我的观点是:

testing需要可维护。

我回顾了这些MSDN杂志文章 ,我认为这对任何开发人员来说都是很重要的。

我定义“好”unit testing的方式是,如果它们具有以下三个属性:

  • 它们是可读的(命名,断言,variables,长度,复杂性..)
  • 它们是可维护的(没有逻辑,没有超过规定的,基于状态的,重构的)
  • 他们是值得信任的(testing正确的东西,孤立的,而不是集成testing..)
  • unit testing只是testing你的单元的外部API,你不应该testing内部行为。
  • TestCase的每个testing都应该testing这个API中的一个(而且只有一个)方法。
    • 应包括故障情况下的附加testing用例。
  • testing你的testing的覆盖范围:一旦它被testing的一个单元,这个单元内的100%的线应该被执行。

Jay Fields在编写unit testing方面有很多好的build议 ,并有一个总结最重要的build议的文章 。 在那里你会看到你应该仔细考虑你的背景,并判断这个build议是否值得你。 你在这里得到了很多惊人的答案,但是由你决定哪一个最适合你的情况。 尝试一下,如果味道不好,就重构一下。

亲切的问候

永远不要假设一个简单的2行方法将起作用。 编写一个快速的unit testing是防止缺less空testing,误放减号和/或精细范围错误的唯一方法,即使你现在处理的时间更less,也不可避免地会发生。

我第二个“TRIP”的答案,除了testing应该依靠对方!

为什么?

干 – 不要重复自己 – 也适用于testing! testing依赖关系可以帮助1)节省设置时间,2)节省灯具资源,3)查明故障。 当然,只要你的testing框架支持一stream的依赖关系。 否则,我承认,他们是不好的。

跟进http://www.iam.unibe.ch/~scg/Research/JExample/

通常unit testing是基于模拟对象或模拟数据。 我喜欢写三种unit testing:

  • “瞬态”unit testing:他们创build自己的模拟对象/数据并用它来testing它们的function,但是破坏所有的东西而不留下痕迹(就像testing数据库中没有数据一样)
  • “持久性”unit testing:它们testing代码中的函数,创build对象/数据,这些对象/数据稍后将被更高级的函数用于自己的unit testing(避免那些高级函数每次都重新创build自己的一组模拟对象/数据)
  • “基于持久性”的unit testing:使用模拟对象/unit testing的unit testing(由于在另一个unit testing会话中创build)。

重点是避免重播所有的东西 ,以便能够testing每个function。

  • 我经常运行第三种,因为所有的模拟对象/数据已经在那里了。
  • 当我的模型改变时,我运行第二种。
  • 我运行第一个检查非常基本的function,偶尔检查基本回归。

考虑两种types的testing,并对它们进行不同的处理 – functiontesting和性能testing。

使用不同的input和指标。 您可能需要为每种types的testing使用不同的软件。

我使用Roy Osherove的unit testing命名标准描述的一致testing命名约定给定testing用例类中的每个方法具有以下命名方式MethodUnderTest_Scenario_ExpectedResult。

    第一个testing名称部分是被测系统中的方法名称。
    接下来是正在testing的特定场景。
    最后是该场景的结果。

每个部分使用上部骆驼案件,并由低于分数分隔。

当我运行testing时,我发现这很有用,testing按照testing方法的名称分组。 有一个约定允许其他开发人员了解testing意图。

如果被testing的方法已经过载,我还会将参数追加到方法名称中。