什么是一个好的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。 密切关注成本收益比或缺陷概率。
- 不要写巨大的testing。 正如“unit testing”中的“单元”所表明的那样,尽可能地使每个单元都是primefaces的和孤立的。 如果必须,请使用模拟对象创build前提条件,而不是手动重新创build过多的典型用户环境。
- 不要testing显然工作的东西。 避免从第三方供应商那里testing这些类,特别是那些提供你编码框架的核心API的供应商。例如,不要testing向供应商的Hashtable类添加项目。
- 考虑使用代码覆盖工具 (如NCover)来帮助发现尚未testing的边缘案例。
- 尝试在实施之前编写testing。 将testing看作更多的规范,您的实施将遵守。 参看 也是行为驱动的开发,是testing驱动开发的一个更具体的分支。
- 始终如一。 如果你只是为你的一些代码编写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的行为。
- validation预期的行为。
- validation错误情况。
- 覆盖类中的所有代码path。
- 锻炼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的依赖关系。 否则,我承认,他们是不好的。
通常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的方法已经过载,我还会将参数追加到方法名称中。