You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在xUnit中实现无参数复用测试,适配多依赖实现?

多依赖实现复用测试用例的简洁方案

现有实现方式

常规参数化写法

这是最基础的实现,通过InlineData传入类型枚举,在测试方法内创建实例:

[Theory]
[InlineData(DoerType.Version1)]
[InlineData(DoerType.Version2)]
public void DoSomethingTest(DoerType type)
{
    IDoSomething doer = Factory.Create(type);

    string result = doer.DoSomething();
}

自定义Attribute优化版

将实例创建逻辑封装到自定义DataAttribute中,测试方法直接接收实例参数:

[Theory]
[DoerVersions(DoerType.Version1, DoerType.Version2)]
public void DoSomethingTest(IDoSomething doer)
{
    string result = doer.DoSomething();
}

public class DoerVersionsAttribute : DataAttribute
{
    private readonly DoerType[] doers;

    public DoerVersionsAttribute(params DoerType[] doers)
    {
        this.doers = doers;
    }

    public override IEnumerable<object[]> GetData(MethodInfo testMethod)
    {
        return doers.Select(d => new object[] { Factory.Create(d) });
    }
}

更符合DRY原则的极致简洁方案

针对大量测试需要复用该逻辑的场景,以下几种方案可以完全消除测试方法参数,让测试代码聚焦业务逻辑:

方案1:测试基类+字段注入

抽象出包含依赖字段的基类,配合自定义Attribute完成实例注入,统一复用逻辑:

// 基类封装依赖字段与注入逻辑
public abstract class DoerTestBase
{
    protected IDoSomething Doer { get; private set; }

    protected void InjectDoer(IDoSomething doer)
    {
        Doer = doer;
    }
}

// 自定义Attribute保持实例生成逻辑
public class DoerVersionsAttribute : DataAttribute
{
    private readonly DoerType[] _doers;

    public DoerVersionsAttribute(params DoerType[] doers)
    {
        _doers = doers;
    }

    public override IEnumerable<object[]> GetData(MethodInfo testMethod)
    {
        return _doers.Select(d => new object[] { Factory.Create(d) });
    }
}

// 测试类继承基类,注入后直接使用字段
public class Tests : DoerTestBase
{
    [Theory]
    [DoerVersions(DoerType.Version1, DoerType.Version2)]
    public void DoSomethingTest(IDoSomething doer)
    {
        InjectDoer(doer);
        string result = Doer.DoSomething();
    }
}

方案2:DI容器+Fixture注入(推荐)

结合测试框架的依赖注入能力,通过IClassFixture完成实例初始化与字段注入,完全消除测试方法参数:

// Fixture封装DI配置与实例创建
public class DoerFixture : IDisposable
{
    public IDoSomething Doer { get; }

    public DoerFixture(DoerType doerType)
    {
        Doer = Factory.Create(doerType);
    }

    public void Dispose()
    {
        // 清理实例资源
        (Doer as IDisposable)?.Dispose();
    }
}

// 自定义Attribute生成Fixture参数
public class DoerVersionsAttribute : DataAttribute
{
    private readonly DoerType[] _doers;

    public DoerVersionsAttribute(params DoerType[] doers)
    {
        _doers = doers;
    }

    public override IEnumerable<object[]> GetData(MethodInfo testMethod)
    {
        return _doers.Select(d => new object[] { new DoerFixture(d) });
    }
}

// 测试类通过构造函数获取Fixture中的实例
public class Tests : IClassFixture<DoerFixture>
{
    private readonly IDoSomething _doer;

    public Tests(DoerFixture fixture)
    {
        _doer = fixture.Doer;
    }

    [Theory]
    [DoerVersions(DoerType.Version1, DoerType.Version2)]
    public void DoSomethingTest()
    {
       string result = _doer.DoSomething();
    }
}

方案3:UI测试场景适配

针对你补充的跨接口UI测试需求,可以直接复用上述DI+Fixture方案,将依赖替换为DSL入口:

// Fixture根据接口类型初始化对应DSL
public class DslFixture : IDisposable
{
    public IDsl Dsl { get; }

    public DslFixture(Interface interfaceType)
    {
        Dsl = interfaceType switch
        {
            Interface.WebChrome => new ChromeDsl(),
            Interface.WebFirefox => new FirefoxDsl(),
            Interface.Android => new AndroidDsl(),
            _ => throw new ArgumentOutOfRangeException()
        };
    }

    public void Dispose()
    {
        Dsl?.Dispose();
    }
}

// 自定义Attribute传入接口类型
public class InterfacesAttribute : DataAttribute
{
    private readonly Interface[] _interfaces;

    public InterfacesAttribute(params Interface[] interfaces)
    {
        _interfaces = interfaces;
    }

    public override IEnumerable<object[]> GetData(MethodInfo testMethod)
    {
        return _interfaces.Select(i => new object[] { new DslFixture(i) });
    }
}

// 测试类完全聚焦业务逻辑
public class BookSearchTests : IClassFixture<DslFixture>
{
    private readonly IDsl _dsl;

    public BookSearchTests(DslFixture fixture)
    {
        _dsl = fixture.Dsl;
    }

    [Theory] 
    [Interfaces(Interface.WebChrome, Interface.WebFirefox, Interface.Android)]
    public void ShouldBeAbleToSearchBook()
    {
        _dsl.Users.Login("user1");
        _dsl.Searcher.Search("title:BDD");
        _dsl.Searcher.ShouldHaveResult("Some BDD book");
    }
}

内容的提问来源于stack exchange,提问作者Francesc Castells

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 09:15:34