如何编写数据库调用的单元测试

时间:2009-08-01 23:14:04

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

我已经接近一个新项目的开始了(喘气!)我第一次尝试将单元测试包含在我的项目中。

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

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对象中返回数据。

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

一旦解决了问题,是否要模拟数据库组件或尝试对实际数据库进行测试。

10 个答案:

答案 0 :(得分:43)

你在测试什么?

有三种可能性,我的头顶:

  

一个。您正在测试DAO(数据访问对象)类,确保它正确地编组传递给数据库的值/参数,并正确地编组/转换/打包数据库中的结果。

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

  

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

在这种情况下,由于SQL方言不同,您希望针对正确版本的RDBMS运行(可能生成的)SQL,而不是尝试模拟RDBMS的所有怪癖(以及任何更改功能的RDBMS升级)被你的测试抓住了。

  

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

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

答案 1 :(得分:28)

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

我认为您最好的方法是使用一个测试数据库,充满了大量糟糕的数据,然后针对它运行数据库组件测试。一直记住,你的用户将比你搞砸数据要好得多。

答案 2 :(得分:10)

单元测试的重点是单独测试单元(duh)。数据库调用的重点是集成与另一个单元(数据库)。 Ergo:对单元测试数据库调用没有意义。

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

答案 3 :(得分:6)

为了爱上帝,不要对已经填充的实时数据库进行测试。但你知道的。

通常,您已经知道每个查询将检索哪种数据,无论您是在验证用户身份,查找电话簿/组织结构图条目,还是其他任何内容。您知道自己感兴趣的字段,并且知道它们存在哪些约束(例如,UNIQUENOT NULL等等。您是对与数据库交互的代码进行单元测试,而不是数据库本身,因此请考虑如何测试这些函数。如果字段可能为NULL,则应进行测试以确保代码正确处理NULL值。如果您的某个字段是字符串(CHARVARCHARTEXT和& c),请测试以确保您正确处理转义字符。

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

*包括不良,恶意或无效的输入。

答案 4 :(得分:4)

您可以对除queryDA.Fill(resultSet);

之外的所有内容进行单元测试

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

我是一个,没有看到集成测试是坏的,只是它会捕获一个不同类型的错误,具有不同的错误否定和误报的可能性,不太可能经常进行,因为它太慢了。

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

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

答案 5 :(得分:3)

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

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

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

答案 6 :(得分:2)

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

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

答案 7 :(得分:1)

为了正确地执行此操作,尽管您应该使用一些依赖注入(DI),而对于.NET,有几个。我目前正在使用Unity Framework,但还有一些更容易。

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

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

既然你问过最佳实践,那就是IMO。

然后,除非你需要,否则不要去db,正如所建议的那样。

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

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

答案 8 :(得分:1)

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

  • 因为您的代码基本上是没有任何内容的DAO /存储库 业务逻辑,您需要集成测试,不需要单元测试。

  • 单元测试应该测试没有外部依赖项的类(例如DB 或致电其他远程服务)。

  • 您应始终尝试分离业务逻辑(您的域 模型)代码,然后从基础结构代码中提取代码 测试。

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

  • 检查以下模式:“域模型”,“六角结构”,“功能核心,命令性外壳”

答案 9 :(得分:0)

在基于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

相关问题