我看到很多问题都在问“如何”用特定语言进行单元测试,但没有问题问“什么”、“为什么”和“何时”。
- 它是什么?
- 它对我有什么用?
- 我为什么要使用它?
- 我应该什么时候使用它(也什么时候不使用它)?
- 有哪些常见的陷阱和误解
我看到很多问题都在问“如何”用特定语言进行单元测试,但没有问题问“什么”、“为什么”和“何时”。
粗略地说,单元测试是与测试代码隔离地测试代码的一部分。想到的直接优势是:
请注意,如果您的测试代码写入文件、打开数据库连接或通过网络执行某些操作,则更适合将其归类为集成测试。集成测试是一件好事,但不应与单元测试混淆。单元测试代码应该简短、甜美且快速执行。
查看单元测试的另一种方法是先编写测试。这被称为测试驱动开发(简称 TDD)。TDD 带来了额外的优势:
如果你现在不做单元测试,我建议你开始做。买一本好书,几乎任何 xUnit 书都可以,因为它们之间的概念非常容易转移。
有时编写单元测试会很痛苦。当它变成那样时,试着找人来帮助你,并抵制“只写该死的代码”的诱惑。单元测试很像洗碗。这并不总是令人愉快的,但它可以让你隐喻的厨房保持清洁,而且你真的希望它干净。:)
编辑:我想到了一个误解,尽管我不确定它是否如此普遍。我听过一个项目经理说单元测试让团队把所有的代码都写了两次。如果它看起来和感觉都是这样,那么,你做错了。编写测试不仅通常会加快开发速度,而且它还为您提供了一个方便的“现在我完成了”的指示符,否则您不会有。
我不反对丹(虽然更好的选择可能就是不回答)......但是......
单元测试是编写代码以测试系统的行为和功能的过程。
显然测试可以提高代码的质量,但这只是单元测试的表面好处。真正的好处是:
您应该进行单元测试,因为向您的客户提供可维护的优质产品符合您的利益。
我建议您将它用于任何系统或系统的一部分,以模拟现实世界的行为。换句话说,它特别适合企业开发。我不会将它用于一次性/实用程序。我不会将它用于测试有问题的系统部分(UI 是一个常见示例,但并非总是如此)
最大的缺陷是开发人员测试了一个太大的单元,或者他们认为一个方法是一个单元。如果您不了解控制反转,则尤其如此-在这种情况下,您的单元测试将始终变成端到端的集成测试。单元测试应该测试个人行为——大多数方法都有很多行为。
最大的误解是程序员不应该测试。只有糟糕或懒惰的程序员才会相信这一点。建造屋顶的人不应该测试吗?更换心脏瓣膜的医生是否应该不测试新瓣膜?只有程序员可以测试他的代码是否按照他的意图去做(QA 可以测试边缘情况——当被告知做程序员不打算做的事情时代码的行为方式,并且客户端可以进行验收测试——代码做客户为此付出的代价)
与“只打开一个新项目并测试此特定代码”相比,单元测试的主要区别在于它是自动化的,因此是可重复的。
如果您手动测试您的代码,它可能会让您相信代码在当前状态下运行良好。但是一周后,当你对它进行轻微修改时呢?每当您的代码发生任何变化时,您是否愿意再次手动重新测试它?很可能不是:-(
但是,如果您可以随时运行测试,只需单击一下,完全相同的方式,在几秒钟内,那么只要出现问题,它们就会立即显示给您。而且,如果您还将单元测试集成到您的自动化构建过程中,即使在看似完全不相关的更改破坏了代码库的远处部分的某些东西的情况下,它们也会提醒您注意错误 - 当您甚至不会想到有需要重新测试该特定功能。
这是单元测试相对于手动测试的主要优势。但是等等,还有更多:
反过来,单元测试框架使您可以轻松编写和运行测试。
我在大学里从来没有教过单元测试,我花了一段时间才“掌握”它。我读到它,然后说“啊,对,自动化测试,我猜这可能很酷”,然后我就忘记了。
在我真正弄清楚这一点之前花了相当长的时间:假设您正在处理一个大型系统并且您编写了一个小模块。它编译,你完成它的步伐,它工作得很好,你继续下一个任务。九个月后,两个版本之后,其他人对程序的一些看似无关的部分进行了更改,并且破坏了模块。更糟糕的是,他们测试他们的更改,他们的代码可以工作,但他们不测试你的模块;见鬼,他们甚至可能不知道你的模块存在。
现在你遇到了一个问题:损坏的代码在后备箱中,甚至没有人知道。最好的情况是内部测试人员在您发布之前发现它,但是在游戏后期修复代码是昂贵的。如果没有内部测试人员发现它......好吧,那确实会变得非常昂贵。
解决方案是单元测试。他们会在你编写代码时发现问题——这很好——但你可以手动完成。真正的回报是,当你现在从事一个完全不同的项目时,他们会在九个月后发现问题,但是暑期实习生认为如果这些参数按字母顺序排列,它看起来会更整洁 - 然后是单元测试你写回去失败了,有人向实习生扔东西,直到他把参数顺序改回来。这就是单元测试的“原因”。:-)
在这里深入了解单元测试和 TDD 的哲学优点是其中一些关键的“灯泡”观察结果,这些观察结果在我通往 TDD 启蒙之路的初步初步步骤中让我印象深刻(非原创或不一定是新闻)......
TDD 并不意味着编写两倍的代码量。测试代码通常写起来相当快速和轻松,并且是您设计过程的关键部分,而且非常重要。
TDD 帮助您意识到何时停止编码!您的测试让您确信您现在已经完成了足够多的工作,并且可以停止调整并继续下一步。
测试和代码协同工作以实现更好的代码。您的代码可能不好/有问题。你的测试可能是坏的/错误的。在 TDD 中,您寄希望于两者都是坏的/错误的可能性相当低。通常它的测试需要修复,但这仍然是一个很好的结果。
TDD 有助于编码便秘。你知道那种你有很多事情要做的感觉,你几乎不知道从哪里开始吗?现在是星期五下午,如果你再拖延几个小时...... TDD 可以让你快速充实你认为需要做的事情,并让你的编码快速进行。另外,就像实验室老鼠一样,我想我们都会对那盏大绿灯做出反应,并更加努力地再次看到它!
同样,这些设计师类型可以看到他们正在做什么。他们可以出去喝一杯果汁/香烟/iphone休息一下,然后回到显示器上,显示器会立即给他们一个视觉提示,知道他们去了哪里。TDD 给了我们类似的东西。当生活介入时,我们更容易看到我们到达了哪里……
我认为是 Fowler 说的:“经常运行的不完美测试比从未编写过的完美测试要好得多”。我将此解释为允许我在我认为它们最有用的地方编写测试,即使我的其余代码覆盖范围非常不完整。
TDD 以各种令人惊讶的方式提供帮助。好的单元测试可以帮助记录应该做什么,它们可以帮助您将代码从一个项目迁移到另一个项目,并给您一种超越非测试同事的无根据的优越感:)
本演示文稿很好地介绍了所有美味佳肴测试所需的内容。
我想推荐 Gerard Meszaros 的 xUnit 测试模式一书。它很大,但在单元测试方面是一个很好的资源。这是他的网站的链接,他在该网站上讨论了单元测试的基础知识。 http://xunitpatterns.com/XUnitBasics.html
我使用单元测试来节省时间。
在构建业务逻辑(或数据访问)时,测试功能通常会涉及在许多可能尚未完成或尚未完成的屏幕中输入内容。自动化这些测试可以节省时间。
对我来说,单元测试是一种模块化的测试工具。每个公共功能通常至少有一个测试。我编写了额外的测试来涵盖各种行为。
你在开发代码时想到的所有特殊情况都可以记录在单元测试的代码中。单元测试也成为有关如何使用代码的示例的来源。
我发现我的新代码在我的单元测试中破坏了某些东西然后检查代码并让一些前端开发人员发现问题要快得多。
对于数据访问测试,我尝试编写没有更改或自行清理的测试。
单元测试不能解决所有的测试需求。他们将能够节省开发时间并测试应用程序的核心部分。
这是我的看法。我会说单元测试是编写软件测试以验证您的真实软件是否达到预期目的的实践。这从 Java 世界中的 jUnit 开始,并已成为 PHP 以及SimpleTest和phpUnit的最佳实践。这是极限编程的核心实践,可帮助您确保您的软件在编辑后仍能按预期工作。如果您有足够的测试覆盖率,您可以进行重大重构、错误修复或快速添加功能,而不必担心引入其他问题。
当所有单元测试都可以自动运行时,它是最有效的。
单元测试通常与 OO 开发相关联。基本思想是创建一个脚本,为您的代码设置环境,然后对其进行练习;您编写断言,指定您应该接收的预期输出,然后使用上述框架执行您的测试脚本。
该框架将对您的代码运行所有测试,然后报告每个测试的成功或失败。默认情况下,phpUnit 从 Linux 命令行运行,尽管有可用的 HTTP 接口。SimpleTest 本质上是基于网络的,并且更容易启动和运行,IMO。结合 xDebug,phpUnit 可以为您提供代码覆盖率的自动统计信息,有些人认为这非常有用。
一些团队从他们的 subversion 存储库中编写挂钩,以便在您提交更改时自动运行单元测试。
将单元测试保存在与应用程序相同的存储库中是一种很好的做法。
如果您想使用Kent Beck 推广的TDD方法开发项目,则像NUnit、xUnit或JUnit这样的库是必需的:
您可以阅读测试驱动开发 (TDD) 简介或 Kent Beck 的书测试驱动开发:示例。
然后,如果您想确保您的测试涵盖代码的“好”部分,您可以使用NCover、JCover、PartCover或其他软件。他们会告诉你代码的覆盖率。取决于你对 TDD 的熟练程度,你会知道你是否练习得足够好 :)
单元测试是对一个代码单元(例如单个函数)的测试,而不需要该代码单元所依赖的基础设施。即单独测试它。
例如,如果您正在测试的函数连接到数据库并进行更新,那么在单元测试中您可能不想进行该更新。如果它是一个集成测试,你会这样做,但在这种情况下它不是。
因此,单元测试将执行您正在测试的“功能”中包含的功能,而不会产生数据库更新的副作用。
假设您的函数从数据库中检索了一些数字,然后执行了标准偏差计算。你想在这里测试什么?标准差计算正确还是从数据库返回数据?
在单元测试中,您只想测试是否正确计算了标准偏差。在集成测试中,您要测试标准差计算和数据库检索。
使用Testivus。所有你需要知道的就在那里:)
单元测试是关于编写测试应用程序代码的代码。
名称的Unit部分是关于一次测试小代码单元(例如一种方法)的意图。
xUnit 可以帮助进行此测试 - 它们是帮助此测试的框架。其中一部分是自动化测试运行程序,它会告诉您哪些测试失败以及哪些测试通过。
他们还可以预先设置您在每个测试中需要的通用代码,并在所有测试完成后将其删除。
您可以进行测试来检查是否引发了预期的异常,而无需自己编写整个 try catch 块。
我认为你不明白的一点是,像 NUnit(等等)这样的单元测试框架将帮助你自动化中小型测试。通常,您可以在 GUI 中运行测试(例如NUnit就是这种情况),只需单击一个按钮,然后 - 希望 - 看到进度条保持绿色。如果它变成红色,则框架会向您显示哪个测试失败以及究竟出了什么问题。在正常的单元测试中,您经常使用断言,例如Assert.AreEqual(expectedValue, actualValue, "some description")
- 因此,如果两个值不相等,您将看到一条错误消息:“some description: expected <expectedValue> but was <actualValue>”。
因此,作为结论,单元测试将使开发人员的测试更快,更舒适。您可以在提交新代码之前运行所有单元测试,这样您就不会破坏同一项目中其他开发人员的构建过程。
测试驱动开发已经取代了单元测试这个术语。作为一个老前辈,我会提到它的更通用的定义。
单元测试还意味着在更大的系统中测试单个组件。这个单个组件可以是 dll、exe、类库等。它甚至可以是多系统应用程序中的单个系统。因此,最终单元测试最终会成为对您想要调用的更大系统的单个部分的测试。
然后,您将通过测试所有组件如何协同工作来进行集成或系统测试。
首先,无论是关于单元测试还是任何其他类型的自动化测试(集成、负载、UI 测试等),与您建议的主要区别在于它是自动化的、可重复的并且不需要任何人力资源被消耗(= 没有人必须执行测试,它们通常只需按一下按钮即可运行)。
我参加了 FoxForward 2007 的单元测试演讲,并被告知永远不要对任何与数据一起工作的东西进行单元测试。毕竟,如果你测试实时数据,结果是不可预测的,如果你不测试实时数据,你实际上并没有测试你编写的代码。不幸的是,这就是我这些天做的大部分编码。:-)
最近,当我编写一个保存和恢复设置的例程时,我确实对 TDD 有所了解。首先,我验证了我可以创建存储对象。然后,它具有我需要调用的方法。然后,我可以称之为。然后,我可以传递它的参数。然后,我可以传递它特定的参数。依此类推,直到我最终验证它是否会保存指定的设置,允许我更改它,然后恢复它,用于几种不同的语法。
我没有走到最后,因为我需要现在的例行公事,但这是一个很好的练习。
如果给你一堆废话,你会怎么做,你会怎么做牌?
那我们怎么做单元测试呢?
你从小处着手。我刚刚进入的项目直到几个月前才进行单元测试。当覆盖率很低时,我们只需选择一个没有覆盖率的文件并单击“添加测试”。
现在我们已经达到了 40% 以上,而且我们已经成功地摘下了大部分低垂的果实。
(最好的部分是,即使在这么低的覆盖率下,我们已经遇到了许多代码做错事的实例,并且测试发现了它。这是推动人们添加更多测试的巨大动力。)
这回答了为什么你应该进行单元测试。
下面的 3 个视频涵盖了 javascript 中的单元测试,但一般原则适用于大多数语言。
单元测试:现在只需几分钟即可节省数小时 - Eric Mann - https://www.youtube.com/watch?v=_UmmaPe8Bzc
JS 单元测试(非常好) - https://www.youtube.com/watch?v=-IYqgx8JxlU
编写可测试的 JavaScript - https://www.youtube.com/watch?v=OzjogCFO4Zo
现在我只是在学习这个主题,所以我可能不是 100% 正确,而且它比我在这里描述的要多,但我对单元测试的基本理解是你编写一些测试代码(它与你的代码是分开的)主代码)在主代码中调用函数,并使用该函数所需的输入(参数),然后代码检查它是否返回有效的返回值。如果它确实返回了一个有效值,那么您用于运行测试的单元测试框架会显示绿灯(一切正常)如果该值无效,您会得到一个红灯,然后您可以在您之前立即解决问题将新代码发布到生产环境中,如果不进行测试,您实际上可能没有发现错误。
因此,您为当前代码编写测试并创建代码以使其通过测试。几个月后,您或其他人需要修改主代码中的函数,因为之前您已经为该函数编写了测试代码,您现在再次运行并且测试可能会失败,因为编码器在函数中引入了逻辑错误或完全返回了某些东西与该函数应该返回的不同。同样,如果没有适当的测试,该错误可能很难追踪,因为它也可能影响其他代码并且不会被注意到。
此外,您拥有一个运行代码并对其进行测试的计算机程序,而不是您在浏览器中逐页手动执行它,这一事实可以节省时间(javascript 的单元测试)。假设您修改了网页上某个脚本所使用的函数,并且该函数运行良好且适用于其新的预期目的。但是,为了争论,我们还要说在代码中的其他地方还有另一个函数,它依赖于新修改的函数才能正常运行。由于您对第一个函数所做的更改,此依赖函数现在可能会停止工作,但是如果没有由您的计算机自动运行的测试,您将不会注意到该函数存在问题,直到它实际执行并且你'
重申一下,在开发应用程序时运行测试将在您编码时发现这些类型的问题。如果没有适当的测试,您将不得不手动检查整个应用程序,即使这样也很难发现错误,天真地您将其发送到生产环境中,过了一会儿,一位好心的用户会向您发送错误报告(其中不会像您在测试框架中的错误消息那样好)。
当你第一次听到这个主题并且你自己想,我不是已经在测试我的代码了吗?而且您编写的代码已经按预期工作,“为什么我需要另一个框架?”......是的,您已经在测试您的代码,但计算机更擅长这样做。您只需为一个函数/代码单元编写一次足够好的测试,其余的将由强大的 cpu 为您处理,而不是在您进行更改时手动检查所有代码是否仍在工作你的代码。
此外,如果您不想对代码进行单元测试,但随着引入错误的机会增加,您的项目/代码库开始变得更大,它会带来回报。
Unit-testing and TDD in general enables you to have shorter feedback cycles about the software you are writing. Instead of having a large test phase at the very end of the implementation, you incrementally test everything you write. This increases code quality very much, as you immediately see, where you might have bugs.