向某些代码添加功能标志时,我想要一种在应用和不应用标志的情况下执行现有代码的方法,以确保打开/关闭它不会破坏现有代码。
假设我有以下课程和测试:
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);
}
}
}
IFeatureFlags
接口,并将其注入到TestSubject
中。我真正需要做的是确保当功能标志为
true
或false
时,原来的两个测试都通过。我的选择是
[true, false]
。[InlineData]
属性,并为前半部分设置 true
,为后半部分设置 false
。true
和 false
[InlineData]
。[InlineData]
属性复杂。我曾考虑过对所有标志的所有变体使用
[ClassData]
属性,但我无法将它们的输入与现有 [InlineData]
属性组合起来,所以我不得不放弃这个想法。
我目前循环遍历每个测试中的所有变化,并确保重新初始化每个循环的所有状态,但这让我每次看到它时都会感到有点恶心。
这个问题有优雅的解决方案吗?
按照 Jon Skeet 的建议,我使用了 Xunit.Combinatorial 库,它工作得非常好!