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

Cod*_*ism 82 .net c# xunit.net

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

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

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

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

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

Eri*_*oom 95

据我所知,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
}
Run Code Online (Sandbox Code Playgroud)

但是,将为每个调用执行基类设置和拆卸代码.这可能不是你想要的,因为效率不高.更优化的版本将使用该IClassFixture<T>接口来确保仅调用一次全局初始化/拆除功能.对于此版本,您不会从测试类扩展基类,而是实现引用fixture类的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)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

导致构造函数TestsFixture仅针对每个被测试的类运行一次.因此,它取决于您想要在两种方法之间进行选择.

  • 虽然这有效,但我认为Geir Sagberg的答案中的CollectionFixture更适合这种情况,因为它是专门为此目的而设计的.您也不必继承测试类,只需使用`[Collection("<name>")]`属性标记它们 (6认同)
  • XUnit 提供了三个初始化选项:每个测试方法、每个测试类和跨越多个测试类。文档在这里:https://xunit.net/docs/shared-context (4认同)
  • 似乎IUseFixture已不再存在,已被IClassFixture取代. (3认同)
  • 有没有办法做异步设置和拆解? (3认同)

Lar*_*ith 37

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.

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:

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
{
    // ...
}
Run Code Online (Sandbox Code Playgroud)

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.

  • 我遇到的唯一问题是,您需要使用“Collection”属性来装饰您的测试类,以便进行“全局”设置。这意味着,如果您在运行 -any- 测试之前需要设置任何内容,则需要使用此属性来装饰 -all- 测试类。在我看来,这太脆弱了,因为忘记装饰单个测试类可能会导致难以追踪的错误。如果 xUnit 能够创建一种真正全局设置和拆卸的方法,那就太好了。 (3认同)

bra*_*ing 11

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

https://github.com/Fody/ModuleInit

这是一个nuget包,当你安装它时,它会添加一个调用ModuleInitializer.cs该项目的新文件.这里有一个静态方法,它在构建后被编织到程序集中,并在程序集加载之后和运行任何程序之前运行.

我使用它将软件许可证解锁到我购买的库中.我总是忘记在每个测试中解锁许可证,甚至忘记从基类中派生测试来解锁它.编写这个库的明亮火花,而不是告诉你它是许可证锁定引入了微妙的数字错误,导致测试失败或通过他们不应该.你永远不会知道你是否正确地解锁了图书馆.所以现在我的模块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");
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • C# 9 现在支持[模块初始值设定项](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9#support-for-code-generators)。 (6认同)
  • 坚实的想法; 不幸的是,它似乎还没有用于DNX单元测试. (2认同)
  • 这是一个糟糕的主意,它将在模块加载时执行代码,而不是在所有测试之前执行。 (2认同)

Gei*_*erg 10

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

引用:

要使用集合装置,您需要执行以下步骤:

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

  • 它工作正常,但它将串行运行所有测试用例,因为您将它们标记在同一集合下。同一集合下的测试用例不能在 xunit 中并行运行。要涵盖并行场景,请参阅[此答案](/sf/answers/3720039851/) (2认同)

Tar*_*aro 8

如果你有一个全局初始化和全局清理函数,你可以这样编写类:

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

    public void Dispose()
    {
        MyOwnTestEngine.Cleanup();
    }
}
Run Code Online (Sandbox Code Playgroud)

对于每个需要执行初始化的测试类,您需要添加额外的属性:

[Collection("TestEngine")]
public class MyTests
{
Run Code Online (Sandbox Code Playgroud)

Collection重要提示:和- 属性中使用的名称CollectionDefinition必须匹配。

您还可以使用在构造函数中提供 TestEngine 类实例,例如如下所示:

[Collection("TestEngine")]
public class MyTests
{
     public MyTests(TestEngineInitializer initializer)
     {
     }
Run Code Online (Sandbox Code Playgroud)

但这不是强制性的。

有关问题的完整文档位于此处:

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