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

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

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

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

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

有帮助吗?

解决方案

你在测试什么?

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

  

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

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

  

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

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

  

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

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

其他提示

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

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

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

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

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

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

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

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

You can unit test everything except: queryDA.Fill(resultSet);

As soon as you execute queryDA.Fill(resultSet), you either have to mock/fake the database, or you are doing integration testing.

I for one, don't see integration testing as being bad, it's just that it'll catch a different sort of bug, has different odds of false negatives and false positives, isn't likely to be done very often because it is so slow.

If I was unit testing this code, I'd be validating that the parameters are build correctly, does the command builder create the right number of parameters? Do they all have a value? Do nulls, empty strings and DbNull get handled correctly?

Actually filling the dataset is testing your database, which is a flaky component out of the scope of your DAL.

Strictly speaking, a test that writes/reads from a database or a file system is not a unit test. (Although it may be an integration test and it may be written using NUnit or JUnit). Unit-tests are supposed to test operations of a single class, isolating its dependencies. So, when you write unit-test for the interface and business-logic layers, you shouldn't need a database at all.

OK, but how do you unit-test the database access layer? I like the advice from this book: xUnit Test Patterns (the link points to the book's "Testing w/ DB" chapter. The keys are:

  • use round-trip tests
  • don't write too many tests in your data access test fixture, because they will run much slower than your "real" unit tests
  • if you can avoid testing with a real database, test without a database

For unit tests I usually mock or fake the database. Then use your mock or fake implementation via dependency injection to test your method. You'd also probably have some integration tests that will test constraints, foreign key relationships, etc. in your database.

As to what you would test, you'd make sure that the method is using the connection from the parameters, that the query string is assigned to the command, and that your result set returned is the same as that you are providing via an expectation on the Fill method. Note -- it's probably easier to test a Get method that returns a value than a Fill method the modifies a parameter.

In order to do this properly though you would should use some dependency injection (DI), and for .NET there are several. I am currently using the Unity Framework but there are others that are easier.

Here is one link from this site on this subject, but there are others: Dependency Injection in .NET with examples?

This would enable you to more easily mock out other parts of your application, by just having a mock class implement the interface, so you can control how it will respond. But, this also means designing to an interface.

Since you asked about best practices this would be one, IMO.

Then, not going to the db unless you need to, as suggested is another.

If you need to test certain behaviors, such as foreign key relationships with cascade delete then you may want to write database tests for that, but generally not going to a real database is best, esp since more than one person may run a unit test at a time and if they are going to the same database tests may fail as the expected data may change.

Edit: By database unit test I mean this, as it is designed to just use t-sql to do some setup, test and teardown. http://msdn.microsoft.com/en-us/library/aa833233%28VS.80%29.aspx

On JDBC based project, JDBC connection can be mocked, so that tests can be executed without live RDBMS, with each test case isolated (no data conflict).

It allow to verify, persistence code passes proper queries/parameters (e.g. https://github.com/playframework/playframework/blob/master/framework/src/anorm/src/test/scala/anorm/ParameterSpec.scala) and handle JDBC results (parsing/mapping) as expected ("takes in all the necessary bits and pieces to extract some data from the database, and returns the data in a DataTable object").

Framework like jOOQ or my framework Acolyte can be used for: https://github.com/cchantep/acolyte .

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top