简体   繁体   English

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

[英]xUnit.net: Global setup + teardown?

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?任何人都可以向我提供有关如何以声明方式或编程方式运行一些全局设置/拆卸代码的提示吗?

As far as I know, xUnit does not have a global initialization/teardown extension point.据我所知,xUnit 没有全局初始化/拆卸扩展点。 However, it is easy to create one.但是,创建一个很容易。 Just create a base test class that implements IDisposable and do your initialization in the constructor and your teardown in the IDisposable.Dispose method.只需创建一个实现IDisposable的基本测试类,并在构造函数中进行初始化并在IDisposable.Dispose方法中进行拆卸。 This would look like this:这看起来像这样:

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.更优化的版本将使用IClassFixture<T>接口来确保全局初始化/拆卸功能只调用一次。 For this version, you don't extends a base class from your test class but implement the IClassFixture<T> interface where T refers to your fixture class:对于此版本,您不会从测试类扩展基类,而是实现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 will result in the constructor of TestsFixture only being run once for every class under test.导致TestsFixture的构造函数对于每个被测类只运行一次。 It thus depends on what you want exactly to choose between the two methods.因此,这取决于您想要在两种方法之间进行选择的确切内容。

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.我一直在寻找相同的答案,此时 xUnit 文档对于如何实现 Class Fixtures 和 Collection Fixtures 非常有帮助,它们为开发人员提供了广泛的类或类组级别的设置/拆卸功能。 This is in line with the answer from Geir Sagberg, and gives good skeleton implementation to illustrate what it should look like.这与 Geir Sagberg 的回答一致,并提供了很好的框架实现来说明它应该是什么样子。

https://xunit.net/docs/shared-context https://xunit.net/docs/shared-context

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

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.您可以使用 xUnit.net 的集合夹具功能在多个测试类中的测试之间共享单个对象实例。

To use collection fixtures, you need to take the following steps:使用采集夹具,需要进行以下步骤:

Create the fixture class, and put the startup code in the fixture class constructor.创建fixture类,并将启动代码放入fixture类构造函数中。 If the fixture class needs to perform cleanup, implement IDisposable on the fixture class, and put the cleanup code in the Dispose() method.如果fixture类需要进行清理,在fixture类上实现IDisposable,将清理代码放在Dispose()方法中。 Create the collection definition class, decorating it with the [CollectionDefinition] attribute, giving it a unique name that will identify the test collection.创建集合定义类,使用 [CollectionDefinition] 属性对其进行修饰,并为其指定一个唯一名称以标识测试集合。 Add ICollectionFixture<> to the collection definition class.将 ICollectionFixture<> 添加到集合定义类。 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.使用您为测试集合定义类的 [CollectionDefinition] 属性提供的唯一名称,将 [Collection] 属性添加到将成为集合一部分的所有测试类。 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:这是一个简单的例子:

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

Test collections can also be decorated with IClassFixture<>.测试集合也可以用 IClassFixture<> 装饰。 xUnit.net treats this as though each individual test class in the test collection were decorated with the class fixture. xUnit.net 将此视为测试集合中的每个单独的测试类都用类夹具装饰。

Test collections also influence the way xUnit.net runs tests when running them in parallel.测试集合也会影响 xUnit.net 在并行运行测试时运行测试的方式。 For more information, see Running Tests in Parallel.有关更多信息,请参阅并行运行测试。

Important note: Fixtures must be in the same assembly as the test that uses them.重要说明:夹具必须与使用它们的测试在同一个程序集中。

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

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.并且放入此程序集中的所有测试都将为其正确解锁许可证。

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.如果fixture类需要进行清理,在fixture类上实现IDisposable,将清理代码放在Dispose()方法中。
  • Create the collection definition class, decorating it with the [CollectionDefinition] attribute, giving it a unique name that will identify the test collection.创建集合定义类,使用 [CollectionDefinition] 属性对其进行修饰,并为其指定一个唯一名称以标识测试集合。
  • Add ICollectionFixture<> to the collection definition class.将 ICollectionFixture<> 添加到集合定义类。
  • 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.使用您为测试集合定义类的 [CollectionDefinition] 属性提供的唯一名称,将 [Collection] 属性添加到将成为集合一部分的所有测试类。
  • If the test classes need access to the fixture instance, add it as a constructor argument, and it will be provided automatically.如果测试类需要访问夹具实例,请将其添加为构造函数参数,它将自动提供。

If you have one global initialization and global cleanup functions, you can write class like this:如果你有一个全局初始化和全局清理函数,你可以这样写类:

[CollectionDefinition("TestEngine")]
public class TestEngineInitializer: IDisposable, ICollectionFixture<TestEngineInitializer>
{
    public TestEngineInitializer()
    {
        MyOwnTestEngine.Init();
    }

    public void Dispose()
    {
        MyOwnTestEngine.Cleanup();
    }
}

And for each test class, where this initialization is required to be executed, you need to add extra attribute:对于每个需要执行此初始化的测试类,您需要添加额外的属性:

[Collection("TestEngine")]
public class MyTests
{

Important: Names used in Collection and CollectionDefinition -attributes must match.重要提示: CollectionCollectionDefinition -attributes 中使用的名称必须匹配。

You can use provide also TestEngine class instance into constructor, for example like this:您还可以使用提供 TestEngine 类实例到构造函数中,例如:

[Collection("TestEngine")]
public class MyTests
{
     public MyTests(TestEngineInitializer initializer)
     {
     }

but this is not mandatory.但这不是强制性的。

Whole documentation on issue is located in here:有关问题的完整文档位于此处:

https://xunit.net/docs/shared-context https://xunit.net/docs/shared-context

I solved this problem like this:我这样解决了这个问题:

public class IntegrationTest: IClassFixture<CustomWebApplicationFactory<Startup>>
{
    private readonly CustomWebApplicationFactory<Startup> _factory;
    public IntegrationTest(CustomWebApplicationFactory<Startup> factory)
    {
        CleanData();
        Initdata();
        _factory = factory;
    }
    public static void Initdata()
    {
        using (var context = new DataContext())
        {
            //Add Data
        }
    }

    public void CleanData()
    {
        using (var context = new DataContext())
        {
            //Clean Data
        }
    }
}

Super easy.超级简单。 that works pretty fine.效果很好。 may solve yours:)可以解决你的问题:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM