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

时间:2012-10-19 14:22:31

标签: c# .net xunit.net

这个问题与单元测试框架xUnit.net有关。

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

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

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

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

5 个答案:

答案 0 :(得分:75)

据我所知,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引用您的fixture类:

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 void SetFixture(TestsFixture data)
    {
    }
}

这个导致TestsFixture的构造函数只运行一次 对于每个受测试的班级。因此,这取决于你想要在两种方法之间做出什么选择。

答案 1 :(得分:29)

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

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

  

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

     

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

     

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

     

创建fixture类,并将启动代码放在fixture类构造函数中。   如果fixture类需要执行清理,则在fixture类上实现IDisposable,并将清理代码放在Dispose()方法中。   创建集合定义类,使用[CollectionDefinition]属性对其进行装饰,为其指定一个用于标识测试集合的唯一名称。   添加ICollectionFixture&lt;&gt;到集合定义类。   使用您为测试集合定义类的[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&lt;&gt;进行修饰。 xUnit.net将此视为测试集合中的每个单独测试类都使用类fixture进行修饰。

     

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

     

重要提示:灯具必须与使用它们的测试位于同一装配中。

答案 2 :(得分:10)

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

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

答案 3 :(得分:9)

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

引用:

  

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

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

答案 4 :(得分:0)

如果您具有一个全局初始化和全局清理功能,则可以编写如下类:

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

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

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

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

重要:CollectionCollectionDefinition属性中使用的名称必须匹配。

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

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

但这不是强制性的。

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

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