你是否将单元测试放在同一个项目或另一个项目中?

为了方便起见,您是将单元测试放在同一个项目中,还是将它们放在一个单独的程序集中?

如果您像我们一样将它们放在一个单独的程序集中,那么解决方案中就会有许多额外的项目。在编写代码时进行单元测试非常好,但是如何在没有这些额外组件的情况下发布应用程序呢?

53955 次浏览

在我看来,单元测试应该放在与生产代码分开的程序集中。以下是将单元测试放在与生产代码相同的程序集中的一些缺点:

  1. 单元测试与生产代码一起发布,唯一与生产代码一起发布的就是生产代码。
  2. 程序集会因为单元测试而不必要地膨胀。
  3. 单元测试可以影响自动化或连续构建等构建过程。

我真的不知道有什么优点。有一个额外的项目(或10个)不是一个缺点。

编辑: 关于建造和运输的更多信息

我进一步建议任何自动化的构建过程将生产和单元测试放置在不同的位置。理想情况下,单元测试生成过程仅在生产代码生成时运行,并将产品文件复制到单元测试目录中。这样做的结果是实际的位被分开运输,等等。此外,此时对特定目录中的所有测试运行自动化单元测试是相当平凡的。

总而言之,下面是每天构建、测试和发送比特和其他文件的总体思路:

  1. 生产构建运行,将生产文件放置到特定的“生产”目录中。
    1. 只建立生产项目。
    2. 将已编译的位和其他文件复制到“生产”目录中。
    3. 复制位和其他文件到一个发布候选目录,即圣诞节发布目录将是“ Release20081225”。
  2. 如果生产生成成功,则运行单元测试生成。
    1. 将生产代码复制到“测试”目录。
    2. 将单元测试构建到“测试”目录。
    3. 运行单元测试。
  3. 向开发人员发送构建通知和单元测试结果。
  4. 当接受一个发布候选版本(如 Release20081225)时,发布这些比特。

我的单元测试总是在单独的项目中进行。事实上,对于我的解决方案中的每个项目,都有一个单独的测试项目与之相伴。测试代码不是应用程序代码,不应该与它混合在一起。将它们保存在单独的项目中的一个优势——至少使用 TestDriven。Net ——就是我可以右键单击一个测试项目并运行该项目中的所有测试,只需单击一下即可测试整个应用程序代码库。

单独的项目,但在同一个解决方案。(我曾经为测试和生产代码的产品提供单独的解决方案——这太可怕了。你总是在两者之间转换。)

分开项目的原因是由其他人陈述的。请注意,如果您使用的是数据驱动测试,那么如果您将测试包含在生产组装中,那么您最终可能会产生相当大的膨胀。

如果需要访问生产代码的内部成员,请使用 InternalsVisibleTo

我在同一个项目和不同的项目之间波动。

如果您正在发布一个使用生产代码发布测试代码的库是一个问题,否则我发现它通常不是(尽管在您尝试之前有一个强大的心理障碍)。

当把测试放在同一个项目中时,我发现在测试和它们所测试的代码之间切换变得更容易,并且更容易重构/移动它们。

我把他们分开了。作为一般规则,程序集的名称反映了命名空间的名称。所以如果有一个叫做公司的项目。产品。Sln,它的输出(程序集名称)为 Company。产品。功能: dll。测试项目是公司。产品。特写。Tests.sln,屈服公司。产品。特写。Tests.dll.

您最好将它们保存在单个解决方案中,并通过配置管理器控制输出。我们为每个主要分支(开发、集成、生产)提供了一个命名配置,以代替使用默认的 Debug 和 Release。一旦设置好了配置,就可以通过单击 Configuration Manager 中的“ Build”复选框来包含或排除它们。(要获取配置管理器,右键单击解决方案并转到配置管理器。)注意,我发现 VisualStudio 中的 CM 有时存在 bug。有几次,我不得不进入项目和/或解决方案文件来清理它创建的目标。

此外,如果您正在使用 TeamBuild (并且我确信其他。NET 构建工具是相同的) ,然后可以将构建与命名配置关联。这意味着,例如,如果您不为您的“生产”构建构建您的单元测试,那么构建项目也可以知道这个设置,而不会构建它们,因为它们被标记为这样的设置。

另外,我们过去常常从构建机器上进行 XCopy 下拉操作。脚本只是省略了复制任何名为 * 的内容。测试。从部署的 DLL。很简单,但很有效。

我不明白为什么经常反对使用生产代码部署测试。我领导的团队只有一个小小的帽子(从14人增加到130人)。我们有六个左右的 Java 应用程序,我们发现将测试部署到这个领域来在表现出异常行为的 具体点机器上执行它们非常有价值。随机问题发生在现场,能够抛出几千个单元测试的神秘零成本是无价的,往往诊断问题在几分钟... 包括安装问题,片状 RAM 问题,机器特定的问题,片状网络问题,等等。我认为在实地进行测试是非常有价值的。另外,随机的问题会在随机的时间出现,而且单元测试已经在那里等待执行是很好的。硬盘空间很便宜。就像我们试图将数据和函数保持在一起(OO 设计)一样,我认为将代码和测试保持在一起(函数 + 验证函数的测试)有一些根本性的价值。

我想把我的测试放在 C #/的同一个项目中。NET/VisualStudio2008,但是我仍然没有足够的研究来实现它。

将 Foo.cs 与 FooTest.cs 保持在同一个项目中的一大好处是,当一个类缺少一个兄弟测试时,会不断地提醒开发人员!这鼓励更好的测试驱动编码实践... 漏洞更加明显。

我确实受到了 Robert Lopez 的 洪水神经网络图书馆单元测试框架的启发。它对每个单元测试类使用不同的项目,并且有一个包含所有这些项目的解决方案,以及一个编译和运行所有测试的主项目。

整洁的东西也是该项目的布局。源文件在一个文件夹中,但是 VS 项目的文件夹在下面。这允许您为不同的编译器创建不同的子文件夹。所有 VS 项目都随代码一起提供,因此任何人都很容易运行任何或所有单元测试。

将单元测试与代码放在同一个项目中,以实现更好的封装。

您可以轻松地测试内部方法,这意味着您不会将本应该是内部方法的方法公开。

另外,让单元测试与您正在编写的代码保持一致也是一件非常好的事情。当您编写一个方法时,您可以很容易地找到相应的单元测试,因为它在同一个项目中。当生成包含 unitTest 的程序集时,unitTest 中的任何错误都会给出一个编译错误,因此必须保持单元测试是最新的,以便生成。在一个单独的项目中进行单元测试,可能会导致一些开发人员忘记构建单元测试项目,并在一段时间内错过破损的测试。

您可以使用编译标记(IF # Debug)从生产代码中删除单元测试。

自动集成测试(made i NUnit)应该在一个单独的项目中,因为它们不属于任何单独的项目。

我会说把他们分开。

除了上面提到的其他原因之外,将代码和测试放在一起会使测试覆盖率数字产生偏差。当您报告单元测试覆盖率时,报告的覆盖率会更高,因为在运行单元测试时会覆盖测试。当您报告集成测试覆盖率时,报告的覆盖率较低,因为集成测试不会运行单元测试。

如果使用 NUnit框架,那么还有一个额外的原因要将测试放在同一个项目中。 考虑下面的生产代码与单元测试混合的例子:

public static class Ext
{
[TestCase(1.1, Result = 1)]
[TestCase(0.9, Result = 1)]
public static int ToRoundedInt(this double d)
{
return (int) Math.Round(d);
}
}

这里的单元测试作为测试代码的文档和规范。我不知道如何实现这种自编文档的效果,因为测试位于一个单独的项目中。函数的用户必须搜索测试才能看到那些测试用例,这是不太可能的。

更新 : 我知道这样使用 TestCase属性不是 NUnit 的开发人员所希望的,但是为什么不呢?

我知道这是一个非常老的问题,但我想加上我的经验,我最近改变了单元测试的习惯,从单独的项目到同一个。

为什么?

首先,我非常倾向于保持主项目文件夹结构与测试项目相同。因此,如果我有一个在 Providers > DataProvider > SqlDataProvider.cs下的文件,那么我将在我的单元测试项目中创建相同的结构,如 Providers > DataProvider > SqlDataProvider.Tests.cs

但是在项目变得越来越大之后,一旦您将文件从一个文件夹移动到另一个文件夹,或者从一个项目移动到另一个项目,那么将这些文件与单元测试项目同步将变得非常麻烦。

其次,从要测试的类导航到单元测试类并不总是很容易。这对于 JavaScript 和 Python 来说更加困难。

最近,我开始练习,我创建的每一个文件(例如 SqlDataProvider.cs)都要创建另一个后缀为 Test 的文件,比如 SqlDataProvider.Tests.cs

一开始,它似乎会膨胀文件和库引用,但长期来看,你会消除移动文件综合症在第一眼,而且你会确保,每一个文件的候选人被测试将有一个 .Tests后缀对文件。它使您很容易跳转到测试文件(因为它是并排的) ,而不是查看单独的项目。

您甚至可以编写业务规则来扫描整个项目并识别没有。测试文件,并向所有者报告它们。你也可以很容易地告诉你的测试运行程序以 .Tests类为目标。

特别是对于 Js 和 Python,您不需要从不同的路径导入引用,只需使用测试目标文件的相同路径即可。

我使用这个实践有一段时间了,我认为在项目规模与可维护性以及项目新手的学习曲线之间进行权衡是非常合理的。

在 TypeScript 项目中花费了一些时间,在这些项目中,测试通常与测试代码一起放在一个文件中,我逐渐喜欢这种方法,而不是将它们分开:

  • 导航到测试文件更快。
  • 当您重命名被测试的类时,更容易记住重命名测试。
  • 当您移动被测试的类时,更容易记住移动测试。
  • 如果一个类错过了测试,那么这一点很明显。
  • 您不需要管理两个重复的文件结构,一个用于测试,一个用于代码。

所以当我开始一个新的。NET 核心项目最近,我想看看是否有可能在 C # 项目中模仿这种结构,而不需要在最终版本中发布测试或测试程序集。

到目前为止,在项目文件中加入以下几行似乎效果很好:

  <ItemGroup Condition="'$(Configuration)' == 'Release'">
<Compile Remove="**\*.Tests.cs" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' != 'Release'">
<PackageReference Include="nunit" Version="3.11.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
</ItemGroup>

上述操作确保了在 Release配置中所有名为 *.Tests.cs的文件都被排除在编译之外,并且所需的单元测试包引用也被删除。

如果您仍然希望能够在它们的发行版配置中对类进行单元测试,那么您只需创建一个从 Release派生出来的新配置,称为类似于 ReleaseContainingTests的配置。


更新: 在使用这个技术一段时间后,我发现在 VS Code 中自定义图标是很有帮助的,可以让测试(和其他事情)在浏览器面板中更加突出:

VS Code Screenshot

要做到这一点,使用材质图标主题扩展,并添加类似下面的东西到您的 VS 代码偏好设置 JSON:

"material-icon-theme.files.associations": {
"*.Tests.cs": "test-jsx",
"*.Mocks.cs": "merlin",
"*.Interface.cs": "Raml",
}

更新2:

如果您得到错误 Program has more than one entry point defined,那么您可以遵循指示 给你,这基本上是添加 <GenerateProgramFile>false</GenerateProgramFile>内的 <PropertyGroup>元素在您的项目的 .csproj文件,这将停止测试框架添加自己的入口点。

正如其他人所回答的那样—— 在不同的项目中进行测试

有一件事没有被提及,那就是除了 .dll文件之外,实际上不能对任何东西运行 nunit3-console.exe

如果您计划通过 TeamCity 运行测试,那么这将带来一个问题。

假设你有一个控制台应用项目,当你编译的时候,它返回一个可执行的 .exebin文件夹。

从 ABc0开始,你将无法运行任何在控制台应用中定义的测试。

换句话说,一个控制台应用返回一个 exe文件,一个类库返回一个 dll文件。

我今天被这个东西咬了一口,感觉糟透了

我最近被派往多克。当 Dockerfile 可以轻松地复制/src 目录并离开/test 目录时,我看不出单独拥有一个项目有什么价值。 但我是新来的,可能错过了一些东西。

为了方便起见,您是将单元测试放在同一个项目中,还是将它们放在一个单独的程序集中?

不,永远,永远不要那样做。组件的使用者不能将测试代码与生产代码分离,因此,他们不能将测试代码用于任何重要的事情。

从注重安全的使用者的角度来看,程序集中的每个公共符号都是客户产品的攻击表面区域的一部分。为什么他们要使用一个包括 任何非生产安全表面积的组装?

如果您认为在生产组件中包含单元测试是可以的,那么您真是可耻!

如果你们接受这样的组件包含在你们的产品中,那将是你们的耻辱。


可以发布测试程序集吗?当然,我认为这是最好的实践。它们可以极大地帮助客户支持,您的客户可能还希望将它们合并到自己的测试环境中。只是不要把你的测试部分强加给你的客户。

将单元测试代码发送到生产环境的另一个缺点是,我们将 多么坚强的知识发送到实际使用该代码的环境中。这可能很糟糕,因为在成功反编译单元测试之后,攻击者将更容易理解业务逻辑。这真的很有害。例如,您的代码可以包含特殊的逻辑来处理州和国家之间税收的特殊性。这些知识将很容易被攻击者窃取,他们将有可能把这些知识卖给同一地区的其他玩家... ... 只需在生产环境中复制代码。根据我的经验,几乎没有代码。NET 在生产中被正确混淆。有许多工具可以轻松地以非常易懂的形式反编译代码。