【问题标题】:XUnit & AutoFixture: Running a test with different data combinations (several AutoDataAttributes)XUnit & AutoFixture:使用不同的数据组合运行测试(几个 AutoDataAttributes)
【发布时间】:2015-12-10 05:47:16
【问题描述】:

可能是愚蠢的问题,我第一次真正编写单元测试(我感到羞耻)。我正在使用 Xunit 和 AutoFixture。 所以,我有一些单元测试,我想用不同的输入数据组合来运行。

例如,假设我正在测试我的 DAL,并希望使用不同类型的存储库(例如内存和 SQL)测试相同的单元测试。我还需要创建一些其他数据,单元测试和存储库都将使用这些数据。 我的一些类需要通过工厂方法创建,因为它们没有任何公共构造函数。

我的理想是做类似的事情

[Theory, InMemoryRepository, SomeOtherData]
[Theory, SqlRepository, SomeOtherData]
// ... some other data combinations ...
public void SomeTest(IRepository repository, ISomeOtherData someOtherData)
{
    // do some tests here
}

public class InMemoryRepositoryAttribute : AutoDataAttribute
    {

    public InMemoryRepositoryAttribute()
    {
        Fixture.Register<IRepository>(CreateRepository);
    }

    protected IRepository CreateRepository()
    {
        var data = Fixture.CreateMany<ISomeOtherData>().ToList();  // this will throw an exception "AutoFixture was unable to create an instance (...)" as my binding doesn't seem be available in the Fixture
        var repository = new InMemoryDAL.Repository();
        repository.Insert(data);

        return repository;
    }
}

public class SomeOtherDataAttribute : AutoDataAttribute
{
    public SomeOtherDataAttribut()
    {
        Fixture.Register<ISomeOtherData>(CreateData);
    }

    protected ISomeOtherData CreateData()
    {
        string test = Fixture.Create<string>();
        var data = (new SomeOtherDataFactory()).Create(test);
        return data;
    }
}

但这不起作用,因为我的两个 AutoDataAttribute 类似乎都基于单独的装置。 IE。我在 SomeOtherDataAttribute 中注册的任何内容似乎在我的 InMemoryRepositoryAttribute 实例中都不可用。

有没有办法解决这个问题并使用属性将不同的数据集组合到一个 Fixture 中?

或者您会建议什么替代方案 - 最好为每个数据组合创建单独的测试函数,并从那里显式调用 SomeTest()?

谢谢!

【问题讨论】:

    标签: c# unit-testing xunit autofixture


    【解决方案1】:

    实际上,您可以通过定义自定义AutoDataAttribute 来实现此目的,该AutoDataAttribute 允许提供自定义类型的数组。以下是如何完成此任务的示例:

    public class CompositeCustomizationsAttribute : AutoDataAttribute
    {
        public CompositeCustomizationsAttribute(params Type[] customizationTypes)
        {
            foreach (var type in customizationTypes)
            {
                var customization = (ICustomization)Activator.CreateInstance(type);
                Fixture.Customize(customization);
            }
        }
    }
    
    public class InMemoryRepositoryCustomization : ICustomization
    {
        public void Customize(IFixture fixture)
        {
            fixture.Register<IRepository>(() => CreateRepository(fixture));
        }
    
        protected IRepository CreateRepository()
        {
            var data = Fixture.CreateMany<ISomeOtherData>().ToList();
            var repository = new InMemoryDAL.Repository();
            repository.Insert(data);
    
            return repository;
        }
    }
    
    public class SomeOtherDataCustomization : ICustomization
    {
        public void Customize(IFixture fixture)
        {
            fixture.Register<ISomeOtherData>(() => CreateData(fixture));
        }
    
        protected ISomeOtherData CreateData()
        {
            string test = Fixture.Create<string>();
            var data = (new SomeOtherDataFactory()).Create(test);
            return data;
        }
    }
    

    鉴于上述自定义和CompositeCustomizationsAttribute,您现在可以定义您的测试方法:

    [Theory, CompositeCustomizations(typeof(InMemoryRepositoryCustomization), typeof(SomeOtherDataCustomization))]
    public void SomeTest(IRepository repository, ISomeOtherData someOtherData)
    {
        // do some tests here
    }
    

    【讨论】:

    • 您好,感谢您的回复。唯一的缺点是你不能轻易地为这些类型指定任何参数/构造函数参数(除非可能会使用一些更丑陋的代码,类型后跟对象数组,然后是类型...),但我猜那是属性的限制。无论如何,可能会使用这个解决方案。谢谢!
    • 是的,你是对的。这是某种权衡。实际上,在软件开发中,我们经常需要权衡取舍。