如何编写良好的单元测试?

40

有人能推荐一些关于单元测试的书籍或资料吗?

有些人认为没有单元测试的代码是遗留的代码。而今,测试驱动开发是管理大型软件项目的方法。我非常喜欢C++,我自学了它,没有任何正规教育。我以前从未接触过单元测试,所以感到很落后。我认为单元测试很重要,长远来看会很有帮助。我会非常感激这个主题上的任何帮助。

我的主要关注点是:

  1. 什么是单元测试?它是应该分析的全面测试用例列表吗?所以让我们假设我有一个叫做“复数”(Complex Numbers)的类,其中包含一些方法(比如找出共轭、重载赋值运算符和重载乘法运算符等),这个类应该有哪些典型的测试用例?是否有选择测试用例的方法论?

  2. 有没有可以为我创建单元测试的框架,或者我必须编写自己的测试类?我在Visual Studio 2008中看到了一个名为“Test”的选项,但从未让它起作用。

  3. 什么是单元测试的标准?每个类中的每个函数都应该有一个单元测试吗?为每个类都编写单元测试是否合理?


8
TDD并不是一种“管理大型软件项目的方法”,而是一种编写更好代码的方法。即使你实践TDD,你仍需要一个项目管理的流程。 - Mathias
10个回答

23

一个重要的观点(我一开始没有意识到)是,单元测试是一种可以单独使用的测试技术,无需应用完整的测试驱动开发方法。

例如,您有一个想要通过为问题区域添加单元测试来改进的旧应用程序,或者您想在现有应用程序中找到漏洞。现在您编写一个单元测试来暴露问题代码,然后修复它。这些是半自动化的测试,但完全可以与您当前(非TDD)的开发流程相适应。

我发现有用的两本书是:

Microsoft .NET中的测试驱动开发

这是一个非常实用的测试驱动开发案例,继承自Kent Beck的原始TDD书籍。

C#和nUnit中的务实单元测试

书中直接阐述了什么是单元测试以及如何应用它。

针对您提出的观点:

  1. 在实际操作中,一个单元测试是类中的一个方法,其中包含足够的代码来测试应用程序的一个方面/行为。因此,您通常会有许多非常简单的单元测试,每个测试应用程序代码的一小部分。例如,在nUnit中,您创建一个包含任意数量测试方法的TestFixture类。关键点是测试“一个单元”代码,即尽可能小的(合理)单元。您不测试您使用的底层API,只测试您编写的代码。

  2. 有一些框架可以减轻创建测试类的繁琐工作,但我不建议使用。要创建有用的单元测试,为代码提供安全保障,开发人员必须思考测试何时以及如何进行。如果你开始依赖生成的单元测试,很容易把它们看作只是另一个必须完成的任务。如果你发现自己处于这种情况,那么你完全错误了。

  3. 没有简单的规则来确定每个类、每个方法等需要多少单元测试。你需要查看应用程序代码,并对复杂性存在的地方进行有根据的评估,并编写更多的测试用例。大多数人从测试公共方法开始,因为这些方法通常会执行其余的私有方法。但是并非总是这样,有时还需要测试私有方法。

简而言之,即使有经验的单元测试人员也会从编写明显的单元测试开始,然后寻找更微妙的测试用例,一旦编写了明显的测试用例,这些更微妙的测试用例就变得更加清晰。他们并不期望一开始就得到所有的测试用例,而是在想到时添加它们。


7

虽然您已经接受了一个答案,但是我想推荐一些其他还未提到的书籍:

  • 《Working Effectively with Legacy Code》- Michael Feathers - 据我所知,这是唯一一本足以应对将原本不适合测试的现有代码转换为可测试代码的书籍。它被写成更像参考手册的形式,分为三个部分:工具和技术概述、针对遗留代码中常见障碍的一系列主题指南以及在整本书中引用的一组特定依赖项打破技术。
  • 《敏捷原则、模式与实践》- Robert C. Martin - Java示例,有一个C#示例的续集。两者都很容易适应C++。
  • 《代码整洁之道》- Robert C. Martin - Martin将其描述为他的APPP书籍的前传,我也同意。这本书为专业主义和自律提出了论据,这是任何严肃的软件开发人员所必需的两个基本品质。

Robert(Uncle Bob)Martin的这两本书涵盖的内容远不止单元测试,但它们强调了单元测试对于代码质量和生产力的好处。我经常参考这三本书。


我会说Lasse Kossela的《测试驱动》是一个很好的入门书籍,更直接地针对TDD。还有一本关于“.NET中的棕地开发”(我想)的书,可能涉及在现有代码周围塞入测试的问题 - 尽管我没有读过它。 - cwash

2
  1. 使用测试驱动设计,通常先编写测试用例。这些测试应该覆盖你实际使用或者将要使用的操作。也就是说,除非它们对客户端代码执行其工作是必要的,否则不应存在。选择测试用例有一定的技巧。有明显的事情,比如测试边界条件,但最终,没有人找到一种真正可靠、系统化的方法来确保测试(单元测试或其他)涵盖所有重要条件。
  2. 是的,有一些框架。其中几个最著名的是:
    Boost Unit Test Framework
    CPPUNit

    CPPUnit 是 JUnit 的一个移植版本,所以那些以前使用过 JUnit 的人可能会觉得很舒服。否则,我倾向于推荐 Boost -- 他们还有一个测试库来帮助编写单个测试 -- 这是一个相当方便的补充。

  3. 单元测试应足以确保代码正常工作。如果(例如)你有一个私有函数在内部使用,通常不需要直接测试它。相反,你测试提供公共接口的东西。只要那个正常工作,外部世界如何执行其工作就不是它的事了。当然,在某些情况下,测试小块更容易,当这样的情况出现时,这是完全合法的 -- 但最终你关心的是可见接口,而不是内部。当然,整个外部接口应该被激活,并且测试用例通常选择通过代码路径进行激活。再次强调,单元测试与其他类型的测试没有什么特别不同。主要是一种更系统化的应用常规测试技术。


4
谷歌的 C++ 测试框架也应该被提到:http://code.google.com/p/googletest/ - Georg Fritzsche
+1 gf,googletest(和googlemock)是C++最好的单元测试框架。在编写测试时的开销几乎可以说是C++中最小的。其他框架过于强调测试套件和其他无关的基础设施,而googletest则专注于帮助轻松编写测试。 - Teemu Kurppa

2

在.NET中,我强烈推荐罗伊·奥舍洛夫(Roy Osherove)的《单元测试的艺术》,它非常全面且充满了良好的建议。


2
现今,测试驱动开发是管理大型软件项目的一种简单方法。这是因为TDD允许您在每次更改后确保之前正常工作的一切仍然正常工作,并且如果出现问题,它可以更容易地确定出错的位置。
什么是单元测试?它是应该分析的测试用例的全面清单吗?
单元测试是一段代码,它要求您的代码“单元”执行操作,然后验证操作是否确实执行并且结果符合预期。如果结果不正确,则会引发/记录错误。
所以假设我有一个名为“复数”的类,其中包含一些方法(例如查找共轭,重载赋值运算符和重载乘法运算符)。对于这样的类,应该使用哪些典型的测试用例?是否有选择测试用例的方法学?
理想情况下,您将测试所有代码。
当你创建一个类的实例时,它将以正确的默认值创建。
当你要求它查找共轭时,它会找到正确的共轭(也测试边界情况,如零的共轭)。
当你赋值时,该值被分配并正确显示。
当你将复数乘以一个值时,它会正确地进行乘法运算。
有没有可以为我创建单元测试的框架,还是我必须编写自己的测试类?
请参见CppUnit
我在Visual Studio 2008中看到了一个名为“Test”的选项,但从未使其工作过。
不确定。我没有使用VS 2008,但它可能仅适用于.NET。
单元测试的标准是什么?每个类中的每个函数都应该有一个单元测试吗?每个类都有单元测试是否有意义?是的,它有意义。虽然写这么多代码(并且随着每次更改维护)是很繁琐的,但对于大型项目来说,这是值得付出的代价:它保证您对代码库的更改只做您想要的事情,而不会做其他事情。此外,当您进行更改时,需要更新该更改的单元测试(以便它们再次通过)。在TDD中,您首先确定您想让代码做什么(例如,您的复数类),然后编写验证这些操作的测试,然后编写类以使测试正确地编译和执行(没有多余的内容)。这确保您编写尽可能少的代码(并且不会过度复杂化复杂类的设计),还确保您的代码所做的事情。在编写代码结束时,您有一种测试其功能并确保其正确性的方法。您还可以获得使用代码的示例,随时可以访问。
进一步阅读/文献资料,请查看在单元测试和TDD中使用的"依赖注入"和方法存根。

1

我无法回答有关 Visual Studio 2008 的问题,但我知道Netbeans有一些集成工具可供您使用。

  1. 代码覆盖工具可以让您查看已经检查过哪些路径,以及单元测试实际覆盖了多少代码。
  2. 它内置了对单元测试的支持。

至于测试质量,我借用了 Andrew Hunt 和 David Thomas 的《Pragmatic Unit Testing in Java with JUnit》中的一些内容:

单元测试应该检查BICEP:边界、反向关系、交叉检查、错误情况和性能。

测试的质量也是由A-TRIP决定的: Automatic(自动化的), Thorough(彻底的), Repeatable(可重复的), Independent(独立的)和Professional(专业的)。


1

单元测试是一种简单的方式,通过检查代码是否符合一组定义好的条件来确保预期的输出结果。正如Steven所指出的那样,这些“练习”应该跨越一系列标准(“BICEP”)。理想情况下,您应该测试所有的类和这些类中的所有方法,尽管总会有一些判断的余地:测试不应该成为目的本身,而是应该支持更广泛的项目目标。

好的,那么...理论很好,但要真正理解单元测试,我的建议是收集适当的工具并开始实际操作。就像大多数编程事物一样,如果您有正确的工具,通过实践学习很容易。

首先,获取NUnit的副本。它是免费的,安装和使用非常容易。如果您需要一些文档,请查看Pragmatic Unit Testing in C# with NUnit.

接下来,前往 http://www.testdriven.net/获取TestDriven.net的副本。 它可以安装到Visual Studio 2008中,并提供了右键访问完整测试工具的功能,包括针对文件、目录或项目运行NUnit测试(通常,测试是在单独的项目中编写的)。 您还可以使用调试运行测试,或者更酷的是,在NCover的副本上运行所有测试。 NCover将向您展示确切正在运行的代码,以便您可以确定需要改进测试覆盖范围的位置。 TestDriven.net的专业许可证费用为170美元,但如果您像我一样使用它,它很快就会成为您工具箱中不可或缺的工具。无论如何,我发现它是一个极好的职业投资。

祝您好运!


0

以下是关于何时不编写单元测试的内容(即何时可以跳过单元测试并且甚至更好):应该测试内部实现,还是只测试公共行为?

简而言之:

  • 当您可以自动化集成测试时(因为拥有自动化测试非常重要,但这些测试不必是单元测试时)
  • 当运行集成测试套件很便宜时(如果需要两天才能运行或者您无法让每个开发人员都可以访问集成测试设备,则没有意义)
  • 当在集成测试之前不必查找错误时(这在一定程度上取决于组件是单独开发还是增量开发)

0

买一本书名为 "xUnit Test Patterns:重构测试代码"。非常优秀。它涵盖了高层次的策略决策和低级别的测试模式。


0
现如今,测试驱动开发是以轻松的方式管理大型软件项目的方法。TDD构建在单元测试之上,但它们是不同的。您不需要使用TDD来使用单元测试。我个人更喜欢先写测试,但并不觉得自己完全做到了TDD。
什么是单元测试?
单元测试是一小段代码,用于测试一个单元的行为。人们对单元的定义有所不同,但通常情况下,它们具备以下特点:
1. 运行速度快 2. 相互独立 3. 仅测试代码库中的一小部分(一个单元) 4. 二进制结果——通过或失败 5. 只应测试一个单元的一个结果(对于每个结果创建不同的单元测试) 6. 可重复执行
有没有可以创建单元测试的框架呢?

编写测试 - 是的,但我从未见过有人对它们说过任何好话。
为了帮助您编写和运行测试,有很多测试。

每个类中的每个函数都应该有一个单元测试吗?

在这方面,您有几个不同的阵营 - 100% 的支持者会说是。每个方法都必须经过测试,并且您应该拥有100%的代码覆盖率。另一个极端是,单元测试只应涵盖您遇到错误或预计发现错误的区域。中间地带(也是我的立场)是对所有“不太容易出错”的内容进行单元测试。设置器/获取器以及仅调用单个其他方法的任何内容。我旨在拥有80%的代码覆盖率和低CRAP因子(因此我没有测试某些东西,因为它“太复杂而无法测试”)。

这本书帮助我理解了单元测试JUnit实战。很抱歉我在C++领域没有太多经验,所以无法提供基于C++的替代方案。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接