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


98

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

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

另外,如果我以编程方式调用xUnit,则还可以使用以下代码来实现所需的功能:

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

谁能为我提供有关如何以声明方式或以编程方式运行某些全局设置/拆卸代码的提示?


1
我想在这里就是答案:stackoverflow.com/questions/12379949/...
the_joric

Answers:


118

据我所知,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
}

但是,将为每个调用执行基类设置和拆卸代码。这可能不是您想要的,因为它不是很有效。一个更优化的版本将使用该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)
    {
    }
}

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


4
似乎IUseFixture已不存在,已由IClassFixture代替。
GaTechThomas 2015年

9
虽然这可行,但我认为Geir Sagberg的答案中的CollectionFixture更适合此情况,因为它是专门为此目的而设计的。您也不必继承测试类,只需将它们标记为[Collection("<name>")]属性
MichelZ

8
有什么方法可以进行异步设置和拆卸吗?
Andrii

看来MS也已经实现了IClassFixture解决方案。docs.microsoft.com/en-us/aspnet/core/test/…–
lbrahim

3
XUnit提供了三个初始化选项:每个测试方法,每个测试类以及跨多个测试类。该文档位于:xunit.net/docs/shared-context
GHN

48

我一直在寻找相同的答案,这时xUnit文档在如何实现类夹具和集合夹具方面非常有帮助,这些夹具为开发人员在类或类组级别提供了广泛的设置/拆卸功能。这与盖尔·萨格伯格(Geir Sagberg)的回答是一致的,并且给出了良好的框架实现以说明其外观。

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

集合夹具何时使用:当您想要创建一个测试上下文并在多个测试类的测试之间共享它,并在测试类中的所有测试完成后将其清除。

有时您会想要在多个测试类之间共享一个夹具对象。用于类固定装置的数据库示例是一个很好的示例:您可能希望使用一组测试数据来初始化数据库,然后将该测试数据保留在适当的位置以供多个测试类使用。您可以使用xUnit.net的collection fixture功能在多个测试类中的测试之间共享一个对象实例。

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

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

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处理集合固定装置的方式与类固定装置的方式几乎相同,不同的是集合固定装置对象的寿命更长:它是在集合中的任何测试类中运行任何测试之前创建的,并且不会被清除直到集合中的所有测试类均已运行完毕。

测试集合也可以用IClassFixture <>装饰。xUnit.net就像对待测试集中的每个单独测试类都用类夹具装饰一样。

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

重要说明:夹具必须与使用夹具的测试位于同一组件中。


1
“测试集合也可以用IClassFixture <>装饰。xUnit.net将此视为将测试集合中的每个测试类都用类夹具装饰。” 我有机会举个例子吗?我不太明白。
rtf

@TannerFaulkner类夹具是进行CLASS级别设置和拆除的一种方式,就像使用传统的.net单元测试项目时,可以使用Test Initialize方法一样:[TestInitialize] public void Initialize(){
Larry Smith

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

13

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

https://github.com/Fody/ModuleInit

这是一个nuget软件包,在您安装它时会ModuleInitializer.cs向项目添加一个新文件。这里有一种静态方法,可在构建后将其编织到程序集中,并在程序集加载后和运行任何东西之前立即运行。

我用它来解锁购买的库的软件许可证。我总是忘记在每个测试中解锁许可证,甚至忘记从一个可以解锁它的基类派生测试。编写该库的璀璨火花,没有告诉您它是被许可证锁定的,而是引入了细微的数字错误,这些错误会导致测试在不应该通过的情况下失败或通过。您永远不会知道您是否已正确解锁了库。所以现在我的模块初始化看起来像

/// <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");
    }
}

并且放置在该部件中的所有测试都将为其正确解锁许可证。


2
好主意;不幸的是,它似乎不适用于DNX单元测试。
杰夫·邓洛普

12

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

引用:

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

  • 创建夹具类,然后将启动代码放入夹具类构造函数中。
  • 如果夹具类需要执行清除,请在夹具类上实现IDisposable,然后将清除代码放入Dispose()方法中。
  • 创建集合定义类,并使用[CollectionDefinition]属性对其进行修饰,为其指定一个唯一的名称,该名称将标识测试集合。
  • 将ICollectionFixture <>添加到集合定义类。
  • 使用您提供给测试集合定义类的[CollectionDefinition]属性的唯一名称,将[Collection]属性添加到将成为集合一部分的所有测试类中。
  • 如果测试类需要访问Fixture实例,请将其添加为构造函数参数,它将自动提供。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.