如何避免重复测试代码

时间:2009-06-10 14:39:10

标签: unit-testing testing integration-testing

我已经编写了我的单元测试,并且在需要外部资源的地方,使用假货进行处理。

到目前为止一切都很好。现在我面临其他测试阶段,主要是集成,我想重复单元测试方法来对抗真实的外部资源,例如数据库。

那么,为单元Vs集成测试构建测试项目有哪些建议?我理解有些人更喜欢单元和集成的单独组件?

如何在两个程序集之间共享通用测试代码?我应该创建一个包含所有抽象测试类的thrid程序集,并让单元和集成继承吗?我正在寻找最大的可重用性......

我听到很多关于依赖注入(StructureMap)的噪音,如何在给定的Unit + Integration测试设置中使用这样的工具?

任何人都可以分享一些智慧吗?感谢

7 个答案:

答案 0 :(得分:1)

我认为你不应该将两者分开。一个好的解决方案是将Microsoft.TeamFoundation.PowerTools.Tasks.CategoryAttribute放在测试之上,以识别常规测试和集成测试。运行测试时(即使使用MSBuild),您可以决定只运行您感兴趣的测试。

或者,您可以将它们放在单独的命名空间中。

答案 1 :(得分:1)

对于将在设置和执行中执行的代码拆解阶段,基类方法会很好。对于集成测试,您可以将单元测试的功能提取到良好参数化的非测试方法(最好放在另一个命名空间中),并从单元测试和集成测试中调用这些“常用”方法。将单元测试,集成测试和常用方法放入单独的命名空间就足够了,不需要额外的程序集。

答案 2 :(得分:0)

一种方法是创建一个单独的文件,其中包含将在多个测试上下文中使用的辅助方法,然后在单元测试和功能测试中包含该文件。对于变化的部分,您可以使用依赖注入 - 例如,通过传入不同的工厂。在单元测试中,工厂可以构建一个假对象,在功能测试中,它可以将一个真实对象插入到测试数据库中。

答案 3 :(得分:0)

是否将测试拆分为两个项目或将它们保存在一个项目中可能取决于您拥有的类/测试数量。单个项目中的类太多会使得难以深入挖掘。如果你将它们拆分出来,可以将helper / common方法抛出到第三个程序集中,或者你可以在单元测试程序集中将它们公开,并让集成程序集引用它。让事情变得像你一样复杂。

答案 4 :(得分:0)

在我们的项目中,我们将集成和单元测试结合在一起,但是在单独的文件夹中。我们的项目布局是这样的,我们为主要部分(域,服务等)提供了单独的程序集。每个组件都有一个匹配的测试组件。允许测试组件参考其他测试组件。

这意味着Services.Test可以引用对我们有意义的Domain.Test,因为Services在实际代码中引用了Domain。

就可重复使用的部分而言

  • 构建器 - 这些构建器提供了一个流畅的界面,用于在域中创建最重要/最复杂的对象。它们位于我们域的主要测试文件夹中。我们的域测试程序集由所有其他测试程序集引用。

  • 母亲 - 这些将数据插入数据库。它们为插入的行返回一个Id,如果需要,可以用它来加载对象。它们位于我们服务的主要测试文件夹中。

  • 助手 - 这些人在我们的测试过程中做了很多小事。例如,我们更喜欢允许通过IEnuermable访问集合,因此我们有一个CollectionHelper.AssertCountIsEqualTo< _T>(int count,IEnumerable< _T>集合,字符串消息),它将IEnumerable包装在List中并断言计数。我们的助手都参加了一项通用测试,其他所有测试都参考了。

对于IoC容器,如果您可以在项目中使用它,它们不仅可以帮助您进行测试(通过自动模拟),还可以用于一般开发。无意中注意到了包含所有内容的内容,尽管它可能只是为了测试而进行的。

答案 5 :(得分:0)

经过一些实验后,您可以重新使用测试方法:

    public abstract class TestBase
    {
        [TestMethod]
        public void BaseTestMethod()
        {
            Assert.IsTrue(true);
        }
    }


    [TestClass]
    public class UnitTest : TestBase
    {
    }

    [TestClass]
    public class IntegrationTest : TestBase
    {
    }

单元和集成测试类将获取基类测试方法并将它们作为两个单独的测试运行。

您应该能够在基类上创建一个参数化构造函数来注入您的模拟或资源。

我认为这种方法可以在同一个程序集中使用。所以看起来单组装方法现在必须要做。

感谢您提示ppl!

答案 6 :(得分:0)

如果你的许多单元测试和相应的集成测试之间的唯一区别是后者使用" real"资源而不是假货(模拟),一种方法如下:

  1. 从外面为您的测试类提供标记 is_unit_test
  2. 在课堂设置中,根据标志制作假冒或真实资源。例如,如果您需要使用真实(DBreal类的实例)或伪(DBfake类的实例)的DB API,您的初始化可能看起来像if is_unit_test then this.dbapi = new DBfake else this.dbapi = new DBreal。 (DBrealDBfake需要符合相同的界面,我们称之为DBapi。)
  3. 从测试方法的角度来看,步骤2相当于(手动)依赖注入:该方法不知道哪个类实际实现了它的依赖(DB API)。相反,依赖从外部注入到方法中。
  4. 如果您的测试用例需要DB API,则会使用this.dbapi
  5. 现在您执行同一个测试类,并为单元测试设置了标志,并且没有为集成测试设置标志。 (如何使标志可用取决于您的单元测试框架。)
  6. 显然,如果在测试类中需要多个资源,则可以使用相同的方法。
  7. 有些人会在第2步中找到明确的if。为了使它更加优雅",您可以使用控制反转(IoC)容器(在Java中,例如Spring或PicoContainer)来半自动化依赖注入。初始化将看起来像this.dbapi = myContainer.create(DBapi)
  8. 简单的情况下,IoC容器只会使事情变得复杂,因为配置容器并非易事,需要学习,可能会出现新的错误,并涉及其他文件。
  9. 更复杂的情况中,容器使事情变得更容易,因为如果资源的创建还需要其他资源,容器也将负责初始化,复杂性确实会下降。但除非你真的到达那里,否则我建议KISS
  10. 除非您有单独组装的重要原因,否则他们违反了KISS。我建议先等待这个原因。
  11. (请注意,有些人可能会告诉您,依赖注入仅在类级别完成。 我认为这是毫无根据的教条主义。注入只是意味着调用者不知道它正在调用的确切类,无论它如何获得对象。在类级别应用时,它通常会变得更有用,但根据您的测试框架,在上述情况下这可能会使事情变得过于复杂。请注意,一些测试框架有自己的注入功能。)