想象一个简单的映射场景:
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);
}
为了测试计算器添加方法是否正确,您必须重复执行。这种情况是否也太简单了?
答案 0 :(得分:2)
没有编程工具可以解决所有问题。确实如此,基于AutoFixture的测试基本上复制了实现代码。只讨论其价值是恰当的。
在考虑替代方案之前,首先要回过头来审核why you should trust tests。其中一个原因是当测试更简单而不是实现时,我们可以独立审核每个测试。实施可能足够复杂,以至于审查不会发现所有潜在的错误,但是对每个测试用例的审查更有可能确保每个测试都是正确的 - 特别是如果您使用TDD编写它们并且首先看到测试失败
在确定测试是否合理时,我经常使用 Cyclomatic Complexity 。测试的循环复杂度应为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
必须全部为大写Fullname
必须至少包含一个空格Fullname
必须以FirstName
开头,使用不区分大小写的比较Fullname
必须以Surname
结尾,使用不区分大小写的比较Fullname
只能包含FirstName
次Fullname
只能包含Surname
次可能还有其他人。
您可以将每个属性实现为独立的测试方法。这是一些工作,所以我通常不会像转换那样简单地进行转换。我只想指出这个选项。