来源:周旭龙
链接:http://www.cnblogs.com/edisonchou/p/5467573.html
此模式的要点在于:你不是具体地测试一个类,而是测试产品代码中的一个接口或者基类。
当然,在.NET中我们也可以通过泛型来实现此模式,例如下面的代码:
public abstract class GenericParserTestswhere T : IStringParser // 01.定义参数的泛型约束 { protected abstract string GetInputHeaderSingleDigit(); protected abstract string GetInputHeaderWithMinorVersion(); protected abstract string GetInputHeaderWithRevision(); // 02.返回泛型变量而非接口 protected T GetParser(string input) { // 03.返回泛型 return (T)Activator.CreateInstance(typeof(T), input); } [Test] public void TestGetStringVersionFromHeader_SingleDigit_Found() { string input = GetInputHeaderSingleDigit(); T parser = GetParser(input); bool result = parser.HasCorrectHeader(); Assert.AreEqual(false, result); } [Test] public void TestGetStringVersionFromHeader_WithMinorVersion_Found() { string input = GetInputHeaderWithMinorVersion(); T parser = GetParser(input); bool result = parser.HasCorrectHeader(); Assert.AreEqual(false, result); } [Test] public void TestGetStringVersionFromHeader_WithRevision_Found() { string input = GetInputHeaderWithRevision(); T parser = GetParser(input); bool result = parser.HasCorrectHeader(); Assert.AreEqual(false, result); } } public class DBLogStringParserTests : GenericParserTests { protected override string GetInputHeaderSingleDigit() { return 'Header;1'; } protected override string GetInputHeaderWithMinorVersion() { return 'Header;1.1'; } protected override string GetInputHeaderWithRevision() { return 'Header;1.1.1'; } }
二、优秀单元测试的支柱
要编写优秀的单元测试,它们应该同时具有 可靠性、可维护性 及 可读性。
2.1 编写可靠的测试
一个可靠的测试能让你觉得自己对事态了如指掌,能够从容应对。以下是一些指导原则和技术:
(1)决定何时删除或修改测试
一旦测试写好并通过,通常我们不应该修改或删除这些测试,因为它们是我们得绿色保护网。但是,有时候我们还是需要修改或者删除测试,所以需要理解什么情况下修改或删除测试会带来问题,什么情况下又是合理的。一般来说,如果有产品缺陷、测试缺陷、语义或者API更改或者是由于冲突或无效测试,我们需要修改和删除测试代码。
(2)避免测试中的逻辑
随着测试中逻辑的增多,出现测试缺陷的几率就会呈现指数倍的增长。如果单元测试中包含了下列语句就是包含了不应该有的逻辑:
switch、if或else语句;
foreach、for或while循环;
这种做法不值得推荐,因为这样的测试可读性较差,也比较脆弱。通常来说,一个单元测试应该是一系列方法的调用和断言,但是不包含控制流程语句,甚至不应该将断言语句放在try-catch中。
(3)只测试一个关注点
如果我们的单元测试对多个对象进行了断言,那么这个测试有可能测试了多个关注点。在一个单元测试中验证多个关注点会使得事情变得复杂,却没有什么价值。你应该在分开的、独立的单元测试中验证多余的关注点,这样才能发现真正失败的地方。
(4)把单元测试和集成测试分开
掐面讨论了测试的绿色安全区,我们需要的就是准备一个单独的单元测试项目,项目中仅包含那些在内存中运行,结果稳定,可重复执行的测试。
(5)用代码审查确保代码覆盖率
如果覆盖率低于20%,说明我们缺少很多测试,我们不会知道下一个开发人员将怎么修改我们得代码。如果没有回失败的测试,可能就不会发现这些错误。
2.2 编写可维护性的测试
可维护性是大多数开发者在编写单元测试时面对的核心问题之一。为此我们需要:
(1)只测试公共契约
(2)删除重复测试(去除重复代码)
(3)实施测试隔离
测试隔离的基本概念是:一个测试应该总是在它自己的小世界中运行,与其他类似或不同的工作的测试隔离,甚至不知道其他测试的存在。
2.3 编写可读性的测试
不可读的测试几乎没有任何意义,它是我们向项目的下一代开发者讲述的故事,帮助开发者理解一个应用程序的组成及其开端。
(1)单元测试命名
这个前面我们讨论过,应该包括三部分:被测试方法名_测试场景_预期行为,如果开发人员都是用这种规范,其他的开发人员就能很容易进入项目,理解测试。
(2)变量命名
通过合理命名变量,你可以确保阅读测试的人可以尽快地理解你要验证什么(相对于理解产品代码中你想要实现什么)。请看下面的一个例子:
[Test] public void BadlyNameTest() { LogAnalyzer log = new LogAnalyzer(); int result = log.GetLineCount('abc.txt'); Assert.AreEqual(-100, result); } [Test] public void GoodNameTest() { LogAnalyzer log = new LogAnalyzer(); int result = log.GetLineCount('abc.txt'); const int COULD_NOT_READ_FILE = -100; Assert.AreEqual(-COULD_NOT_READ_FILE, result); }
经过改进后,我们会很容易理解这个返回值的意义。
(3)有意义的断言
只有当测试确实需要,并且找不到别的办法使测试更清晰时,你才应该编写定制的断言信息。编写好的断言信息就像编写好的异常信息,一不小心就会犯错,使读者产生误解,浪费他们的时间。
(4)断言和操作分离
为了可读性,请不要把断言和方法调用写在同一行。
// 断言和操作写在了同一行 Assert.AreEqual(-COULD_NOT_READ_FILE, log.GetLineCount('abc.txt'));
三、小结
这一篇我们学习了:
尽量将测试自动化,尽可能多次地运行测试,尽可能持续地进行产品交付;
把集成测试和单元测试分开,为整个团队构建一个绿色安全区,该区域中所有的测试都必须通过;
按照项目和类型组织测试,把测试分别放在不同的目录、文件夹或者命名空间中;
使用测试类层次,对一个层次中相关的几个类进行同一组测试,或者对共享一个通用接口或者基类的类型进行同一组测试;
优秀单元测试具有三大支柱:可读性、可维护性与可靠性,它们相辅相成。
如果人们能读懂你的测试,就能理解和维护测试,如果测试能够通过,它们也会信任测试。一旦实现这个目标,你就能知道系统是否正常工作,具有了处理变更和在需要时修改代码的能力;
参考资料
(1)Roy Osherove 著,金迎 译,《单元测试的艺术(第2版)》
.NET单元测试系列文章:
|