自动混合 - 测试中的逻辑重复

时间:2015-07-22 02:21:45

标签: autofixture

想象一个简单的映射场景:

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);
}

为了测试计算器添加方法是否正确,您必须重复执行。这种情况是否也太简单了?

1 个答案:

答案 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

可能还有其他人。

您可以将每个属性实现为独立的测试方法。这是一些工作,所以我通常不会像转换那样简单地进行转换。我只想指出这个选项。