【问题标题】:Does a Middle Ground Exist? (Unit Testing vs. Integration Testing)是否存在中间立场? (单元测试与集成测试)
【发布时间】:2011-06-05 20:03:51
【问题描述】:

考虑存储库模式(或类似模式)的实现。我会尽量保持示例/插图简洁:

interface IRepository<T>
{
    void Add(T entity);
}

public class Repository<T> : IRepository<T>
{
    public void Add(T entity)
    {
        // Some logic to add the entity to the repository here.
    }
}

在这个特定的实现中,Repository 由接口 IRepository 定义,以具有一个将实体添加到存储库的方法,从而使 Repository 依赖于泛型类型 T(此外,Repository 必须隐式依赖于另一种类型 TDataAccessLayer ,因为抽象是存储库模式的全部要点。但是,这种依赖关系目前还不容易获得)。在这一点上,据我目前的理解,我有两个选择:单元测试和集成测试。

如果假设集成测试有更多的活动部件,我宁愿最初进行单元测试,以便至少验证基线功能。但是,如果不创建某种“实体”属性(泛型类型 T),我无法断言任何逻辑实际上是在 Repository 实现的 Add() 方法中执行的。

是否存在单元测试和集成测试之间的中间地带,它允许(通过反射或其他方式)验证在测试单元中是否已达到特定的执行点?

我对这个特定问题的唯一解释是从存储库中进一步抽象数据访问层,导致 Add() 方法不仅接受实体参数,还接受数据访问参数。但是,在我看来,这可能会破坏存储库模式的目的,因为存储库的使用者现在必须知道数据访问层。

关于请求示例:

(1) 关于单元测试,根据我对当前测试技术的理解,我不确定像存储库这样的东西实际上是否可以进行单元测试。因为存储库是围绕特定数据访问层的抽象(包装器),所以似乎唯一的验证方法是集成测试? (当然,存储库接口可能不绑定到任何特定的 DAL,但任何实现的存储库肯定必须绑定到特定的 DAL 实现,因此需要能够测试 Add() 方法是否实际执行了一些工作)。

(2) 关于集成测试,据我了解该技术,测试将通过实际调用 Add() 方法(应该向存储库添加一条记录)来验证 Add() 方法是否执行工作,并且然后检查数据是否实际添加到存储库(或者可能是特定场景中的数据库)。这可能看起来像:

[TestMethod]
public void Add()
{
    Repository<Int32> repository = new Repository<Int32>();
    Int32 testData = 10;

    repository.Add(testData);

    // Intended to illustrate the point succinctly. Perhaps the repository Get() method would not
    // be called (and a DBCommand unrelated to the repository issued instead). However, assuming the
    // Get() method to have been previously verified, this could work.
    Assert.IsTrue(testData == repository.Get(testData));
}

因此,在这种情况下,假设存储库是某个数据库逻辑层的包装器,那么数据库实际上会在测试期间被命中两次(一次在插入期间,一次在检索期间)。

现在,我认为有用的是一种技术,用于验证在运行时是否采用了特定的执行路径。例如,如果传入的是非空引用,则采用验证执行路径 A,如果传入的是空引用,则采用验证执行路径 B。此外,也许可以验证要执行特定的 LINQ 查询。因此,在测试期间数据库从未真正受到影响(允许在没有任何实际 DAL 的情况下进行原型设计和开发)。

【问题讨论】:

  • 你在这里究竟在哪里划定“单元测试”和“集成测试”之间的界限?假设您想对“添加”方法进行单元测试 - 您能举个例子,单元测试应该是什么样子,集成测试应该如何不同于那个?
  • 你到底想测试什么?具体实现?遵循一条思路:如果您使用的是 TDD,那么如果没有单元测试,您就不会拥有具体的实现。因此,问自己希望实现实现哪些功能就等于问自己希望它通过哪些测试。
  • @John Saunders 在某种程度上,我同意你所说的关于 TDD 的看法。我添加了更多信息,希望能更好地定义问题。我只是不确定目前是否可以通过单元测试来驱动诸如存储库之类的开发。
  • @Brad:我会看看你的方法,但仅供参考,我发现很少有不能通过 TDD 创建的。
  • @John Saunders 这可能只是我缺乏理解。我只是还没有看到一种方法来执行我尝试执行的各种测试而不实际运行集成测试。

标签: c# unit-testing integration-testing


【解决方案1】:

听起来您描述的是对实现细节的测试,而不是模式实现者对模式要求的满足。在被测单元内是否达到“特定执行点”并不重要,重要的是具体实现者是否遵守接口的合同。测试创建T 实体用于测试目的是完全可以接受的,这就是模拟的用途。

【讨论】:

  • 当然,我可以模拟一个实体对象,但我的问题是,我在单元测试中实际断言什么?调用repository.Add(someMockObject) 对我来说并不是很有用,除非我可以确认对someMockObject 采取了一些行动。
  • 存储库必须满足的要求是持久化您添加的对象。测试“仅添加”存储库是徒劳的,因为您正在测试一个无用的存储库。在任意存储库实现上测试Add 方法的唯一方法是要求它返回您添加的对象。
  • 这与@JohnSaunders 所说的基本相同,与其用教条的“测试模式”术语来思考,不如用“证明实现者做了它需要做的事情”来思考。不管是“集成”还是“单元”,重要的是测试不知道实现细节。
【解决方案2】:

如果你想做集成测试,你需要使用真实的数据库。但是,如果您想快速测试一些东西,您可以尝试使用内存数据库。 问题是你可以测试什么,你不能测试什么。只要您的数据库访问代码是特定于数据库的,您就可以使用应该模拟的外部系统(以保持单元测试说话)。但由于您真的想知道您的数据是否最终存储在数据库中,您需要针对真实数据库进行测试。

但是如果你使用一些数据库抽象,例如ORM 映射器,您可以使用 ORM 映射器并测试至少映射是否正常工作。然后,ORM 映射器可以使用内存数据库进行测试,以检查 ORM 映射器是否按预期工作。

如果您不使用 ORM 映射器并且您创建一个额外的数据库抽象层只是为了拥有一个抽象,那么您的代码执行的唯一目的是在您的真实单元测试中发现您想要发现的错误让您更有效率。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-05
    • 1970-01-01
    • 2010-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-29
    • 2013-07-13
    相关资源
    最近更新 更多