【问题标题】:Unit/integration testing nHibenrate query单元/集成测试 nHibenrate 查询
【发布时间】:2015-12-14 11:14:28
【问题描述】:

场景:我需要编写一个复杂的 nHibernate 查询,它会返回预计的 DTO,但我想使用 TDD 方法。该方法如下所示:

public PrintDTO GetUsersForPrinting(int userId)
{
     Session.QueryOver<User>().//some joins, conditions etc.
     //returns projected dto
}

问题:

  1. 由于最常见的方法是使用内存数据库进行此类操作。我应该编写集成测试吗?
  2. 如果我在内存数据库中使用,我可以编写单元测试吗?
  3. 一次测试就够了吗?
  4. 由于我的集成测试可能会检查投影,我应该如何命名它? “GetUserForPrinting_return_correct_DTO”似乎过于抽象和愚蠢。

我问是因为:

  • 有很多关于 TDD 和集成测试的抽象信息,但在具体实施时,很难应用这些信息。
  • TDD 建议集成测试应该由单元测试组成:

【问题讨论】:

    标签: unit-testing nhibernate tdd integration-testing


    【解决方案1】:

    这并不是一个学习 TDD 的好问题。我假设您还不知道复杂的查询是什么样的,并且您想使用测试驱动技术来解决它。太棒了:)

    但让我们看看我能不能回答你的问题。

    1. 任何包含真实数据库的测试,无论是内存中的还是磁盘上的,都不是单元测试。单元测试将使用模拟数据库。

    2. 也许 - 如果您的查询足够复杂,那么不会。

    3. testGetUsersForPrinting 或 getUsersForPrintingTest 或类似

    我很可能会在 SQL 解释器中驱动查询,而不是在代码中。目的是根据我在此过程中学到的知识,针对内存数据库生成一系列集成测试。 从您能想到的最小 DTO 开始,然后从那里开始构建。

    最后将查询转换为 nhibernate 调用,然后使集成测试通过。

    测试驱动,但不是真正的单元测试驱动。

    如果您愿意接受最大程度的 TDD 纪律并处理比平时更慢和更烦人的工作,您可以在开发每个集成测试并编写代码以使其通过时自动化。这将意味着您在 3 个抽象级别/编辑器/环境(直接 SQL 查询、集成测试、c# 代码)之间频繁切换 - 我通过设置技术来强制自己每次都遵循正确的步骤来处理这个问题。

    最后一点是为什么这不是学习 TDD 的好问题。您将需要很多您可能还没有强迫自己获得的纪律!

    祝你好运。


    好的,一些具体的例子。我会将您的代码示例修改为如下所示

    public PrintDTO GetUsersForPrinting(int userId, ISession session)
    {
         var data = session.QueryOver<User>().//some joins, conditions etc.
    
         return data; // or whatever
    }
    

    在你的单元测试中你会写

    public testDTO()
    {
        //Arrange
        StubSession session = .... setup a stub session, which returns hardcoded values
    
        // Act
        PrintDTO users = GetUsersForPrinting(111, session);
    
        // Assert
        Assert.That(users.size(), Is.EqualTo(1));    
        Assert.That(users.get(0).userId, Is.EqualTo(111));
    
    }
    

    在您的集成测试中,您将使用一个真实的数据库,并且您的会话对象将实际连接到它,并且将针对该数据库解析查询

    Arrange-Act-Assert 是组织单元测试的标准方法。 通常,您希望在单元测试中尽可能少地使用断言。你将有多个单元测试。 在编写单元测试时,首先编写 Assert,然后填写其余部分以使其编译/获得所需的结果。首先让测试失败,因为这样你就知道当它通过时你确实交付了一些东西。

    在这个实现存根 ISession 的示例中,您将从 ISession 派生一个本地 StubSession 类(仅对测试套件可见),只需填写绝对最小值以使其编译,并返回最小数据以进行测试通过。


    要建立您的整个 DTO - 假设您知道自己想要在 DTO 中做什么 - 正如您在 cmets 中所说的那样,逐步进行。构建 DTO 的每个部分 一次一块,为每一块添加一个单元测试。

    跟踪这是 TDD 规则的另一部分。

    为自己设置一个 TODO 列表 - 只是一个简单的文本文件,或者在测试套件开始时可能有一个冗长的 cmets。列出您要测试的所有内容,例如0 个结果,1 个结果,2 个结果,20 个结果。用户 ID,您需要拥有的任何其他信息。 如果您正在跨表执行复杂查询,或者为每个连接添加待办事项,where 子句的每个部分等。 如果您正在使用这些项目,请添加用于订购和分页等的项目。

    首先选择最简单的东西。一次只做一件小事(在一个红-绿-重构周期中)。在处理清单时,您可能希望将项目分解成更小的部分,或者您可能会想到需要做的其他事情。将它们添加到 TODO 列表中,而不是直接处理它们。

    在这种特殊情况下,我会在每个 red-green-refactor 循环之后交换到 SQL 环境和/或 sqlite 集成测试,以找出如何使下一部分工作。我想这是介于红色和绿色之间的一种步骤——选择接下来要测试的内容,编写测试(显然失败了),在 SQL 中摆弄直到你知道如何让它通过,编写 nHibernate 调用来进行测试绿色,然后重构。

    请注意,您列出的某些内容可能会用完而不必要,或者花费太长时间等。最好还是把它们写下来,这样您就可以知道哪些事情没有做以及您正在做的事情。专注于你的目标。

    我还倾向于制定一个“气味”和/或重构列表,我可以看到我想做但还没有完全准备好这个周期。请记住尽量减少重复/重构您的测试以及您的 SUT(被测系统)。

    这是一种做而不是看到的事情。您最终使用的单元测试列表以及它们执行的代码并不是对旅程的很好描述。 Kent Beck 的原始 TDD 书很薄,会给你一些很好的总体指导,但并不是真正关于构造查询。

    这些有帮助吗?

    【讨论】:

    • 您好,感谢您的回答。你能提供一个关于这个方法的集成测试的例子吗?我只是想看看一些实现,而不仅仅是理论。
    • 我应该说我了解 TDD 和单元测试的基础知识。问题是我不知道如何通过测试来驱动查询。您提供的示例测试将驱动两件事:1)我的查询将采用 1 条记录 2)我的查询将包含按 ID 过滤用户的 where 子句。目前我正在 sqlite 中创建假数据并检查我的方法是否返回预期的 DTO。但这只是一个测试,它驱动整个查询没有增量步骤。
    【解决方案2】:

    由于最常见的方法是使用内存数据库进行此类操作。我应该编写集成测试吗?

    使用内存数据库仍然是一个集成测试(因为它实际上测试您的查询是否生成正确的 SQL 并针对数据库执行它,see)。

    如果我在内存数据库中使用,我可以编写单元测试吗?

    不,这将是一个集成测试

    一次测试就够了吗?

    可能不是,您应该检查查询的每个条件,例如每个 where 子句进行一个测试,一个用于分页,一个用于排序(如果适用)。

    由于我的集成测试可能会检查投影,我应该如何 命名它? “GetUserForPrinting_return_correct_DTO”似乎太抽象了 又傻。

    GivenUserForPrinting_WhenGetUserForPrinting_ThenMapToDTO 会是更好的命名方式

    【讨论】:

    • 您好,感谢您的回答。你能提供一个关于这个方法的集成测试的例子吗?我只是想看看一些实现,而不仅仅是理论。
    猜你喜欢
    • 2013-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-05
    • 1970-01-01
    • 2020-10-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多