我正在尝试创建一种在单元测试中测试字符串参数的简单方法,对于大多数字符串参数,我想在参数为Null,Empty或仅包含空格时检查行为.
在大多数情况下,我使用string.IsNullOrWhiteSpace()检查参数,如果它具有这三个值中的一个,则抛出异常.
现在进行单元测试,似乎我必须为每个字符串参数编写三个单元测试.一个用于空值,一个用于空值,一个用于空白.
想象一个带有3或4个字符串参数的方法,然后我需要编写9或12个单元测试...
任何人都可以想出一个简单的方法来测试这个吗?也许使用AutoFixture?
为避免多次重复相同的测试,您可以编写参数化测试.
如果你正在使用xUnit,你会写一个所谓的理论.理论意味着您正在证明一个原则,即当给定相同类型输入数据的不同样本时,某个函数的行为与预期一致.例如:
[Theory] [InlineData(null)] [InlineData("")] [InlineData(" ")] public void Should_throw_argument_exception_when_input_string_is_invalid(string input) { Assert.Throws(() => SystemUnderTest.SomeFunction(input)); }
xUnit运行器将多次运行此测试,每次将input
参数分配给[InlineData]
属性中指定的值之一.
如果正在测试的函数有多个参数,您可能不关心将哪些值传递给其余参数,只要其中至少有一个是null
空字符串或仅包含空格的字符串.
在这种情况下,您可以将参数化测试与AutoFixture结合使用.AutoFixture旨在为您提供通用测试数据,这些数据足以在大多数情况下使用,而您不必关心确切的值.
为了将它与xUnit理论一起使用,您需要将AutoFixture.Xunit NuGet包(或AutoFixture.Xunit2,具体取决于您使用的版本)添加到测试项目中并使用该InlineAutoData
属性:
[Theory] [InlineAutoData(null)] [InlineAutoData("")] [InlineAutoData(" ")] public void Should_throw_argument_exception_when_the_first_input_string_is_invalid( string input1, string input2, string input3) { Assert.Throws(() => SystemUnderTest.SomeFunction(input1, input2, input3)); }
只有input1
参数具有特定值,而其余参数将由AutoFixture分配随机字符串.
这里需要注意的一点是,通过[InlineAutoData]
属性传递的值将根据其位置分配给测试参数.由于我们需要测试所有三个参数相同的行为独立,我们将不得不写三个测试:
[Theory] [InlineAutoData(null)] [InlineAutoData("")] [InlineAutoData(" ")] public void Should_throw_argument_exception_when_the_second_input_string_is_invalid( string input2, string input1, string input3) { Assert.Throws(() => SystemUnderTest.SomeFunction(input1, input2, input3)); } [Theory] [InlineAutoData(null)] [InlineAutoData("")] [InlineAutoData(" ")] public void Should_throw_argument_exception_when_the_third_input_string_is_invalid( string input3, string input1, string input2) { Assert.Throws (() => SystemUnderTest.SomeFunction(input1, input2, input3)); }
我不禁认为这些测试场景是基于属性的测试的完美匹配.在不进行太多细节的情况下,基于属性的测试是通过使用生成的输入多次运行来证明函数的特定行为(或"属性").
换句话说:
基于属性的测试根据输入生成有关代码输出的语句,并且会针对许多不同的可能输入验证这些语句.
在您的情况下,您可以编写一个单独的测试来验证函数是否抛出一个ArgumentException
,其中至少有一个参数是null
空字符串或只包含空格的字符串.
在.NET中,您可以使用名为FsCheck的库来编写和执行基于属性的测试.虽然API主要用于从F#中使用,但它也可以在C#中使用.
但是,在这种特殊情况下,我认为您最好坚持使用常规参数化测试和AutoFixture来实现相同的目标.事实上,用FsCheck和C#编写这些测试最终会变得更加冗长,并且在稳健性方面不会给你带来太大的影响.
正如@RubenBartelink 在评论中指出的,还有另一种选择.AutoFixture在一个名为AutoFixture.Idioms的小型库中封装了常见的断言.您可以使用它来集中对方法如何string
验证参数的期望,并在测试中使用它们.
虽然我对这种方法有所保留,但为了完整起见,我会在此处添加它作为另一种可能的解决方案:
[Theory, AutoData] public void Should_throw_argument_exception_when_the_input_strings_are_invalid( ValidatesTheStringArguments assertion) { var sut = typeof(SystemUnderTest).GetMethod("SomeMethod"); assertion.Verify(sut); } public class ValidatesTheStringArguments : GuardClauseAssertion { public ValidatesTheStringArguments(ISpecimenBuilder builder) : base( builder, new CompositeBehaviorExpectation( new NullReferenceBehaviorExpectation(), new EmptyStringBehaviorExpectation(), new WhitespaceOnlyStringBehaviorExpectation())) { } } public class EmptyStringBehaviorExpectation : IBehaviorExpectation { public void Verify(IGuardClauseCommand command) { if (!command.RequestedType.IsClass && !command.RequestedType.IsInterface) { return; } try { command.Execute(string.Empty); } catch (ArgumentException) { return; } catch (Exception e) { throw command.CreateException("empty", e); } throw command.CreateException("empty"); } } public class WhitespaceOnlyStringBehaviorExpectation : IBehaviorExpectation { public void Verify(IGuardClauseCommand command) { if (!command.RequestedType.IsClass && !command.RequestedType.IsInterface) { return; } try { command.Execute(" "); } catch (ArgumentException) { return; } catch (Exception e) { throw command.CreateException("whitespace", e); } throw command.CreateException("whitespace"); } }
根据表达的期望NullReferenceBehaviorExpectation
,EmptyStringBehaviorExpectation
以及WhitespaceOnlyStringBehaviorExpectation
AutoFixture将自动尝试调用指定的方法"SomeMethod"
有null
,分别为空字符串和空白.
如果方法没有抛出正确的异常 - 正如catch
期望类中的块中指定的那样- 那么AutoFixture本身会抛出异常来解释发生的事情.这是一个例子:
尝试将值空白分配给方法"SomeMethod"的参数"p1",并且没有Guard子句阻止此操作.你错过了一个守卫条款吗?
您也可以通过自己简单地实例化对象来使用没有参数化测试的AutoFixture.Idioms :
[Fact] public void Should_throw_argument_exception_when_the_input_strings_are_invalid() { var assertion = new ValidatesTheStringArguments(new Fixture()); var sut = typeof(SystemUnderTest).GetMethod("SomeMethod"); assertion.Verify(sut); }