【发布时间】:2010-09-08 20:25:48
【问题描述】:
我敢肯定,你们中的大多数人都在编写大量自动化测试,并且在进行单元测试时也遇到了一些常见的陷阱。
我的问题是,为了避免将来出现问题,您是否遵循任何编写测试的行为规则?更具体地说:良好单元测试的属性是什么,或者您如何编写测试?
鼓励语言无关的建议。
【问题讨论】:
标签: unit-testing language-agnostic tdd integration-testing testing-strategies
我敢肯定,你们中的大多数人都在编写大量自动化测试,并且在进行单元测试时也遇到了一些常见的陷阱。
我的问题是,为了避免将来出现问题,您是否遵循任何编写测试的行为规则?更具体地说:良好单元测试的属性是什么,或者您如何编写测试?
鼓励语言无关的建议。
【问题讨论】:
标签: unit-testing language-agnostic tdd integration-testing testing-strategies
让我从插入源代码开始 - Pragmatic Unit Testing in Java with JUnit(也有一个带有 C#-Nunit 的版本.. 但我有这个.. 它在很大程度上是不可知的。推荐。)
好的测试应该是一次旅行(首字母缩略词不够粘——我有一个打印出来的书里的备忘单,我不得不拿出来确保我做对了。 )
专业:从长远来看,您将拥有与生产一样多的测试代码(如果不是更多的话),因此您的测试代码遵循相同的良好设计标准。精心设计的方法类,具有揭示意图的名称、无重复、具有好名称的测试等。
良好的测试也运行快速。任何需要超过半秒才能运行的测试......需要进行处理。测试套件运行的时间越长……运行的频率就越低。开发人员在运行之间尝试偷偷摸摸的更改越多..如果有任何问题..找出哪个更改是罪魁祸首需要更长的时间。
2010-08 更新:
除此之外,其他大多数都是减少低收益工作的指南:例如“不要测试你不拥有的代码”(例如第三方 DLL)。不要去测试 getter 和 setter。密切关注成本效益比或缺陷概率。
【讨论】:
【讨论】:
这里的大多数答案似乎都是针对一般的单元测试最佳实践(何时、何地、为什么以及什么),而不是实际编写测试本身(如何)。由于这个问题在“如何”部分似乎非常具体,我想我会发布这个,取自我在公司进行的“棕色袋子”演示文稿。
1.使用长的、描述性的测试方法名称。
- Map_DefaultConstructorShouldCreateEmptyGisMap()
- ShouldAlwaysDelegateXMLCorrectlyToTheCustomHandlers()
- Dog_Object_Should_Eat_Homework_Object_When_Hungry()
2。在Arrange/Act/Assert style 中编写您的测试。
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 上进行单元测试,我不同意。我想要对每个类进行单元测试,包括我不向外部调用者公开的类。 (不过,if I feel the need to write tests for private methods, then I need to refactor.)
编辑:有一条关于“每个测试一个断言”引起的重复的评论。具体来说,如果您有一些代码来设置场景,然后想要对其进行多个断言,但每个测试只有一个断言,您可能会在多个测试中重复设置。
我不采用这种方法。相反,我使用每个场景的测试装置。这是一个粗略的例子:
[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 有一个有趣的article 在他的网站上进行测试。
HTH。
干杯,
罗伯
【讨论】:
测试最初应该失败。然后你应该编写使它们通过的代码,否则你会冒着编写一个有错误但总是通过的测试的风险。
【讨论】:
我喜欢上述 Pragmatic Unit Testing 书中的 Right BICEP 首字母缩略词:
我个人觉得你可以通过检查你得到正确的结果(1+1 应该在加法函数中返回 2),尝试所有你能想到的边界条件(例如使用两个数字和大于 add 函数中的整数最大值)并强制错误条件,例如网络故障。
【讨论】:
好的测试需要可维护。
我还没有完全弄清楚如何在复杂的环境中做到这一点。
随着您的代码库开始达到目标,所有教科书都开始脱胶 成数百或数百万行代码。
好的架构可以控制一些交互爆炸,但不可避免的是 随着自动化测试系统的发展,系统变得更加复杂。
这是您开始必须权衡取舍的地方:
您还需要决定:
您将测试用例存储在代码库的什么位置?
我可以永远继续下去,但我的意思是:
测试需要可维护。
【讨论】:
我不久前在 This MSDN Magazine article 中介绍了这些原则,我认为这对任何开发人员都很重要。
我定义“好”单元测试的方式是,它们是否具有以下三个属性:
【讨论】:
【讨论】:
Jay Fields 有一个关于编写单元测试的lot of good advices,还有一个a post where he summarize the most important advices。在那里你会读到你应该批判性地思考你的背景并判断这些建议是否对你有价值。您会在这里获得大量惊人的答案,但由您决定哪个最适合您的上下文。尝试它们,如果你觉得不好就重构。
亲切的问候
【讨论】:
永远不要假设一个简单的 2 行方法会起作用。编写快速单元测试是防止空测试丢失、负号放错位置和/或细微的范围界定错误困扰您的唯一方法,当您处理它的时间比现在更少时,这是不可避免的。
【讨论】:
我支持“A TRIP”的答案,除了测试应该相互依赖!!!
为什么?
DRY - 不要重复自己 - 也适用于测试!测试依赖项可以帮助 1) 节省设置时间,2) 节省夹具资源,以及 3) 查明故障。当然,前提是您的测试框架支持一流的依赖项。否则,我承认,它们很糟糕。
【讨论】:
单元测试通常基于模拟对象或模拟数据。 我喜欢写三种单元测试:
关键是要避免重播一切,以便能够测试每个功能。
【讨论】:
考虑两种类型的测试并区别对待它们 - 功能测试和性能测试。
为每个使用不同的输入和指标。您可能需要为每种类型的测试使用不同的软件。
【讨论】:
我使用Roy Osherove's Unit Test Naming standards 描述的一致测试命名约定给定测试用例类中的每个方法都具有以下命名样式 MethodUnderTest_Scenario_ExpectedResult。
每个部分都使用大写驼峰式,并由下划线分隔。
我发现这在我运行测试时很有用,测试按被测方法的名称分组。并且有一个约定可以让其他开发者理解测试意图。
如果被测方法已重载,我还会将参数附加到方法名称。
【讨论】: