首页 > 解决方案 > 如何参数化 xunit 类夹具?

问题描述

xUnit 提供了(共享)类固定装置的概念,如测试之间的共享上下文中所述。到目前为止,我还没有弄清楚是否有一种方法可以对此类固定装置进行参数化。例如,如果DatabaseFixture应该用一些依赖于它所针对的测试的测试数据来丰富它怎么办?测试类可能想要插入测试数据,但只插入一次,然后针对该数据库(夹具)运行所有测试。

换句话说,如果// ... initialize data in the test database ...来自文档(上面引用)也取决于测试怎么办?因为并非所有测试都可能希望拥有相同的测试数据。实际上,我什至认为很多时候测试定义自己的测试数据而不是在测试数据级别上耦合测试是一种很好的做法。

到目前为止,我正在做的解决方法是提供一种ConfiguredWith方法,该方法接受只执行一次的回调。为了做到这一点,我需要延迟测试数据库的初始化,以便确定配置选项已设置。就像是:

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

    public MyDatabaseTests(DatabaseFixture fixture)
    {
         this.fixture = fixture;
         this.fixture.ConfigureWith(new DatabaseFixtureOptions
         {
             InitTestData = db => db.Insert(...);
         };
    }

    // ... 
}

这看起来相当做作,因为在针对数据库编写测试时感觉像是标准要求。

如果 xUnit 不提供这种开箱即用的功能,也许有人有更好的模式来解决这个问题。

这个问题似乎朝着类似的方向发展,但我不一定固定在具有这种结构的解决方案上。

标签: c#xunitxunit.net

解决方案


我了解到,由于 xUnit 的并行执行,实体框架会抛出异常,因为它已经跟踪到了尝试共享实体框架数据库上下文IClassFixtureCollectionFixtures最终导致测试被另一个测试数据或死锁/竞争条件污染的艰难方法。具有给定 ID 的对象和更多类似的头痛。就个人而言,我建议针对您的特定使用原因,将数据库上下文创建/清理保留在constructor/dispose替代方案中,例如:

    public class TestClass : IDisposable
    {
        DatabaseContext DatabaseContext;

        public TestClass()
        {
            var options = new DbContextOptionsBuilder<DatabaseContext>()
              .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
              .Options;

            DatabaseContext = new DatabaseContext(options);

            //insert the data that you want to be seeded for each test method:
            DatabaseContext.Set<Product>().Add(new Product() { Id = 1, Name = Guid.NewGuid().ToString() });
            DatabaseContext.SaveChanges();
        }

        [Fact]
        public void FirstTest()
        {
            var product = DatabaseContext.Set<Product>().FirstOrDefault(x => x.Id == 1).Name;
            //product evaluates to => 0f25a10b-1dfd-4b4b-a69d-4ec587fb465b
        }

        [Fact]
        public void SecondTest()
        {
            var product = DatabaseContext.Set<Product>().FirstOrDefault(x => x.Id == 1).Name;
            //product evaluates to => eb43d382-40a5-45d2-8da9-236d49b68c7a
            //It's different from firstTest because is another object
        }

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

当然你总是可以做一些改进,但想法就在那里


推荐阅读