【问题标题】:How to write unit tests for database calls如何为数据库调用编写单元测试
【发布时间】:2010-11-16 02:34:21
【问题描述】:

我即将开始一个新项目并且(喘气!)我第一次尝试在我的项目中包含单元测试。

我自己设计一些单元测试时遇到了麻烦。我有一些方法很容易测试(传入两个值并检查预期的输出)。我有代码的其他部分正在做更复杂的事情,比如对数据库运行查询,我不知道如何测试它们。

public DataTable ExecuteQuery(SqlConnection ActiveConnection, string Query, SqlParameterCollection Parameters)
{
    DataTable resultSet = new DataTable();
    SqlCommand queryCommand = new SqlCommand();
    try
    {
        queryCommand.Connection = ActiveConnection;
        queryCommand.CommandText = Query;

        if (Parameters != null)
        {
            foreach (SqlParameter param in Parameters)
            {
                 queryCommand.Parameters.Add(param);
            }
        }

        SqlDataAdapter queryDA = new SqlDataAdapter(queryCommand);
        queryDA.Fill(resultSet);
    }
    catch (Exception ex)
    {
        //TODO: Improve error handling
        Console.WriteLine(ex.Message);
    }

    return resultSet;
}

该方法本质上是从数据库中提取一些数据所需的所有必要的零碎部分,并在 DataTable 对象中返回数据。

第一个问题可能是最复杂的:在这种情况下我应该测试什么?

一旦解决了这个问题,就是要模拟数据库组件还是尝试针对实际数据库进行测试。

【问题讨论】:

    标签: database unit-testing testing integration-testing data-access-layer


    【解决方案1】:

    对于单元测试,我通常模拟或伪造数据库。然后通过依赖注入使用你的模拟或假实现来测试你的方法。您可能还会有一些集成测试来测试数据库中的约束、外键关系等。

    至于您要测试的内容,您要确保该方法正在使用来自参数的连接,查询字符串已分配给该命令,并且您返回的结果集与您提供的结果集相同通过对 Fill 方法的期望。注意 -- 测试返回值的 Get 方法可能比修改参数的 Fill 方法更容易。

    【讨论】:

      【解决方案2】:

      看在上帝的份上,请勿针对已填充的实时数据库进行测试。但你知道的。

      一般来说,您已经知道每个查询将检索哪种类型的数据,无论您是在验证用户身份、查找电话簿/组织结构图条目还是其他什么。您知道您对哪些字段感兴趣,并且您知道它们存在哪些限制(例如,UNIQUENOT NULL 等)。您正在对与数据库交互的代码进行单元测试,而不是数据库本身,因此请考虑如何测试这些功能。如果字段可能是NULL,您应该进行测试以确保您的代码正确处理NULL 值。如果您的某个字段是字符串(CHARVARCHARTEXT 和 c),请测试以确保您正确处理了转义字符。

      假设用户会尝试将任何东西*放入数据库,并相应地生成测试用例。您需要为此使用模拟对象。

      * 包括不受欢迎的、恶意的或无效的输入。

      【讨论】:

      • 实际上——你提出了一个有趣的观点。是否有工具可以帮助为数据库层显式创建单元测试? (换句话说,对过程本身进行单元测试?)
      • 啊——看来我的问题已经被问及回答了,这里:stackoverflow.com/questions/754527/best-way-to-test-sql-queries/…
      • 你必须保持模拟并且不犯任何错误。它假定 mock 比数据库更正确。
      【解决方案3】:

      严格来说,从数据库或文件系统写入/读取的测试不是单元测试。 (虽然它可能是一个集成测试,并且可能使用 NUnit 或 JUnit 编写)。单元测试应该测试单个类的操作,隔离它的依赖关系。因此,当您为接口和业务逻辑层编写单元测试时,您根本不需要数据库。

      好的,但是如何对数据库访问层进行单元测试呢?我喜欢这本书的建议:xUnit Test Patterns(链接指向本书的“使用 DB 进行测试”一章。关键是:

      • 使用往返测试
      • 不要在数据访问测试夹具中编写太多测试,因为它们的运行速度会比“真正的”单元测试慢得多
      • 如果可以避免使用真实数据库进行测试,请在没有数据库的情况下进行测试

      【讨论】:

        【解决方案4】:

        为了正确执行此操作,您应该使用一些依赖注入 (DI),对于 .NET,有几个。我目前正在使用 Unity 框架,但还有其他更简单的。

        以下是本网站关于此主题的一个链接,但还有其他链接: Dependency Injection in .NET with examples?

        这将使您能够更轻松地模拟应用程序的其他部分,只需让模拟类实现接口,这样您就可以控制它的响应方式。但是,这也意味着要设计一个界面。

        既然您询问了最佳实践,这就是 IMO。

        然后,除非你需要,否则不要去数据库,建议是另一个。

        如果您需要测试某些行为,例如与级联删除的外键关系,那么您可能需要为此编写数据库测试,但通常最好不要使用真正的数据库,尤其是因为可能不止一个人运行一次进行单元测试,如果他们要去同一个数据库测试可能会失败,因为预期的数据可能会改变。

        编辑:我的意思是数据库单元测试,因为它旨在仅使用 t-sql 进行一些设置、测试和拆卸。 http://msdn.microsoft.com/en-us/library/aa833233%28VS.80%29.aspx

        【讨论】:

        • 但是在这种情况下,您希望测试在遇到意外数据时失败,以便您可以重写组件以正确处理条件。
        • 我认为最好使用我添加了参考的数据库测试,因为它有助于限制您准备测试所需做的事情,因此您不必测试设置例如,连接。
        【解决方案5】:

        你在测试什么?

        我想到了三种可能性:

        A.您正在测试 DAO(数据访问对象)类,确保它正确封送传递给数据库的值/参数,并正确封送/转换/打包从数据库中获得的结果。

        在这种情况下,您根本不需要连接到数据库;您只需要一个单元测试,将数据库(或中间层,例如 JDBC、(N)Hibernate、iBatis)替换为 mock。

        B.您正在测试(生成的)SQL 的语法正确性。

        在这种情况下,由于 SQL 方言不同,您希望针对您的 RDBMS 的正确版本运行(可能生成的)SQL,而不是尝试模拟您的 RDBMS 的所有怪癖(并且因此任何 RDBMS 升级都会更改功能被您的测试捕获)。

        C.您正在测试 SQL 的语义正确性,即对于给定的基线数据集,您的操作(访问/选择和突变/插入和更新)会产生预期的新数据集。

        为此,您想使用 dbunit 之类的东西(它允许您设置基线并将结果集与预期结果集进行比较),或者可能使用我在此处概述的技术完全在数据库中进行测试:Best way to test SQL queries.

        【讨论】:

          【解决方案6】:

          这就是为什么(恕我直言)单元测试有时会给开发人员带来错误的安全感。根据我对与数据库通信的应用程序的经验,错误通常是数据处于意外状态(异常或缺失值等)的结果。如果您经常在单元测试中模拟数据访问,您会认为您的代码运行良好,但实际上仍然容易受到此类错误的影响。

          我认为你最好的方法是准备一个测试数据库,里面装满大量糟糕的数据,然后针对它运行你的数据库组件测试。始终记住,在搞砸数据方面,您的用户会比您好得多。

          【讨论】:

            【解决方案7】:

            您可以对所有内容进行单元测试,除了:queryDA.Fill(resultSet);

            一旦您执行queryDA.Fill(resultSet),您要么必须模拟/伪造数据库,要么正在进行集成测试。

            我认为,集成测试并不坏,只是它会捕获不同类型的错误,具有不同的误报和误报几率,不太可能经常进行,因为太慢了。

            如果我对这段代码进行单元测试,我会验证参数是否正确构建,命令构建器是否创建了正确数量的参数?它们都有价值吗?空值、空字符串和 DbNull 是否得到正确处理?

            实际上填充数据集是在测试您的数据库,这是 DAL 范围之外的一个不稳定组件。

            【讨论】:

              【解决方案8】:

              单元测试的全部意义在于单独测试一个单元 (duh)。数据库调用的全部意义在于与另一个单元(数据库)集成。 Ergo:对数据库调用进行单元测试没有意义。

              但是,您应该集成测试数据库调用(如果需要,您可以使用用于单元测试的相同工具)。

              【讨论】:

              【解决方案9】:

              在基于 JDBC 的项目上,可以模拟 JDBC 连接,这样就可以在没有实时 RDBMS 的情况下执行测试,每个测试用例都是隔离的(没有数据冲突)。

              它允许验证,持久性代码传递正确的查询/参数(例如https://github.com/playframework/playframework/blob/master/framework/src/anorm/src/test/scala/anorm/ParameterSpec.scala)并按预期处理JDBC结果(解析/映射)(“获取所有必要的点点滴滴从数据库中提取一些数据,并返回 DataTable 对象中的数据")。

              类似 jOOQ 的框架或我的框架 Acolyte 可用于:https://github.com/cchantep/acolyte

              【讨论】:

                【解决方案10】:

                第一个问题可能是最复杂的:在这种情况下我应该测试什么?

                • 由于您的代码基本上是一个 DAO/存储库,没有任何 您需要集成测试的业务逻辑,而不是单元测试。

                • 单元测试应该测试没有外部依赖的类(比如 DB 或调用其他远程服务)。

                • 您应该始终尝试分离业务逻辑(您的域 模型)来自基础设施代码的代码,那么它将很容易使用单元 测试。

                • 小心使用 Mocks,它可能是糟糕设计的信号。它的意思是 您的业​​务逻辑与基础架构混合在一起。

                • 检查以下模式:“域模型”、“六边形架构”、“功能核心、命令式外壳”

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2019-06-28
                  • 1970-01-01
                  • 2011-09-30
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-07-31
                  • 1970-01-01
                  相关资源
                  最近更新 更多