想象一个简单的映射场景:
public class Person {
public string Firstname { get; set; }
public string Surname { get; set; }
}
public class PersonDTO {
public string Firstname { get; set; }
public string Surname { get; set; }
public string Fullname { get; set; }
}
public static class PersonExtensions {
public static PersonDTO ToDTO(this Person person) {
var uppercaseFirstname = person.Firstname.ToUpper();
var uppercaseSurname = person.Surname.ToUpper();
return new PersonDTO() {
Firstname = uppercaseFirstname,
Surname = uppercaseSurname,
Fullname = string.Format("{0} {1}", uppercaseFirstname, uppercaseSurname)
};
}
}
如果我要使用自动固定装置来测试它,我会做类似的事情
public class PersonExtensionsTests {
public void ToDTOShouldMapPersonToPersonDTO)
{
var fixture = new Fixture();
var person = fixture.Create<Person>();
var actualPersonDTO = person.ToDTO();
var expectedPersonDTO = new PersonDTO() {
Firstname = person.Firstname.ToUpper(),
Surname = person.Surname.ToUpper(),
Fullname = person.Firstname.ToUpper() + " " + person.Surname.ToUpper()
};
// ShouldBeEquivalentTo is from fluent assertions
actualPersonDTO.ShouldBeEquivalentTo(expectedPersonDTO);
}
}
正如您所看到的,生成一个人的全名的逻辑在测试中是重复的。我们必须这样做,因为我们使用自动生成的输入值。
我的问题是,这可以接受吗?
在使用 Autofixture 时,我没有看到任何解决办法,但是在输出是由一系列复杂计算生成的情况下,在测试中重复这些计算感觉不太合适。
更新 - 回复 Mark 的评论,因为评论中没有格式化的代码
我不确定我是否理解您的回答。您是否认为这个示例非常简单,不需要测试?
我正在观看字符串计算器 kata,也出现了同样的问题。
public void AddTwoNumbersReturnsCorrectResult(
Calculator sut,
int x,
int y)
{
var numbers = string.Join(",", x, y);
sut.Add(numbers).ShouldBe(x + y);
}
为了测试计算器add方法是否正确,你必须重复执行。这个案例是不是也太简单了,无法测试?
最佳答案
没有任何编程工具可以解决所有问题。确实,正如所给出的,基于 AutoFixture 的测试本质上重复了实现代码。质疑它的值(value)才合适。
在考虑替代方案之前,重要的是退后一步并回顾 why you should trust tests首先。原因之一是,当测试比实现简单时,我们可以独立审查每个测试。实现可能非常复杂,审查无法发现所有潜在的错误,但是对每个测试用例的审查更有可能确保每个测试都是正确的 - 特别是如果您使用 TDD 编写它们并从看到测试失败开始.
在确定是否需要进行测试时,我经常使用循环复杂度。测试的循环复杂度应为 1。当实现的循环复杂度也为 1 时,通常不需要进行测试。毕竟,您可以检查实现本身的正确性,而不是检查测试的正确性,并且发现任何缺陷的机会会更好,因为如果没有测试,您需要检查的代码就会更少。
上述讨论取决于失败成本。如果您正在构建火箭制导软件或起搏器系统,您需要将您的软件制作为 fail-safe as possible; in other cases, there may be safe-fail alternatives .
在后一种情况下,我只是省略对此特定方法的测试。
在前一种情况下,您还有其他选择。
示例驱动的测试
示例是回退到示例驱动测试,如下所示:
[Theory]
[InlineData("foo", "bar", "FOO BAR")]
[InlineData("Foo", "Bar", "FOO BAR")]
[InlineData("baZ", "quUx", "BAZ QUUX")]
public void ToDTOShouldMapPersonToPersonDTO(
string firstName,
string surname,
string expected)
{
var person = new Person { FirstName = firstName, Surname = surname };
var actualPersonDTO = person.ToDTO();
// ShouldBeEquivalentTo is from fluent assertions
actualPersonDTO.Fullname.ShouldBeEquivalentTo(expected);
}
请注意,此处看不到 AutoFixture。
基于属性的测试
另一种选择是从基于属性的测试中获取线索,并使用 AutoFixture 或 FsCheck 提供设计时未知的值,但将问题分解为属性(特征、品质),并且单独测试每个分解的属性。
对于这个特定的转换,我可以识别以下属性:
全名
必须全部大写全名
必须至少包含一个空格Fullname
必须以FirstName
开头,使用不区分大小写的比较Fullname
必须以Surname
结尾,使用不区分大小写的比较Fullname
只能包含FirstName
一次Fullname
只能包含Surname
一次
可能还有其他。
您可以将每个属性实现为独立的测试方法。这是一项工作,所以我通常不会像这样简单地进行转换。我只是想指出这个选项。
关于Autofixture - 测试中的逻辑重复,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31552801/