我可以运行 XUnit 测试类的变体吗?

问题描述 投票:0回答:1

向某些代码添加功能标志时,我想要一种在应用和不应用标志的情况下执行现有代码的方法,以确保打开/关闭它不会破坏现有代码。

假设我有以下课程和测试:

public class TestSubject
{
    public void EnsureNumberIsOdd(int inputValue)
    {
        if (inputValue % 2 != 1)
            throw new ArgumentException("Must be an odd number", paramName: nameof(inputValue));
    }
}

public class UnitTest2
{
    private readonly TestSubject Subject;

    public UnitTest2()
    {
        Subject = new TestSubject();
    }

    [Theory]
    [InlineData(2)]
    [InlineData(4)]
    [InlineData(6)]
    public void WhenValueIsEven_ThenThrowsException(int inputValue)
    {
        var exception = Assert.Throws<ArgumentException>(() => Subject.EnsureNumberIsOdd(inputValue));
        Assert.Equal("inputValue", exception!.ParamName);
    }

    [Theory]
    [InlineData(1)]
    [InlineData(3)]
    [InlineData(5)]
    public void WhenValueIsOdd_ThenDoesNotThrowException(int inputValue)
    {
        Subject.EnsureNumberIsOdd(inputValue);
    }
}

现在有人决定有一个新要求,即

inputValue
必须小于
11
,但他们也希望将其隐藏在功能切换后面。

using Moq;
public interface IFeatureFlags
{
    bool IsFlagEnabled(string flagName);
}

public class TestSubject(IFeatureFlags FeatureFlags)
{
    public void EnsureNumberIsOdd(int inputValue)
    {
        if (inputValue % 2 != 1)
            throw new ArgumentException("Must be an odd number", paramName: nameof(inputValue));

        if (FeatureFlags.IsFlagEnabled("ValueMustBeLessThan11"))
            ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(inputValue, 11);
    }
}

public class UnitTest2
{
    private readonly Mock<IFeatureFlags> MockFeatureFlags;
    private readonly TestSubject Subject;
    private bool FeatureFlagIsEnabled;

    public UnitTest2()
    {
        MockFeatureFlags = new Mock<IFeatureFlags>();
        MockFeatureFlags
            .Setup(x => x.IsFlagEnabled("ValueMustBeLessThan11"))
            .Returns(() => FeatureFlagIsEnabled);

        Subject = new TestSubject(MockFeatureFlags.Object);
    }

    [Theory]
    [InlineData(2)]
    [InlineData(4)]
    [InlineData(6)]
    public void WhenValueIsEven_ThenThrowsException(int inputValue)
    {
        // Same as before
    }

    [Theory]
    [InlineData(1)]
    [InlineData(3)]
    [InlineData(5)]
    public void WhenValueIsOdd_ThenDoesNotThrowException(int inputValue)
    {
        // Same as before
    }

    [Fact]
    public void WhenFeatureFlagIsOn_AndValueIsNotLessThan10_ThenThrowsException()
    {
        FeatureFlagIsEnabled = true;
        foreach(int i in new[] { 11, 13, 15, 17, 19 })
        {
            var exception = Assert.Throws<ArgumentOutOfRangeException>(() => Subject.EnsureNumberIsOdd(i));
            Assert.Equal("inputValue", exception!.ParamName);
        }
    }
}

  1. 我添加了一个
    IFeatureFlags
    接口,并将其注入到
    TestSubject
    中。
  2. 我添加了一个新的测试,以确保如果该值不小于 11,则会抛出异常。

我真正需要做的是确保当功能标志为

true
false
时,原来的两个测试都通过。我的选择是

  1. 循环值
    [true, false]
  • 问题:这两项检查并不是孤立运行的。对于有状态的主体,这可能会导致失败。
  1. 复制
    [InlineData]
    属性,并为前半部分设置
    true
    ,为后半部分设置
    false
  • 问题:添加新场景时,有人可能不会同时添加
    true
    false
    [InlineData]
  • 问题:向同一代码添加新的功能标志会导致组合呈指数级增长,这难以管理,并且可能会出错。
  1. 重复测试。
  • 问题:如上所述,但复制测试方法远比复制
    [InlineData]
    属性复杂。

我曾考虑过对所有标志的所有变体使用

[ClassData]
属性,但我无法将它们的输入与现有
[InlineData]
属性组合起来,所以我不得不放弃这个想法。

我目前循环遍历每个测试中的所有变化,并确保重新初始化每个循环的所有状态,但这让我每次看到它时都会感到有点恶心。

这个问题有优雅的解决方案吗?

c# unit-testing xunit
1个回答
0
投票

按照 Jon Skeet 的建议,我使用了 Xunit.Combinatorial 库,它工作得非常好!

© www.soinside.com 2019 - 2024. All rights reserved.