C# xUnit.net:全局设置+拆卸?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12976319/
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-10 01:10:37  来源:igfitidea点击:

xUnit.net: Global setup + teardown?

c#.netxunit.net

提问by Codism

This question is about the unit testing framework xUnit.net.

这个问题是关于单元测试框架xUnit.net 的

I need to run some code before any test is executed, and also some code after all tests are done. I thought there should be some kind of attribute or marker interface to indicate the global initialization and termination code, but couldn't find them.

我需要在执行任何测试之前运行一些代码,以及在所有测试完成后运行一些代码。我认为应该有某种属性或标记接口来指示全局初始化和终止代码,但找不到它们。

Alternatively, if I invoke xUnit programmatically, I can also achieve what I want with the following code:

或者,如果我以编程方式调用 xUnit,我也可以使用以下代码实现我想要的:

static void Main()
{
    try
    {
        MyGlobalSetup();
        RunAllTests();  // What goes into this method?
    }
    finally
    {
        MyGlobalTeardown();
    }
}

Can anyone provide me a hint about how to declaratively or programmatically run some global setup/teardown code?

任何人都可以向我提供有关如何以声明方式或以编程方式运行某些全局设置/拆卸代码的提示吗?

采纳答案by Erik Schierboom

As far as I know, xUnit does not have a global initialization/teardown extension point. However, it is easy to create one. Just create a base test class that implements IDisposableand do your initialization in the constructor and your teardown in the IDisposable.Disposemethod. This would look like this:

据我所知,xUnit 没有全局初始化/拆卸扩展点。但是,创建一个很容易。只需创建一个基本测试类,该类实现IDisposable并在构造函数中进行初始化并在IDisposable.Dispose方法中进行拆卸。这看起来像这样:

public abstract class TestsBase : IDisposable
{
    protected TestsBase()
    {
        // Do "global" initialization here; Called before every test method.
    }

    public void Dispose()
    {
        // Do "global" teardown here; Called after every test method.
    }
}

public class DummyTests : TestsBase
{
    // Add test methods
}

However, the base class setup and teardown code will be executed for each call. This might not be what you want, as it is not very efficient. A more optimized version would use the IClassFixture<T>interface to ensure that the global initialization/teardown functionality is only called once. For this version, you don't extends a base class from your test class but implement the IClassFixture<T>interface where Trefers to your fixture class:

但是,每次调用都会执行基类设置和拆卸代码。这可能不是您想要的,因为它不是很有效。更优化的版本将使用该IClassFixture<T>接口来确保全局初始化/拆卸功能仅被调用一次。对于此版本,您不会从测试类扩展基类,而是实现引用夹具类的IClassFixture<T>接口T

using Xunit;

public class TestsFixture : IDisposable
{
    public TestsFixture ()
    {
        // Do "global" initialization here; Only called once.
    }

    public void Dispose()
    {
        // Do "global" teardown here; Only called once.
    }
}

public class DummyTests : IClassFixture<TestsFixture>
{
    public DummyTests(TestsFixture data)
    {
    }
}

This willresult in the constructor of TestsFixtureonly being run once for every class under test. It thus depends on what you want exactly to choose between the two methods.

导致构造函数TestsFixture对于每个被测类只运行一次。因此,这取决于您想要在两种方法之间进行选择的确切内容。

回答by bradgonesurfing

There is an easy easy solution. Use the Fody.ModuleInit plugin

有一个简单的解决方案。使用 Fody.ModuleInit 插件

https://github.com/Fody/ModuleInit

https://github.com/Fody/ModuleInit

It's a nuget package and when you install it it adds a new file called ModuleInitializer.csto the project. There is one static method in here that gets weaved into the assembly after build and is run as soon as the assembly is loaded and before anything is run.

它是一个 nuget 包,当您安装它时,它会添加一个名为ModuleInitializer.cs该项目的新文件。这里有一个静态方法,它在构建后被编入程序集,并在程序集加载后和任何运行之前立即运行。

I use this to unlock the software license to a library that I have purchased. I was always forgetting to unlock the license in each test and even forgetting to derive the test from a base class which would unlock it. The bright sparks that wrote this library, instead of telling you it was license locked introduced subtle numerical errors which causes tests to fail or pass when they shouldn't. You would never know if you had correctly unlocked the library or not. So now my module init looks like

我用它来解锁我购买的库的软件许可证。我总是忘记在每次测试中解锁许可证,甚至忘记从可以解锁它的基类派生测试。写这个库的火花,而不是告诉你它被许可证锁定引入了微妙的数字错误,导致测试在不应该失败或通过时失败。你永远不会知道你是否正确地解锁了图书馆。所以现在我的模块 init 看起来像

/// <summary>
/// Used by the ModuleInit. All code inside the Initialize method is ran as soon as the assembly is loaded.
/// </summary>
public static class ModuleInitializer
{
    /// <summary>
    /// Initializes the module.
    /// </summary>
    public static void Initialize()
    {
            SomeLibrary.LicenceUtility.Unlock("XXXX-XXXX-XXXX-XXXX-XXXX");
    }
}

and all tests that are placed into this assembly will have the license unlocked correctly for them.

并且放入此程序集中的所有测试都将为其正确解锁许可证。

回答by Geir Sagberg

To share SetUp/TearDown-code between multiple classes, you can use xUnit's CollectionFixture.

要在多个类之间共享 SetUp/TearDown 代码,您可以使用 xUnit 的CollectionFixture

Quote:

引用:

To use collection fixtures, you need to take the following steps:

  • Create the fixture class, and put the the startup code in the fixture class constructor.
  • If the fixture class needs to perform cleanup, implement IDisposable on the fixture class, and put the cleanup code in the Dispose() method.
  • Create the collection definition class, decorating it with the [CollectionDefinition] attribute, giving it a unique name that will identify the test collection.
  • Add ICollectionFixture<> to the collection definition class.
  • Add the [Collection] attribute to all the test classes that will be part of the collection, using the unique name you provided to the test collection definition class's [CollectionDefinition] attribute.
  • If the test classes need access to the fixture instance, add it as a constructor argument, and it will be provided automatically.

使用采集夹具,需要进行以下步骤:

  • 创建夹具类,并将启动代码放入夹具类构造函数中。
  • 如果fixture类需要进行清理,在fixture类上实现IDisposable,将清理代码放在Dispose()方法中。
  • 创建集合定义类,用 [CollectionDefinition] 属性修饰它,给它一个唯一的名称来标识测试集合。
  • 将 ICollectionFixture<> 添加到集合定义类。
  • 使用您为测试集合定义类的 [CollectionDefinition] 属性提供的唯一名称,将 [Collection] 属性添加到将成为集合一部分的所有测试类。
  • 如果测试类需要访问夹具实例,请将其添加为构造函数参数,它将自动提供。

回答by Larry Smith

I was looking for the same answer, and at this time the xUnit documentation is very helpful in regards to how to implement Class Fixtures and Collection Fixtures that give developers a wide range of setup/teardown functionality at the class or group of classes level. This is in line with the answer from Geir Sagberg, and gives good skeleton implementation to illustrate what it should look like.

我一直在寻找相同的答案,此时 xUnit 文档对于如何实现 Class Fixtures 和 Collection Fixtures 非常有帮助,它们为开发人员提供了广泛的类或类组级别的设置/拆卸功能。这与 Geir Sagberg 的回答一致,并提供了很好的框架实现来说明它应该是什么样子。

https://xunit.github.io/docs/shared-context.html

https://xunit.github.io/docs/shared-context.html

Collection Fixtures When to use: when you want to create a single test context and share it among tests in several test classes, and have it cleaned up after all the tests in the test classes have finished.

Sometimes you will want to share a fixture object among multiple test classes. The database example used for class fixtures is a great example: you may want to initialize a database with a set of test data, and then leave that test data in place for use by multiple test classes. You can use the collection fixture feature of xUnit.net to share a single object instance among tests in several test class.

To use collection fixtures, you need to take the following steps:

Create the fixture class, and put the the startup code in the fixture class constructor. If the fixture class needs to perform cleanup, implement IDisposable on the fixture class, and put the cleanup code in the Dispose() method. Create the collection definition class, decorating it with the [CollectionDefinition] attribute, giving it a unique name that will identify the test collection. Add ICollectionFixture<> to the collection definition class. Add the [Collection] attribute to all the test classes that will be part of the collection, using the unique name you provided to the test collection definition class's [CollectionDefinition] attribute. If the test classes need access to the fixture instance, add it as a constructor argument, and it will be provided automatically. Here is a simple example:

Collection Fixtures 何时使用:当您想要创建单个测试上下文并在多个测试类中的测试之间共享它时,并在测试类中的所有测试完成后将其清理干净。

有时您会希望在多个测试类之间共享一个夹具对象。用于类固定装置的数据库示例是一个很好的示例:您可能希望使用一组测试数据初始化数据库,然后将这些测试数据留在原处供多个测试类使用。您可以使用 xUnit.net 的集合夹具功能在多个测试类中的测试之间共享单个对象实例。

使用采集夹具,需要进行以下步骤:

创建夹具类,并将启动代码放入夹具类构造函数中。如果fixture类需要进行清理,在fixture类上实现IDisposable,将清理代码放在Dispose()方法中。创建集合定义类,用 [CollectionDefinition] 属性修饰它,给它一个唯一的名称来标识测试集合。将 ICollectionFixture<> 添加到集合定义类。使用您为测试集合定义类的 [CollectionDefinition] 属性提供的唯一名称,将 [Collection] 属性添加到将成为集合一部分的所有测试类。如果测试类需要访问夹具实例,请将其添加为构造函数参数,它将自动提供。这是一个简单的例子:

public class DatabaseFixture : IDisposable
{
    public DatabaseFixture()
    {
        Db = new SqlConnection("MyConnectionString");

        // ... initialize data in the test database ...
    }

    public void Dispose()
    {
        // ... clean up test data from the database ...
    }

    public SqlConnection Db { get; private set; }
}

[CollectionDefinition("Database collection")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture>
{
    // This class has no code, and is never created. Its purpose is simply
    // to be the place to apply [CollectionDefinition] and all the
    // ICollectionFixture<> interfaces.
}

[Collection("Database collection")]
public class DatabaseTestClass1
{
    DatabaseFixture fixture;

    public DatabaseTestClass1(DatabaseFixture fixture)
    {
        this.fixture = fixture;
    }
}

[Collection("Database collection")]
public class DatabaseTestClass2
{
    // ...
}

xUnit.net treats collection fixtures in much the same way as class fixtures, except that the lifetime of a collection fixture object is longer: it is created before any tests are run in any of the test classes in the collection, and will not be cleaned up until all test classes in the collection have finished running.

Test collections can also be decorated with IClassFixture<>. xUnit.net treats this as though each individual test class in the test collection were decorated with the class fixture.

Test collections also influence the way xUnit.net runs tests when running them in parallel. For more information, see Running Tests in Parallel.

Important note: Fixtures must be in the same assembly as the test that uses them.

xUnit.net 以与类设备相同的方式处理集合设备,除了集合设备对象的生命周期更长:它在集合中的任何测试类中运行任何测试之前创建,并且不会被清除直到集合中的所有测试类都完成运行。

测试集合也可以用 IClassFixture<> 装饰。xUnit.net 将此视为测试集合中的每个单独的测试类都用类夹具装饰。

测试集合也会影响 xUnit.net 在并行运行测试时运行测试的方式。有关更多信息,请参阅并行运行测试。

重要说明:夹具必须与使用它们的测试在同一个程序集中。