Autofixture - 测试中的逻辑重复

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

为了测试计算器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/

相关文章:

c# - 如果 IRepository 调用没有返回值,则使用 Moq 解决测试依赖关系的目的

c# - 在大型依赖对象图上注入(inject)模拟

c# - 无法掌握 Freeze/Inject/Register 之间的区别

自动修复 : pass an argument to specimen builder

c# - 使用例如创建模拟对象和匿名对象的混合体最小起订量和 AutoFixture?

nuget - AutoFixture.Xunit 与 Xunit.net 2.0 测试版

c# - AutoFixture:如何从 System.Type 创建匿名

c# - 使用 AutoFixture 中的 AutoMock 对构造函数中的响应式(Reactive)代码进行单元测试?

c# - 为什么 Matching.ImplementedInterfaces 的行为与 Matching.ExactType 和 FrozenAttribute.As 不同?

c# - 如何使用 NSubstitute 模拟对同一方法的一系列调用以返回 AutoFixture 中的不同值?