我敢肯定,你们中的大多数人都在编写大量自动化测试,并且在单元测试时也遇到了一些常见的陷阱。
我的问题是您是否遵循任何编写测试的行为规则以避免将来出现问题?更具体地说:好的单元测试的属性是什么,或者你如何编写测试?
鼓励语言无关的建议。
我敢肯定,你们中的大多数人都在编写大量自动化测试,并且在单元测试时也遇到了一些常见的陷阱。
我的问题是您是否遵循任何编写测试的行为规则以避免将来出现问题?更具体地说:好的单元测试的属性是什么,或者你如何编写测试?
鼓励语言无关的建议。
让我从插入源代码开始 -使用 JUnit 在 Java 中进行实用单元测试(也有一个带有 C#-Nunit 的版本.. 但我有这个.. 它在大多数情况下是不可知的。推荐。)
好的测试应该是一次旅行(首字母缩略词不够粘——我在书中有一份备忘单的打印输出,我必须拿出来确保我做对了……)
专业:从长远来看,您将拥有与生产一样多的测试代码(如果不是更多的话),因此对您的测试代码遵循相同的良好设计标准。精心设计的方法类——具有揭示意图的名称、无重复、具有好名称的测试等。
好的测试也运行得很快。任何需要超过半秒才能运行的测试......需要进行处理。测试套件运行的时间越长……运行的频率就越低。开发人员将尝试在运行之间进行的更改越多..如果有任何问题..找出哪个更改是罪魁祸首将需要更长的时间。
2010-08 更新:
除了这些之外,大多数其他指南都是减少低收益工作的指南:例如“不要测试您不拥有的代码”(例如第三方 DLL)。不要去测试 getter 和 setter。密切关注成本效益比或缺陷概率。
这里的大多数答案似乎都是针对一般的单元测试最佳实践(何时、何地、为什么和什么),而不是实际编写测试本身(如何)。由于这个问题在“如何”部分似乎非常具体,我想我会发布这个,取自我在公司进行的“棕色袋子”演示。
1. 使用长的、描述性的测试方法名称。
- Map_DefaultConstructorShouldCreateEmptyGisMap()
- ShouldAlwaysDelegateXMLCorrectlyToTheCustomHandlers()
- Dog_Object_Should_Eat_Homework_Object_When_Hungry()
2. 以Arrange/Act/Assert 风格编写测试。
3. 始终在您的断言中提供失败消息。
Assert.That(x == 2 && y == 2, "An incorrect number of begin/end element
processing events was raised by the XElementSerializer");
4. 评论测试的原因——业务假设是什么?
/// 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()
{
}
5. 每个测试都必须总是恢复它接触到的任何资源的状态
牢记这些目标(改编自 Meszaros 的《xUnit 测试模式》一书)
一些使这更容易的事情:
不要忘记您也可以使用 xUnit 框架进行集成测试,但将集成测试和单元测试分开
优秀单元测试的一些属性:
当测试失败时,问题出在哪里应该立即显而易见。如果您必须使用调试器来跟踪问题,那么您的测试不够精细。每个测试只有一个断言在这里有帮助。
重构时,任何测试都不应该失败。
测试应该运行得如此之快,以至于您毫不犹豫地运行它们。
所有测试都应始终通过;没有不确定的结果。
单元测试应该考虑周全,就像您的生产代码一样。
@Alotor:如果您建议库应该只在其外部 API 上进行单元测试,我不同意。我想要对每个类进行单元测试,包括我不向外部调用者公开的类。(但是,如果我觉得需要为私有方法编写测试,那么我需要重构。)
编辑:有一条关于“每个测试一个断言”引起的重复的评论。具体来说,如果您有一些代码来设置场景,然后想要对其进行多个断言,但每个测试只有一个断言,您可能会在多个测试中重复设置。
我不采用这种方法。相反,我使用每个场景的测试装置。这是一个粗略的例子:
[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...
}
}
测试应该被隔离。一项测试不应依赖于另一项测试。更进一步,测试不应该依赖于外部系统。换句话说,测试您的代码,而不是您的代码所依赖的代码。您可以将这些交互作为集成或功能测试的一部分进行测试。
你所追求的是描述被测类的行为。
基本意图是增加您对班级行为的信心。
这在重构代码时特别有用。Martin Fowler在他的网站上有一篇关于测试的有趣文章。
HTH。
干杯,
抢
测试最初应该失败。然后你应该编写使它们通过的代码,否则你会冒着编写一个有错误但总是通过的测试的风险。
我喜欢前面提到的实用单元测试书中的 Right BICEP 首字母缩写词:
就我个人而言,我觉得你可以通过检查你得到正确的结果(1+1 应该在加法函数中返回 2),尝试你能想到的所有边界条件(例如使用两个数字的和大于 add 函数中的整数最大值)并强制错误条件,例如网络故障。
好的测试需要可维护。
我还没有完全弄清楚如何在复杂的环境中做到这一点。
当您的代码库开始达到数百或数百万行代码时,所有教科书都开始脱节。
好的架构可以控制一些交互爆炸,但随着系统变得越来越复杂,自动化测试系统不可避免地会随之增长。
这是您开始必须权衡取舍的地方:
您还需要决定:
您将测试用例存储在代码库的什么位置?
我可以永远继续下去,但我的观点是:
测试需要可维护。
不久前,我在这篇 MSDN 杂志文章中介绍了这些原则,我认为这对任何开发人员都很重要。
我定义“好”单元测试的方式是它们是否具有以下三个属性:
Jay Fields 有很多关于编写单元测试的好建议,并且有一篇文章他总结了最重要的建议。在那里你会读到你应该批判性地思考你的背景并判断这些建议是否对你有价值。您会在这里获得大量惊人的答案,但由您决定哪个最适合您的上下文。试试它们,如果你觉得不好,就重构。
亲切的问候
永远不要假设一个简单的 2 行方法会起作用。编写一个快速的单元测试是防止缺少空测试、放错位置的负号和/或微妙的范围错误咬你的唯一方法,当你处理它的时间比现在更少时,这是不可避免的。
我支持“A TRIP”的答案,除了测试应该相互依赖!!!
为什么?
DRY - 不要重复自己 - 也适用于测试!测试依赖项可以帮助 1) 节省设置时间,2) 节省夹具资源,以及 3) 查明故障。当然,前提是您的测试框架支持一流的依赖项。否则,我承认,他们很糟糕。
单元测试通常基于模拟对象或模拟数据。我喜欢写三种单元测试:
关键是要避免重播所有内容,以便能够测试每个功能。
考虑两种类型的测试并区别对待它们——功能测试和性能测试。
为每个使用不同的输入和指标。您可能需要为每种类型的测试使用不同的软件。
我使用Roy Osherove 的单元测试命名标准描述的一致的测试命名约定给定测试用例类中的每个方法都具有以下命名样式 MethodUnderTest_Scenario_ExpectedResult。
每个部分使用 Upper Camel Case 并由下划线分隔。
当我运行测试时,我发现这很有用,测试按被测方法的名称分组。并且有一个约定可以让其他开发者理解测试意图。
如果被测方法已重载,我还将参数附加到方法名称。