具有存储库模式的TDD


10

在我的新项目中,我决定尝试使用TDD。一开始我就遇到了问题。我想在应用程序中做的第一件事就是赋予从数据源读取数据的能力。为此,我想使用存储库模式。现在:

  • 如果测试是为了真正实现存储库接口,那么我将测试具有数据库访问权限的类,并且我知道应该避免这种情况。
  • 如果测试不是针对存储库模式的真正实现,那么我将进行良好的测试……只是模拟。在这些单元测试中,将不会测试任何生产代码。

我从两天开始考虑这个问题,但仍然无法提出任何合理的解决方案。我该做什么?

Answers:


11

存储库的作用是从您的域转换到DAL框架(例如NHibernate或Doctrine)或SQL执行类。这意味着您的存储库将在所述框架上调用方法以执行其职责:您的存储库构造了获取数据所需的查询。如果您不使用ORM框架(希望您...),则存储库将是构建原始SQL语句的地方。

这些方法中最基本的就是保存:在大多数情况下,这只会将对象从存储库传递到工作单元(或会话)上。

public void Save(Car car)
{
    session.Save(car);
}

但是,让我们看另一个示例,例如,通过ID提取汽车。它可能看起来像

public function GetCarWithId(String id)
{
    return Session.QueryOver<Car>()
                    .Where(x => x.Id == id)
                    .SingleOrDefault();
}

仍然不太复杂,但是您可以想象在多种情况下(让我了解“大众”组中所有品牌在2010年之后生产的所有汽车),这很棘手。因此,您需要以真正的TDD方式进行测试。有几种方法可以做到这一点。

选项1:模拟对ORM框架的调用

当然,您可以模拟会话对象,并只需断言进行了正确的调用。在测试存储库时,它并不是真正的测试驱动器,因为您只是在测试存储库在内部看起来是您希望的样子。测试基本上说“代码应该看起来像这样”。仍然,这是一种有效的方法,但感觉这种测试的价值很小。

选项2:从测试中(重新)构建数据库

一些DAL框架使您能够根据您创建的用于将域映射到表的映射文件来构建数据库的完整结构。对于这些框架,测试存储库的方法通常是在测试的第一步中使用内存数据库创建数据库,然后使用DAL框架将对象添加到内存数据库中。之后,您可以使用内存数据库中的存储库来测试这些方法是否有效。这些测试速度较慢,但​​非常有效,可以推动您的测试。它确实需要您DAL框架的一些合作。

选项3:在实际的数据库上进行测试

另一种方法是在实际数据库上进行测试并隔离单元测试。您可以通过几种方式做到这一点:用事务包围测试,手动清理(不建议很难维护),在每个步骤之后完全重建数据库...取决于您正在构建的应用程序,这可能或可能不可行。在我的应用程序中,我可以从源代码控制中完全构建本地开发数据库,​​并且我在存储库上的单元测试使用事务将测试彼此完全隔离(打开事务,插入数据,测试存储库,回滚事务)。每个构建都首先建立本地开发数据库,​​然后对该本地开发数据库上的存储库执行事务隔离的单元测试。它'

不要测试DAL

如果您使用的是DAL框架(例如NHibernate),则无需测试该框架。您可以通过保存,检索然后比较一个域对象以确保一切正常(请确保禁用任何类型的缓存)来测试映射文件,以确保一切正常(但不要像您应该编写的许多其他测试那样要求它)。我倾向于这样做主要是针对有孩子条件的父母收藏。

在测试存储库的退货时,您只需检查域对象上的某些标识属性是否匹配即可。这可以是一个id,但在测试中,检查人类可读的属性通常更有利。在“让我把2010年以后制造的所有汽车...”中,这可以简单地检查是否归还了五辆汽车,并且车牌是“在此插入列表”。额外的好处是,它迫使您考虑进行排序,而您的测试会自动强制进行排序。您会惊讶地发现有多少个应用程序进行了多次排序(从数据库返回排序,在创建视图对象之前进行排序,然后对视图对象进行排序,以防万一,所有这些属性均在同一属性上),或者隐式地假设存储库进行了排序并意外删除了一直在破坏UI。

“单元测试”只是一个名字

在我看来,单元测试通常不应该访问数据库。您构建一个应用程序,以便需要源代码中的数据的每个代码段都通过存储库来执行此操作,然后将该存储库作为依赖项注入。这样就可以轻松进行嘲笑,并获得所需的所有TDD优点。但是最后,您要确保存储库执行其职责,如果最简单的方法是访问数据库,那么就这样吧。我已经放过了“单元测试不应该触及数据库”的观念,并且知道这样做的确有确凿的理由。但前提是您可以自动重复执行此操作。我们称这种测试为“单元测试”或“集成测试”的天气是没有意义的。


3
单元测试和集成测试具有不同的目的。这些测试的名称不仅是装饰性的。它们也是描述性的。
罗伯特·哈维

9
  1. 不要测试琐碎或明显的存储库方法。

    如果这些方法是普通的CRUD操作,那么您真正要测试的只是参数是否正确映射。如果您有集成测试,则无论如何都会立即发现此类错误。

    这是适用于琐碎属性的相同原理,例如:

    public property SomeProperty
    {
        get { return _someProperty; }
        set { _someProperty = value; }
    }
    

    您无需测试,因为没有要测试的东西。该属性中没有验证或其他逻辑需要验证。

  2. 如果您仍然想测试那些方法...

    嘲弄是做到这一点的方法。请记住,这些是单元测试。 您不使用单元测试来测试数据库。这就是集成测试的目的。

更多信息
完整堆栈,第3部分:使用TDD构建存储库(大约在16分钟后开始观看)。


3
当然,我明白这一点。不过,如果是TDD方法,如果我首先没有对此代码进行测试,那么我不应该编写任何代码,对吗?
Thaven 2014年

1
@Thaven-YouTube上有一系列名为“ tdd死了吗?”的视频。看他们。它们解决了许多有趣的问题,其中之一就是在您的应用程序的每个级别应用TDD不一定是最好的主意。结论之一就是“没有测试失败就没有代码”是一个极端的立场。
Jules 2014年

2
@Jules:tl; dw是多少?
罗伯特·哈维

1
@RobertHarvey很难总结,但是最重要的一点是将TDD视为必须始终遵守的宗教是一个错误。选择使用它是折衷方案的一部分,您需要考虑(1)在某些问题上没有它,您可能可以更快地工作;(2)它可能会促使您朝着比您需要的方向更复杂的解决方案前进,特别是如果您发现自己使用了大量的模拟游戏。
Jules

1
点1的+1。测试可能是错误的,只是它们通常是微不足道的。测试正确性比测试更明显的功能毫无意义。这并不像获得100%的代码覆盖率使您可以接近测试程序的每个可能执行一样,因此您最好对测试工作花在哪里。
Doval 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.