就个人而言,我总是将单元测试放在一个单独的项目中,因为这就是MSTest的设置方式。但是我正在阅读Martin Fowler的 Refactoring:改进现有代码的设计,他似乎主张不仅将它们放在同一个项目中,而且还将它们放在与它们相同的类中。 '重新测试。
我真的很难想到这样做的方法不同于将测试放在与代码本身不同的区域而不是哲学差异(测试文档或杂乱吗?)。
有没有明确的理由选择一个而不是另一个?或者这主要是一种哲学上的差异?
更新:我不一定相信这种或那种方式,但至少我知道这些论点是什么。我希望我可以选择每个人的答案,但我只能选择一个。
答案 0 :(得分:5)
也许有一些优雅的自我测试代码,但我倾向于支持与你相同的哲学 - 代码分离胜过一些抽象美的概念。在设计课程时,您可以从根本上将其分为三个部分:
我将测试用例视为文档的目的,以及测试套件中安全网的一部分。当一个新程序员正在查看你的代码时,可能很久就停止了它的工作,文档很少是沟通如何使用类的最有效方式。它可以回答有关代码在特定情况下的行为方式的问题,提供类及其方法的一般概述等,但测试用例提供了如何在实际代码中使用该类的具体示例/ em>的
因此,我倾向于说他们应该留在课堂之外,因为这会重新强化这种程度的分离。
答案 1 :(得分:4)
将它们放在正在测试的同一个类中可能会破坏流行的单元测试框架,因为NUnit不会测试没有默认无参数构造函数的类型。
将测试放在不同的文件中但是相同的项目更好,但仍然会导致您的主项目引用测试框架,如NUnit.Framework.dll以及任何模拟框架,如Rhino.Mocks.dll。
在测试的类中进行测试也会增加分布式项目的大小。
将测试分成单独的项目,并且您没有任何这些问题。
答案 2 :(得分:3)
将测试保存在单独的区域(子目录,项目,等等)是保留部署 生产代码的选项的好方法,这通常是您想要做的。
答案 3 :(得分:2)
将测试放在您正在测试的同一个类中有很多方便。很多时候(在开始使用TDD之前),我使用了一个Main方法来测试我编写的代码的功能。并且我没有删除主要方法,因为测试代码位于其中非常有用。这很方便,因为将文档绑定到代码很方便。我没有必要在其他地方查找或搜索测试代码。
话虽如此,TDD的重点不仅仅是测试/质量保证。它更多的是让你考虑你的设计/接口。从这个角度来看,将“测试”(a.k.a“设计”)代码放在一个单独的文件中是有意义的。您的“测试”代码是您班级的客户/用户。
如果在单独的文件中编写测试的目的是技术性的,即“因为单元测试框架是按照这种方式设计的”或“否则您需要在主项目中包含参考”,或“它提高性能“,这听起来不太令人信服。如果是这样的话,情况会有很大不同。
答案 4 :(得分:2)
我认为在同一个类中使用测试代码和生产代码没有任何好处。相反,我看到了一些缺点:
如果没有测试代码,将无法部署和分发生产代码。因此,您可能需要发送200KB或更大的文件,而不是100KB文件。 (当使用TDD时,测试代码行通常等于或大于生产代码行。)
测试结构与生产代码紧密耦合。测试和生产类之间不应存在1:1的关系。相反,测试和行为之间应该存在1:1的关系。
引自http://blog.daveastels.com.s3.amazonaws.com/files/BDD_Intro.pdf
“当你意识到这一切都是关于指定行为而不是编写测试时,你的观点会发生变化。突然间,为每个生产类设置一个Test类的想法是非常有限的。并且考虑测试你的每一个使用自己的测试方法(1-1关系)的方法将是可笑的。“
我主要使用Java编程,我使用Maven来构建我的项目。在那里,我在与它们操作的生产类相同的模块和包中进行测试,但是在不同的目录中(/ src / main / java和/ src / test / java)。当Maven构建项目时,它会执行所有测试,但只有生产代码包含在二进制可分发项中。
附录:10年后(2019年)
现在我主要在Clojure工作,我发现有时候在同一个文件中为函数编写测试有时会很方便。这仅适用于小辅助方法的纯单元测试。 (使用静态类型,其中一些测试可能是不必要的。)Clojure可以从编译的生成版本中排除测试,从而避免二进制膨胀。由于clojure.test
是标准库的一部分,因此在生产文件中编写简单测试不需要额外的依赖项。
业务逻辑测试,这是所有测试中的大多数,我仍然希望保留在单独的文件中。这样,您可以更轻松地专注于阅读生产代码或测试代码 - 系统如何实现与应该如何实现 - 而无需跳过大量交错代码。由于设置数据库和其他依赖项的测试设置混乱,集成测试总是在一个单独的文件中。
还有镜像方面:特别是在开始新的东西时,我首先在测试文件中实现该功能。然后当结构开始稳定时,我提取它的一部分成为生产代码。这种方法称为TDD as if you Meant It。在代码kata中,我可能永远不会在删除项目之前提取生产代码,但在工作项目中,它最终将被拆分为生产和测试代码。
答案 5 :(得分:1)
我认为这主要是一种哲学上的差异。
将它们放在一个单独的项目中可能会有微不足道的性能优势(因为你的测试没有部署到生产中),但它们可能 。
还要记住 Refactoring 是在很久以前写的(以IT术语表示),所以从那时起,优选的实践可能会继续发展。
答案 6 :(得分:0)
将测试代码直接放在要测试的类中有一些实际的好处。
以下所有这些点基本上是不同的效果,其源于如何将测试与代码保持在一起使得如何更难忽略测试。这提醒我们,代码和测试是高度相关的,并且同样重要。将它们组合在一起可以帮助我们将两个概念合并为一个,从而获得实现更好的概念“压缩”的类型(类)(认为类有时被称为“抽象”,但是“压缩”成为我们并非一直想要的核心原因)在我们的抽象中解耦)。
这使得我们编写代码的程序员很少会忘记考虑一下测试。这看起来就像是先更改代码而无需任何测试,然后再运行测试,然后再发布/推送/合并,然后看它们仍然通过,而不去考虑是否需要对它们进行更多的测试鉴于更改的性质而写。
这使程序员更难以懒惰。如果测试是“一直到那边”,那么程序员必须找到测试代码,仔细阅读,找到他们只是在看/想着改变的代码的相关部分。但是,如果您更喜欢一种方法来测试一种方法,然后再测试一种方法,那么您就必须非常懒惰,不要“觉得”找到测试。
如果程序员已经在乎实际代码中的代码质量,则此方法可帮助他们同样在乎测试中的代码质量,因为两者之间的区别较小。这确实很重要,因为可以说测试中的代码质量甚至比其余代码的质量还要重要,因为要避免测试最终使团队进一步退缩,必须对测试代码进行精心维护。而不是他们的帮助,这实际上是正确使用测试中最困难的部分。
如果您正在执行TDD,或者甚至编写一些代码然后立即编写测试,您通常会发现自己在测试和代码之间来回走动,试图将两者进行比较。但是,如果它们属于同一班级,这会使比较两者的行为更加自然,对认知的要求也降低。这也导致测试无法(在情感上和精力上)使您退缩不止于帮助您,这可能导致测试受挫并删除所有内容。
这导致测试和代码之间更自然的代码重用。这具有双重目的。它允许您至少在某些时间将运行时断言和测试的思想合并为一件事情。这样做的方法是,除了执行断言(如果断言失败会抛出异常)之外,这些方法无用。然后,您可以随意重用代码中的那些断言方法,验证合同以及在测试中验证代码。无论如何,验证合同并在违反合同时抛出异常被认为是一种好习惯,这使其更加自然。然后,某些测试甚至可以变成一件简单的事情,它们仅以正确的方式执行正确的方法,然后这些方法从字面上进行某种程度的自我测试,因为它们已经调用了那些断言方法。然后,您可以决定在发布代码时保留所有这些断言,以便更好地监视和报告生产,或者关闭至少其中一些断言以获取更快的代码。当需要更高级别的报告时,甚至可以根据需要在生产中启用它们。重用还可以减少所需测试代码的总量,从而帮助我们进一步确保测试物有所值。
测试可以是很好的文档形式。通过对代码进行测试,可以使文档与正在编写的代码保持一致,这通常是程序员真正了解代码的最佳方式。
如果您已经在测试文件和被测试的代码之间建立了一对一的映射,则可以更轻松地进行重构。如果您重命名一个类,则不必再重命名其测试。
将测试放置在代码中使具有一对一的测试映射和正在测试的代码更加自然。有人说这很糟糕,因为测试应该测试行为而不是类/方法,但是当该反对意见有效时,实际上就是代码味道。代码的编写方式应该既适合于所使用的语言,又应使其看上去好像该语言是专门为您的代码设计的。然后,代码应将该概念与语言构造相结合,以使该代码成为该语言可能的实际最终行为的最自然表达/规范。如果测试“代码”意味着您不测试“行为”,则您已经遇到了代码质量问题。在面向类型的编程中(想想面向对象的编程,在大多数现代的OO语言中,它实际上是面向类的编程,实际上是面向类型的编程,因为类是您定义自己的类型的一种方式),这当然是很合理的您希望每种类型都在技术水平上进行单独测试(对该类型的值执行操作的准确性)。这是因为您制作的每种新类型实际上都将成为您编写代码所用语言的核心部分,并且您不希望自己的编程语言有很多错误吗?这也不排除直接编程最终用户功能的原因,因为这些功能可以实现为自己的类型(请考虑每个网页的类型或每个API命令的类型。因此某些类型将更接近最终用户,并且其他的则要低得多,但是两者都需要在类型上胜过,因为那是它们的本质。