C# 如何确保在测试后始终执行数据库清理?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9860562/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 10:58:14  来源:igfitidea点击:

How to ensure that database cleanup is always performed after a test?

c#unit-testing

提问by Mathias Lykkegaard Lorenzen

Consider the following example of a unit test. The comments pretty much explain my problem.

考虑以下单元测试示例。评论几乎解释了我的问题。

[TestMethod]
public void MyTestMethod()
{

  //generate some objects in the database
  ...

  //make an assert that fails sometimes (for example purposes, this fails always)
  Assert.IsTrue(false);

  //TODO: how do we clean up the data generated in the database now that the test has ended here?

}

采纳答案by Mike Zboray

There are two ways to do this. Oneis using TestInitialize and TestCleanup attributes on methods in the test class. They will always be run before and after the test, respectively.

有两种方法可以做到这一点。一种是在测试类中的方法上使用 TestInitialize 和 TestCleanup 属性。它们将始终分别在测试前后运行。

Another way is to use the fact that test failures are propagated to the test runner via exceptions. This means that a try { } finally { } block in your test can be used clean up anything after an assert fails.

另一种方法是利用测试失败通过异常传播到测试运行器的事实。这意味着测试中的 try { } finally { } 块可用于在断言失败后清理任何内容。

[TestMethod]
public void FooTest()
{
  try
  {
     // setup some database objects
     Foo foo = new Foo();
     Bar bar = new Bar(foo);
     Assert.Fail();
  }
  finally
  {
     // remove database objects.
  }
}

The try/finally cleanup can get really messy is there are a lot of objects to cleanup. What my team has leaned towards is a helper class which implements IDisposable. It tracks what objects have been created and pushes them onto a stack. When Dispose is called the items are popped off the stack and removed from the database.

try/finally 清理可能会变得非常混乱,因为有很多对象需要清理。我的团队倾向于使用实现 IDisposable 的辅助类。它跟踪创建了哪些对象并将它们推送到堆栈上。当调用 Dispose 时,项目从堆栈中弹出并从数据库中删除。

[TestMethod]
public void FooTest()
{
  using (FooBarDatabaseContext context = new FooBarDatabaseContext())
  {
    // setup some db objects.
    Foo foo = context.NewFoo();
    Bar bar = context.NewBar(foo);
    Assert.Fail();
  } // calls dispose. deletes bar, then foo.
}

This has the added benefit of wrapping the constructors in method calls. If constructor signatures change we can easily modify the test code.

这具有将构造函数包装在方法调用中的额外好处。如果构造函数签名发生变化,我们可以轻松修改测试代码。

回答by David

A couple of responses:

几个回应:

  1. If it's using an actual database, I'd argue that it's not a "unit test" in the strictest sense of the term. It's an integration test. A unit test should have no such side-effects. Consider using a mocking library to simulate the actual database. Rhino Mocksis one, but there are plenty of others.

  2. If, however, the whole pointof this test is to actually interact with a database, then you'll want to interact with a transient test-only database. In that case part of your automated testing would include code to build the test database from scratch, then run the tests, then destroy the test database. Again, the idea is to have no external side-effects. There are probably multiple ways to go about this, and I'm not familiar enough with unit testing frameworks to really give a concrete suggestion. But if you're using the testing that's built in to Visual Studio then perhaps a Visual Studio Database Projectwould be of use.

  1. 如果它使用的是实际数据库,我认为它不是最严格意义上的“单元测试”。这是一个集成测试。单元测试不应该有这样的副作用。考虑使用模拟库来模拟实际数据库。 Rhino Mocks是其中之一,但还有很多其他的。

  2. 但是,如果整本次测试的是真正互动与数据库,那么你会希望与瞬态只测试数据库进行交互。在这种情况下,自动化测试的一部分将包括从头开始构建测试数据库的代码,然后运行测试,然后销毁测试数据库。同样,这个想法是没有外部副作用。可能有多种方法可以解决这个问题,我对单元测试框架不够熟悉,无法真正给出具体的建议。但是,如果您正在使用 Visual Studio 中内置的测试,那么也许可以使用Visual Studio 数据库项目

回答by alfa

Your question is a little bit too general. Usually you should clean up after every single test. Usually you cannot rely that all tests are always executed in the same order and you have to be sure about what is in your database. For general setup or cleanup most unit test frameworks provide setUp and tearDown methods that you can override and will automatically be called. I don't know how that works in C# but e. g. in JUnit (Java) you have these methods.

你的问题有点太笼统了。通常你应该在每次测试后清理。通常你不能相信所有的测试总是以相同的顺序执行,你必须确定你的数据库中有什么。对于一般设置或清理,大多数单元测试框架提供了 setUp 和 tearDown 方法,您可以覆盖这些方法并将自动调用。我不知道它在 C# 中是如何工作的,但例如在 JUnit (Java) 中你有这些方法。

I agree with David. Your tests usually should have no side effects. You should set up a new database for every single test.

我同意大卫。您的测试通常应该没有副作用。您应该为每个测试设置一个新数据库。

回答by Jason Jong

You'll have to do manual cleanup in this circumstance. Ie, the opposite of the generate some objects in the db.

在这种情况下,您必须进行手动清理。即,与在数据库中生成一些对象相反。

The alternative, is to use Mocking tools such as Rhino Mocks so that the database is just a in memory database

另一种方法是使用 Rhino Mocks 等 Mocking 工具,使数据库只是一个内存数据库

回答by Tigran

It depends on what you are actuallytesting. Looking on the comments I would say yes, but by the way it's difficult to deduct looking on comments. Cleaning up the object you just inserted you, in practice, reset the state of the test. So if you cleanup, you begin to test from cleanup system.

这取决于您实际测试的内容。查看评论我会说是的,但顺便说一下,很难扣除评论。清理您刚刚插入的对象,实际上是重置测试状态。所以如果你清理,你就从清理系统开始测试。

回答by Peter Provost

I think the best answer in situations like this is to think very carefully about what you are trying to test. Ideally a unit test should be trying to test a single fact about a single method or function. When you start combining many things together it crosses over into the world of integration tests (which are equally valuable, but different).

我认为在这种情况下最好的答案是非常仔细地考虑您要测试的内容。理想情况下,单元测试应该尝试测试有关单个方法或函数的单个事实。当你开始将许多东西组合在一起时,它跨越到集成测试的世界(它们同样有价值,但不同)。

For unit testing purposes, to enable you to test only the thing you want to test, you will need to design for testability. This typically involves additional use of interfaces (I'm assuming .NET from the code you showed) and some form of dependency injection (but doesn't require an IoC/DI container unless you want one). It also benefits from, and encourages you to create very cohesive (single purpose) and decoupled (soft dependencies) classes in your system.

出于单元测试的目的,为了使您能够只测试您想要测试的东西,您需要针对可测试性进行设计。这通常涉及额外使用接口(我假设您展示的代码中是 .NET)和某种形式的依赖注入(但不需要 IoC/DI 容器,除非您需要)。它还受益于并鼓励您在系统中创建非常内聚(单一用途)和解耦(软依赖项)的类。

So when you are testing business logic that depends on data from a database, you would typically use something like the Repository Patternand inject a fake/stub/mockIXXXRepository in for unit testing. When you are testing the concrete repository, you either need to do the kind of database cleanup you are asking about or you need to shim/stub the underlying database call. That is really up to you.

因此,当您测试依赖于数据库数据的业务逻辑时,您通常会使用诸如Repository Pattern 之类的东西,并在单元测试中注入一个fake/stub/mockIXXXRepository。当您测试具体的存储库时,您要么需要执行您所要求的那种数据库清理,要么需要对底层数据库调用进行填充/存根。这真的取决于你。

When you do need to create/populate/cleanup the database, you might consider taking advantage of the various setup and teardown methods available in most testing frameworks. But be careful, because some of them are run before and after each test, which can seriously impact the performance of your unit tests. Tests that run too slowly will not be run very often, and that is bad.

当您确实需要创建/填充/清理数据库时,您可以考虑利用大多数测试框架中可用的各种设置和拆卸方法。但要小心,因为其中一些会在每次测试前后运行,这会严重影响单元测试的性能。运行速度太慢的测试不会经常运行,这很糟糕。

In MS-Test, the attributes you would use to declare setup/teardown are ClassInitialize, ClassCleanUp, TestInitialize, TestCleanUp. Other frameworks have similarly named constructs.

在 MS-Test 中,用于声明设置/拆卸的属性是ClassInitializeClassCleanUpTestInitializeTestCleanUp。其他框架具有类似命名的构造。

There are a number of frameworks that can help you with the mocking/stubbing: Moq, Rhino Mocks, NMock, TypeMock, Moles and Stubs(VS2010), VS11 Fakes(VS11 Beta), etc. If you are looking for dependency injection frameworks, look at things like Ninject, Unity, Castle Windsor, etc.

有一些框架,可以帮助你的嘲讽/磕碰的:起订量犀牛嘲笑NMockTypeMock痣和存根(VS2010),VS11假货(VS11测试版)等,如果你正在寻找的依赖注入框架,看看像NinjectUnityCastle Windsor等。

回答by TGH

I think the clean up depends on how you're building the data, so if "old test data" doesn't interact with future tests runs, I think it's fine to leave it behind.

我认为清理取决于您如何构建数据,所以如果“旧测试数据”不与未来的测试运行交互,我认为可以将其留在后面。

An approach I've been taking when writing integration tests is to have the tests run against a different db than the application db. I tend to rebuild the test db as a precondition to each test run. That way you don't need a granular clean-up scheme for each test as each test run gets a clean slate between runs. Most of my development i done using SQL server, but I have in some cases run my tests against a SQL Compact edition db, which is fast and efficient to rebuild between runs.

我在编写集成测试时一直采用的一种方法是让测试针对与应用程序数据库不同的数据库运行。我倾向于重建测试数据库作为每次测试运行的先决条件。这样你就不需要为每个测试制定一个细粒度的清理方案,因为每个测试运行在运行之间都会得到一个干净的石板。我的大部分开发工作都是使用 SQL Server 完成的,但在某些情况下,我会针对 SQL Compact 版本数据库运行我的测试,该数据库在两次运行之间重建既快速又高效。

回答by Ivan Gerken

mbUnit has a very handy attribute Rollback that cleans up the database after finishing the test. However, you'll have to configure DTC (Distributed Transaction Coordinator) in order to be able to use it.

mbUnit 有一个非常方便的属性 Rollback,可以在完成测试后清理数据库。但是,您必须配置 DTC(分布式事务协调器)才能使用它。

回答by hngr18

I was having a comparable issue where one test's assertion was preventing cleanup and causing other tests to fail.

我遇到了一个类似的问题,其中一个测试的断言阻止了清理并导致其他测试失败。

Hopefully this is of use to somebody, sometime.

希望这对某人有用,有时。

    [Test]
    public void Collates_Blah_As_Blah()
    {
        Assert.False(SINGLETON.Collection.Any());

        for (int i = 0; i < 2; i++)
            Assert.That(PROCESS(ValidRequest) == Status.Success);

        try
        {
            Assert.AreEqual(1, SINGLETON.Collection.Count);
        }
        finally
        {
            SINGLETON.Collection.Clear();
        }
    }

The finally block will execute whether the assertion passes or fails, it also doesn't introduce the risk of false passes - which catch will cause!

无论断言通过还是失败,finally 块都会执行,它也不会引入错误通过的风险 - 会导致错误通过!

回答by Larry

The following is a skeleton of a test method that I use. this allows me to use a try catch finally to do the cleanup code in the finally block without loosing my assert that fail.

以下是我使用的测试方法的框架。这允许我使用 try catch finally 在 finally 块中执行清理代码而不会丢失我的失败断言。

    [TestMethod]
    public void TestMethod1()
    {
        Exception defaultException = new Exception("No real execption.");
        try
        {
            #region Setup
            #endregion

            #region Tests
            #endregion

        }
        catch (Exception exc)
        { 
            /*if an Assert fails this catches its Exception so that it can be thrown 
            in the finally block*/
            defaultException = exc; 
        }
        finally
        {
            #region Cleanup
            //cleanup code goes here 

            if (!defaultException.Message.Equals("No real execption."))
            {
                throw defaultException;
            }
            #endregion
        }
    }