在存储库保存最重要的代码时编写单元测试

本文关键字:单元测试 代码 最重要的 存储 保存 | 更新日期: 2023-09-27 17:57:13

我有一个EAV系统,它将实体存储在SQL数据库中,提取它们并将它们存储在缓存中。 应用程序是使用存储库模式编写的,因为在将来的某个时候,我们可能会切换到使用 NOSQL 数据库来提供部分或全部数据。 我使用 Ninject 在运行时获取正确的存储库。

该系统的很大一部分功能是高效及时地存储、检索和查询数据。 没有大量的功能不属于数据访问或用户界面领域。

我已经阅读了单元测试 - 我理解理论,但由于以下几个原因尚未将其付诸实践:

  • 实体由字段集、字段、值组成,每个字段集都有许多属性。 为了进行测试,在代码中创建任意数量的这些内容都需要付出很多努力。
  • 我的代码中一些最关键的部分在存储库中。 例如,所有数据访问都通过一个高度优化的方法,该方法从数据库或缓存中获取实体。
  • 使用测试数据库感觉就像我打破了单元测试的关键原则之一 - 没有外部依赖关系。

除此之外,存储库的构建方式感觉就像它与数据存储在SQL中的方式有关。 实体在一个表中,字段在另一个表中,值在另一个表中,等等。 所以我为每个都有一个存储库。 我的理解是,在文档存储数据库中,实体、其字段和值都将作为单个对象存在,从而消除了对多个存储库的需求。 我考虑过使我的数据访问更加精细,以便将代码部分移动到存储库之外,但这会迫使我以旨在从SQL检索数据的方式编写存储库接口,从而使问题更加复杂。

问题:基于上述内容,我是否应该接受我不能为大部分代码编写单元测试而只测试我能测试的东西?

在存储库保存最重要的代码时编写单元测试

我应该接受我不能为大部分代码编写单元测试吗?

不,你不应该接受这一点。事实上,情况并非如此 - 只要付出足够的努力,你几乎可以对任何东西进行单元测试。

你的

问题归结为:你的代码依赖于数据库,但你不能使用它,因为它是一个外部依赖关系。可以使用模拟对象来解决此问题,模拟对象是在单元测试代码中构造的特殊对象,它们表示为数据库接口的实现,并向程序提供完成特定单元测试所需的数据。当程序向这些对象发送请求时,单元测试代码可以验证请求是否正确。当程序需要特定响应时,单元测试会根据单元测试方案的要求为其提供响应。

模拟可能并非易事,尤其是在请求和响应很复杂的情况下。有几个库可以帮助你在 .NET 中解决这个问题,使模拟对象编码的任务几乎独立于真实对象的结构。然而,真正的复杂性通常在于你正在模拟的系统的行为 - 在你的例子中,这就是数据库。编写这种复杂性的努力完全由您承担,并且确实消耗了您相当一部分编码时间。

看来,当你说"单元测试"时,你实际上指的是"集成测试"。因为在单元测试世界中没有数据库。如果您希望获取或将一些数据插入外部资源,您只需伪造它(使用模拟,存根,假货,间谍等)

应该接受我不能为我的大部分编写单元测试吗 代码并测试我能测试的东西?

如果不看到您的代码,很难分辨,但听起来您可以轻松地对其进行单元测试。这基于您对接口和存储库模式的使用。只要单元测试独立于其他测试,测试单个功能,小,简单,不依赖于任何外部资源 - 你很好。

不要将其与集成和其他类型的测试混淆。这些可能涉及真实数据,并且编写起来可能有点棘手。

如果您使用正确的存储库模式测试很容易,因为

  1. 业务层只知道存储库接口,该接口仅处理该层已知的对象,并且不公开与实际 Db 相关的任何内容(如 EF)。这是您使用假货实现存储库接口的地方。

  2. 测试数据库访问意味着您正在测试 Repo 实现,您可以获取测试对象。存储库与数据库耦合是很自然的。

对 repo 的测试应该是这样的

public void add_get_object()
 {
     var entity=CreateSomeTestEntity();
     _repo.Save(entity);
     var loaded=_repo.Get(entity.Id);
     //assert those 2 entities are identical, but NOT using reference
     // either make it implement IEquatable<Entity> or test each property manually
  }

这些存储库测试可以与任何存储库实现重用:在内存中,EF,raven db等,因为实现无关紧要,重要的是存储库执行它需要做的事情(保存/加载业务对象)。