为什么需要单元测试来测试存储库方法?


19

我需要在这个问题上扮演一个恶魔倡导者,因为由于缺乏经验我无法很好地捍卫它。这是交易,我从概念上得到了单元测试和集成测试之间的区别。当专门关注持久性方法和存储库时,单元测试可能会通过Moq之类的框架使用模拟,以断言所搜索的订单已按预期返回。

假设我已经建立了以下单元测试:

[TestMethod]
public void GetOrderByIDTest()
{
   //Uses Moq for dependency for getting order to make sure 
   //ID I set up in 'Arrange' is same one returned to test in 'Assertion'
}

因此,如果我设置好OrderIdExpected = 5并且我的模拟对象返回5了ID,那么我的测试就会通过。我知道了。我对代码进行单元测试,以确保我的代码瓶坯返回期望的对象和ID,而不返回其他内容。

我将得到的参数是这样的:

“为什么不跳过单元测试并进行集成测试呢?这是一起测试数据库存储过程代码的重要性。当最终我想知道数据库是否调用时,进行单元测试和集成测试似乎是多余的工作。我知道测试需要花费更长的时间,但是无论如何都必须运行和测试它们,所以对我来说,两者都显得毫无意义。只需对重要问题进行测试。”

我可以用诸如以下的教科书定义来捍卫它:“嗯,这是一个集成测试,我们需要将代码作为单元测试和yada,yada,yada分别进行测试。”这种情况是一种纯粹的实践解释与现实的对比正在消失。有时我会碰到这种情况,如果我无法捍卫最终依赖于外部依赖关系的单元测试代码背后的原因,那我就没有理由了。

非常感谢您对这个问题的任何帮助,谢谢!


2
我说直接将其发送给用户测试...无论如何他们都会改变要求...
Nathan hayfield 2013年

嘲讽+1可以使事情不时发光
atconway,2013年

2
一个简单的答案是,每种方法都可能有x个边缘情况(假设您要测试正ID,0 ID和负ID)。如果要测试使用此方法的某些功能,该方法本身具有3个边缘情况,则需要编写9个测试用例来测试边缘情况的每种组合。通过隔离它们,您只需要编写6。此外,测试还为您提供了有关某些原因的更详细概念。也许您的存储库在失败时返回null,并且将null异常抛出了数百行代码。
罗布(Rob)2015年

我认为您对“单元”测试的定义过于严格。存储库类的“工作单元”是什么?
卡雷布

并确保您正在考虑:如果您的单元测试取消了您要测试的所有内容,那么您真正在测试什么?
卡雷布

Answers:


20

单元测试和集成测试具有不同的目的。

单元测试验证了代码功能 ...调用该方法后,您可以从该方法中获得期望的结果。集成测试测试代码作为系统组合在一起时的行为 您既不会期望单元测试来评估系统行为,也不会期望集成测试来验证特定方法的特定输出。

如果单元测试正确完成,则比集成测试更容易设置。如果仅依靠集成测试,则测试将:

  1. 总体来说,更难写
  2. 由于所有必需的依赖关系,因此变得更加脆弱,并且
  3. 提供较少的代码覆盖率。

底线:使用集成测试来验证对象之间的连接是否正常工作,但是首先要依靠单元测试来验证功能要求。


综上所述,对于某些存储库方法,您可能不需要进行单元测试。单元测试不应采用简单的方法进行;如果您要做的只是将对对象的请求传递给ORM生成的代码并返回结果,那么在大多数情况下,您无需进行单元测试;集成测试就足够了。


是的,谢谢您的快速回复,我非常感谢。我对回应的唯一批评是它属于我上一段的关注。我的反对者仍然只会听到抽象的解释和定义,而没有深入的推理。例如,您能否提供适用于我的用例来测试与我的代码相关的存储过程/数据库的推理,以及为什么单元测试对于该特定用例仍然有价值?
atconway 2013年

1
如果SP仅从数据库返回结果,则集成测试可能就足够了。如果其中包含非平凡的逻辑,则指示单元测试。另请参见stackoverflow.com/questions/1126614/…msdn.microsoft.com/en-us/library/aa833169(v = vs.100.aspx(特定于SQL Server)。
罗伯特·哈维

12

我站在实用主义者的一边。不要两次测试您的代码。

我们只为存储库编写集成测试。这些测试仅取决于针对内存数据库运行的简单测试设置。我认为他们提供了单元测试所做的一切,以及更多。

  1. 在进行TDD时,它们可以代替单元测试。在您可以从真实代码开始之前,还有更多的测试代码样板可以编写,但是一旦一切就绪,它就可以很好地与红色/绿色/重构方法一起使用。
  2. 他们测试存储库的真实代码 -SQL字符串或ORM命令中包含的代码。验证查询是否正确比验证您实际将某些字符串发送到StatementExecutor更为重要。
  3. 它们非常适合作为回归测试。如果失败,则总是由于实际问题,例如尚未考虑的架构更改。
  4. 当更改数据库架构时,它们是必不可少的。您可以确信,只要测试通过,就不会以破坏应用程序的方式更改架构。(在这种情况下,单元测试是无用的,因为当存储过程不再存在时,单元测试仍将通过。)

确实很务实。我经历了以下情况:由于ORM稍有不同,内存数据库实际上在行为上与实际数据库有所不同(在性能方面)。在集成测试中要注意这一点。
Marcel

1
@Marcel,我遇到了。我有时通过对真实数据库运行所有测试来解决此问题。
2013年

4

单元测试提供了集成测试(通过设计)无法实现的模块化程度。重构或重组系统时(这发生),单元测试通常可以重用,而集成测试则通常必须重新编写。试图完成应该在单元测试中完成的工作的集成测试通常做得太多,这使得它们难以维护。

此外,包括单元测试还有以下好处:

  1. 单元测试使您可以快速分解失败的集成测试(可能是由于回归错误所致)并找出原因。而且,与图表或其他文档相比,这将使问题更快地传达给整个团队。
  2. 单元测试可以与代码一起作为示例和文档(实际上是一种文档)。与其他文档不同,您会在过期时立即知道。
  3. 尝试分解较大的性能问题时,单元测试可以用作基准性能指标,而集成测试往往需要大量的工具才能发现问题。

同时使用单元测试和集成测试时,可以拆分您的工作(并强制执行DRY方法)。只需依靠单元测试来实现较小的功能单元,而不重复集成测试中单元测试中已经存在的任何逻辑。这通常会减少工作量(并因此减少返工)。


4

测试在中断时非常有用:主动或重新主动。

单元测试是主动的,并且可以是连续的功能验证,而集成测试对分段/伪造的数据是被动的。

如果所考虑的系统比计算逻辑更接近/依赖于数据,那么集成测试应该变得更加重要。

例如,如果我们正在构建ETL系统,则最相关的测试将围绕数据(分段,伪造或实时)进行。同一系统将具有一些单元测试,但仅围绕验证,过滤等。

存储库模式不应具有计算或业务逻辑。它非常接近数据库。但是存储库也接近使用它的业务逻辑。因此,这是达成平衡的问题。

单元测试可以测试存储库的行为方式。集成测试可以测试调用存储库时真正发生了什么。

现在,“真正发生的事情”似乎非常诱人。但这对于连续不断地运行可能会非常昂贵。脆性测试的经典定义在这里适用。

因此,大多数时候,我只是觉得足以在使用存储库的对象上编写单元测试。这样,我们测试是否调用了正确的存储库方法,并返回了正确的模拟结果。


我喜欢这样:“现在,“真正发生的事情”似乎很诱人。但这对于连续不断地运行可能会非常昂贵。”
atconway

2

有3个需要测试的独立事物:存储过程,调用存储过程的代码(即您的存储库类)和使用者。存储库的工作是生成查询并将返回的数据集转换为域对象。有足够的代码来支持单元测试,这与查询的实际执行和数据集的创建分开。

因此(这是一个非常非常简化的示例):

interface IOrderRepository
{
    Order GetOrderByID(Guid id);
}

class OrderRepository : IOrderRepository
{
    private readonly ISqlExecutor sqlExecutor;
    public OrderRepository(ISqlExecutor sqlExecutor)
    {
        this.sqlExecutor = sqlExecutor;
    }

    public Order GetOrderByID(Guid id)
    {
        var sql = "SELECT blah, blah FROM Order WHERE OrderId = @p0";
        var dataset = this.sqlExecutor.Execute(sql, p0 = id);
        var result = this.orderFromDataset(dataset);
        return result;
    }
}

然后在测试时OrderRepository,传递一个模拟ISqlExecutor对象,并检查被测对象是否传递了正确的SQL(这是它的工作),并在Order给定一些结果数据集的情况下返回了一个适当的对象(也被模拟)。测试具体SqlExecutor类的唯一方法可能是使用集成测试,这很公平,但这是一个精简的包装类,并且很少更改,因此意义重大。

您仍然还必须对存储过程进行单元测试。


0

我可以简化为一个简单的思路:支持单元测试,因为它有助于定位错误。并始终执行此操作,因为不一致会引起混乱。

进一步的原因来自指导OO开发的相同规则,因为它们也适用于测试。例如,单一责任原则。

如果某些单元测试似乎不值得进行,那么这可能表明该主题实际上不值得拥有。或者,它的功能比对应的功能更抽象,并且针对它的测试(以及其代码)可以抽象到更高的层次。

与任何事物一样,也有例外。而且编程仍然是一种艺术形式,因此许多问题之间的差异足以确保评估每种方法的最佳方法。

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.