137

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

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

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

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

谁能给我一个关于如何以声明方式或编程方式运行一些全局设置/拆卸代码的提示?

4

5 回答 5

166

据我所知,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只为每个被测类运行一次。因此,这取决于您想要在两种方法之间进行选择。

于 2013-05-16T14:48:16.560 回答
58

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

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

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 对待集合夹具的方式与类夹具非常相似,只是集合夹具对象的生命周期更长:它是在集合中的任何测试类中运行任何测试之前创建的,并且不会被清理直到集合中的所有测试类都完成运行。

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

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

重要提示:fixtures 必须与使用它们的测试在同一个程序集中。

于 2017-02-01T17:22:17.253 回答
14

有一个简单易行的解决方案。使用 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");
    }
}

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

于 2014-03-26T12:10:58.107 回答
12

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

引用:

要使用集合夹具,您需要执行以下步骤:

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

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

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

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

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

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

重要提示:CollectionCollectionDefinition-attributes 中使用的名称必须匹配。

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

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

但这不是强制性的。

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

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

于 2020-11-26T13:50:45.613 回答