70

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

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

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

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

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

4

10 回答 10

50

你在测试什么?

有三种可能性,我的脑海中浮现:

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

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

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

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

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

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

于 2009-08-02T00:36:58.237 回答
38

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

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

于 2009-08-02T00:46:26.133 回答
15

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

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

于 2009-08-03T01:11:24.287 回答
10

看在上帝的份上,不要对一个活生生的、已经填充好的数据库进行测试。但你知道的。

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

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

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

于 2009-08-01T23:41:55.827 回答
5

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

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

  • 使用往返测试
  • 不要在您的数据访问测试夹具中编写太多测试,因为它们会比您的“真实”单元测试运行得慢得多
  • 如果您可以避免使用真实数据库进行测试,请在没有数据库的情况下进行测试
于 2009-08-02T00:02:10.700 回答
4

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

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

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

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

实际上填充数据集是在测试您的数据库,这是一个超出 DAL 范围的易碎组件。

于 2009-08-02T01:24:57.407 回答
2

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

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

于 2009-08-01T23:28:53.247 回答
2

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

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

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

  • 您应该始终尝试将业务逻辑(您的领域模型)代码与基础设施代码分开,这样单元测试就会很容易使用。

  • 小心 Mocks,它可能是糟糕设计的信号。这意味着您的业务逻辑与基础设施混合在一起。

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

于 2020-04-12T08:45:00.700 回答
1

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

这是该站点上有关此主题的一个链接,但还有其他链接: .NET 中的依赖注入并带有示例?

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

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

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

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

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

于 2009-08-02T00:24:17.673 回答
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 。

于 2014-01-15T09:06:09.170 回答