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

发布于 2024-07-29 19:41:45 字数 1073 浏览 6 评论 0原文

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

我在设计一些单元测试本身时遇到了麻烦。 我有一些很容易测试的方法(传入两个值并检查预期输出)。 我还有代码的其他部分,它们正在执行更复杂的操作,例如对数据库运行查询,但我不确定如何测试它们。

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

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

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

I'm near the beginning of a new project and (gasp!) for the first time ever I'm trying to include unit tests in a project of mine.

I'm having trouble devising some of the unit tests themselves. I have a few methods which have been easy enough to test (pass in two values and check for an expected output). I've got other parts of the code which are doing more complex things like running queries against the database and I'm not sure how to test them.

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;
}

This method essentially takes in all the necessary bits and pieces to extract some data from the database, and returns the data in a DataTable object.

The first question is probably the most complex: What should I even test in a situation like this?

Once that's settled comes the question of whether or not to mock out the database components or try to test against the actual DB.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

羁客 2024-08-05 19:41:46

你在测试什么?

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

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

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

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

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

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

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

What are you testing?

There are three possibilities, off the top of my head:

A. You're testing the DAO (data access object) class, making sure it's correctly marshaling the values/parameters being passed to the database,, and correctly marshaling/transforming/packaging results gotten frm the database.

In this case, you don't need to connect to the database at all; you just need a unit test that replaces the database (or intermediate layer, eg., JDBC, (N)Hibernate, iBatis) with a mock.

B. You're testing the syntactic correctness of (generated) SQL.

In this case, because SQL dialects differ, you want to run the (possibly generated) SQL against the correct version of your RDBMS, rather than attempting to mock all quirks of your RDBMS (and so that any RDBMS upgrades that change functionality are caught by your tests).

C. You're testing the semantic correctness of your SQL, i.e, that for a given baseline dataset, your operations (accesses/selects and mutations/inserts and updates) produce the expected new dataset.

For that, you want to use something like dbunit (which allows you to set up a baseline and compare a result set to an expected result set), or possibly do your testing wholly in the database, using the technique I outline here: Best way to test SQL queries.

挽你眉间 2024-08-05 19:41:46

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

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

This is why (IMHO) unit tests can sometimes create a false sense of security on the part of developers. In my experience with applications that talk to a database, errors are commonly the result of data being in an unexpected state (unusual or missing values etc.). If you routinely mock up data access in your unit tests, you will think your code is working great when it is in fact still vulnerable to this kind of error.

I think your best approach is to have a test database handy, filled with gobs of crappy data, and run your database component tests against that. All the while remembering that your users will be much much better than you are at screwing up your data.

[旋木] 2024-08-05 19:41:46

单元测试的重点是单独测试一个单元(废话)。 数据库调用的全部目的是与另一个单元(数据库)集成。 因此:对数据库调用进行单元测试是没有意义的。

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

The whole point of a unit test is to test a unit (duh) in isolation. The whole point of a database call is to integrate with another unit (the database). Ergo: it doesn't make sense to unit test database calls.

You should, however, integration test database calls (and you can use the same tools you use for unit testing if you want).

赴月观长安 2024-08-05 19:41:46

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

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

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

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

For the love of God, don't test against a live, already-populated database. But you knew that.

In general you already have an idea of what sort of data each query is going to retrieve, whether you're authenticating users, looking up phonebook/org chart entries, or whatever. You know what fields you're interested in, and you know what constraints exist on them (e.g., UNIQUE, NOT NULL, and so on). You're unit testing your code that interacts with the database, not the database itself, so think in terms of how to test those functions. If it's possible for a field to be NULL, you should have a test that makes sure that your code handles NULL values correctly. If one of your fields is a string (CHAR, VARCHAR, TEXT, &c), test to be sure you're handling escaped characters correctly.

Assume that users will attempt to put anything* into the database, and generate test cases accordingly. You'll want to use mock objects for this.

* Including undesirable, malicious or invalid input.

回忆躺在深渊里 2024-08-05 19:41:46

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

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

  • 使用往返测试,
  • 不要在数据访问测试装置中编写太多测试,因为
  • 如果您可以避免使用真实数据库,无数据库测试

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
花辞树 2024-08-05 19:41:46

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

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

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

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

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

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.

千仐 2024-08-05 19:41:46

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

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

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.

久伴你 2024-08-05 19:41:46

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

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

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

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

  • 小心 Mock,它可能是糟糕设计的信号。 它的意思是
    你的业务逻辑与基础设施混合在一起。

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

The first question is probably the most complex: What should I even test in a situation like this?

  • Since your code code is basically a DAO/repository without any
    business logic you need an integration test, not a unit test.

  • Unit test should test classes without external dependencies (like DB
    or calls to other remote services).

  • You should always try to separate the business logic (your Domain
    Model) code from infrastructure code then it will be easy to use unit
    tests.

  • Be careful with Mocks, it can be a signal of bad design. It means
    you business logic is mixed with infrastructure.

  • Check these patterns: "Domain Model", "Hexagonal Architecture", "Functional Core, Imperative Shell"

绝情姑娘 2024-08-05 19:41:46

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

以下是该网站关于此主题的一个链接,但还有其他链接:
.NET 中的依赖注入示例?

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

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

然后,除非需要,否则不要访问数据库,如建议的那样是另一回事。

如果您需要测试某些行为,例如带有级联删除的外键关系,那么您可能需要为此编写数据库测试,但通常最好不要使用真实的数据库,特别是因为可能会有多个人在以下位置运行单元测试如果他们要使用相同的数据库,测试可能会失败,因为预期的数据可能会发生变化。

编辑:我所说的数据库单元测试是指这个,因为它被设计为仅使用 t-sql 进行一些设置、测试和拆卸。
http://msdn.microsoft.com/en-我们/库/aa833233%28VS.80%29.aspx

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

巴黎盛开的樱花 2024-08-05 19:41:46

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

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 .

度的依靠╰つ 2024-08-05 19:41:46

也许,一个好的方法是测试与后台数据库通信的域逻辑的行为。

无需深入了解 DDDCQRS,您可以查看 DbSample 在 GitHub 上,一个基于 EF Core 的示例项目,对与 MS SQL Server 配合使用的服务进行全自动测试。 它还具有 GitHub Actions 管道,用于在云构建中运行测试。

测试的一个示例是

[Fact]
public async Task Update_Client_Works()
{
    // GIVEN a DB with a client
    var existingClient = await DataContext.AddAsync(new Client { Name = "Name" });
    await DataContext.SaveChangesAsync();
    var clientId = existingClient.Id;
    
    // WHEN update name of the client (this domain command executes an SQL query)
    await _clientCommandService.Update(clientId, new CreateUpdateClientRequest("XYZ"));
        
    // THEN the name is updated
    var client = await DataContext.Clients.FindAsync(clientId);
    Assert.Equal("XYZ", client!.Name);
}

要更深入地了解编排测试,请参阅“付出与收获针对 SQL 的自动化测试(MS SQL、PostgreSQL)”文章。 它进入了“如何?”的困境。 和“为什么?”。 剧透警告——它非常依赖 Docker。

Perhaps, a good approach would be to test the behaviour of your domain logic that communicates with the DB under the hood.

Without diving into DDD and CQRS, you could check out DbSample on GitHub, a sample EF Core based project with fully automated tests against services that work with MS SQL Server. It also has a GitHub Actions pipeline to run the tests in cloud builds.

An example of the test would be

[Fact]
public async Task Update_Client_Works()
{
    // GIVEN a DB with a client
    var existingClient = await DataContext.AddAsync(new Client { Name = "Name" });
    await DataContext.SaveChangesAsync();
    var clientId = existingClient.Id;
    
    // WHEN update name of the client (this domain command executes an SQL query)
    await _clientCommandService.Update(clientId, new CreateUpdateClientRequest("XYZ"));
        
    // THEN the name is updated
    var client = await DataContext.Clients.FindAsync(clientId);
    Assert.Equal("XYZ", client!.Name);
}

For a deeper dive into orchestrating tests, see "Pain & Gain of automated tests against SQL (MS SQL, PostgreSQL)" article. It goes into the woods of "how?" and "why?". Spoiler alert – it relies on Docker a lot.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文