在 xUnit 测试方法中共享测试上下文

19 篇文章 3 订阅


在使用 xUnit 进行单元测试时,有时候我们需要在不同的测试类中进行一些相同的初始化或清理工作,这些代码称为 测试上下文(test context)。根据范围的不同, xUnit 提供了三种共享测试上下文的方式:

  • 构造函数与析构函数(constrctor & Dispose)
  • Class Fixture (在一个类的不同测试中共享对象实例)
  • Collection Fixture (在不同测试类之间共享对象实例)

Constructor & Dispose

适用场景:每个测试都需要一个全新的测试上下文,即每个测试方法共享初始与清理代码,但不需要共享对象实例。

这里需要说明一下 xUnit 的工作原理。当每个测试运行时,xUnit 都会为其创建一个所在测试类的新实例。因此,测试类的构造函数中的任何代码都将针对每个单独的测试运行。 这使得构造函数成为一个放置可重用的上下文设置代码的理想地方。这样,每个运行的测试都将获得上下文对象的干净副本。
如果想要清理上下文,测试类需要实现 IDisposable 接口,把清理代码放在 Dispose() 方法中。
举个栗子:

public class StackTests : IDisposable
{
    Stack<int> stack;

    public StackTests()
    {
        stack = new Stack<int>();
    }

    public void Dispose()
    {
        stack.Dispose();
    }

    [Fact]
    public void WithNoItems_CountShouldReturnZero()
    {
        var count = stack.Count;
        Assert.Equal(0, count);
    }

    [Fact]
    public void AfterPushingItem_CountShouldReturnOne()
    {
        stack.Push(42);
        var count = stack.Count;
        Assert.Equal(1, count);
    }
}

这种方式也被称作 “测试类作为上下文” 模式。

Class Fixtures

适用场景:当我们需要创建一个单例的测试上下文,并在整个测试类的不同测试中共享它,最后在这个类所有测试运行完后清理它。

有时测试上下文的初始化和清理可能开销很大。 如果在每个测试中都运行初始化和清理代码,测试可能运行的很慢。 这时可以使用 xUnit 的 class fixture 功能在一个测试类的所有测试之间共享单个对象实例。

我们已经知道 xUnit 会为每个测试创建一个新的测试类实例。 使用 class fixture 时,xUnit 会在任何测试运行之前创建 fixture 实例,然后当所有测试完成后,它将通过调用 Dispose(如果存在)清理fixture 对象。

使用 Class fixtures 的步骤如下:
(1)创建 fixture 类,将 startup 代码放在 fixture 类的构造函数中。
(2)如果 fixture 类需要进行清理,在 fixture 类上实现 IDisposable,将清理代码放在Dispose()方法中。
(3)测试类实现 IClassFixture<>
(4)如果测试类需要获取 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; }
}

public class MyDatabaseTests : IClassFixture<DatabaseFixture>
{
    DatabaseFixture fixture;

    public MyDatabaseTests(DatabaseFixture fixture)
    {
        this.fixture = fixture;
    }

    // ... write tests, using fixture.Db to get access to the SQL Server ...
}

在上面的案例中,在 MyDatabaseTests 中的第一个测试运行之前,xUnit 将创建一个 DatabaseFixture 实例。 对于每个测试,它将创建一个新的 MyDatabaseTests 实例,并将 DatabaseFixture 的共享实例传递给构造函数。

重要说明:xUnit 通过接口 IClassFixture<> 来感知你要创建和清理一个 class fixture。 无论是否将类的实例作为构造函数参数,它都会执行此操作。 如果添加了构造函数参数但忘记添加接口,xUnit 将报错。

如果需要多个 fixture 对象,可多次实现该接口,并把多个 fixture 对象实例放到构造函数参数中。 构造函数参数的顺序不重要。

但是 ,我们无法控制 fixture 对象的创建顺序,并且 fixture 不能依赖于其他 fixture。 如果想控制创建顺序或 fixture 之间的依赖关系,可创建一个封装其他两个 fixture 的类,用它创建对象。

Collection Fixtures

适用场景:当我们需要创建一个单例的测试上下文,并在多个测试类的不同测试中共享它,最后在这些类所有测试运行完后清理它。

有时我们想在多个测试类之间共享一个 fixture 对象。 上面的数据库案例是一个很好的示例:你可能想用一组测试数据初始化数据库,然后将这些测试数据留在原处供多个测试类使用。 这时可用 xUnit 的 collection fixture 功能在多个测试类中的测试之间共享单个对象实例。

使用步骤:
(1)创建 fixture 类,将 startup 代码放在 fixture 类的构造函数中。
(2)如果 fixture 类需要进行清理,在 fixture 类上实现 IDisposable,将清理代码放在Dispose()方法中。
(3)创建一个类,打上 [CollectionDefinition] 特性(表示它定义了一个Collection),该特性接受一个字符串参数作为集合名称。
(4)将上一步的 Collection 定义类实现 ICollectionFixture<> 接口。
(5)对想要共享的测试类,打上 [Collection] 特性,该特性的参数传入在第3步创建的集合类的特性中给的名称。
(6)在测试类中,通过构造函数注入 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>
{
    // 这个集合定义类没有代码,也不会被实际实例化,它存在的意义仅仅是提供集合定义的信息。
}

[Collection("Database collection")]
public class DatabaseTestClass1
{
    DatabaseFixture fixture;

    public DatabaseTestClass1(DatabaseFixture fixture)
    {
        this.fixture = fixture;
    }
}

[Collection("Database collection")]
public class DatabaseTestClass2
{
    // ...
}

xUnit 处理 collection fixture 的逻辑与 class fixture 差不多,除了 fxiture 的生命周期不一样。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值