kimsagro
kimsagro

Reputation: 17425

Autofixture - Duplication of logic in tests

Imagine a simple mapping scenario:

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

If I were to test this using autofixture, I would do something like

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

}

As you can see, the logic for generating a person's fullname is duplicated in the test. We have to do this as we are using auto-generated values for the input.

My question is, is this acceptable?.

I don't see any way around it when using Autofixture, however in a situation where the output is generated by a series of complicated calculations, it doesn't feel right to duplicate these calculations in the test.

Update - Responding to Mark's comment as code formatted is not avaliable in comments

I not sure I understand your answer. Are you suggesting that this example is so simple that it doesn't require a test?

I was watching the string calculator kata and the same issues arises.

public void AddTwoNumbersReturnsCorrectResult(
      Calculator sut,
      int x,
      int y)
{
  var numbers = string.Join(",", x, y);
  sut.Add(numbers).ShouldBe(x + y);
}

In order to test the calculator add method is correct, you have to repeat the implementation. Is this case also too simple to test?

Upvotes: 2

Views: 241

Answers (1)

Mark Seemann
Mark Seemann

Reputation: 233457

No programming tool solves all problems. It's true that as given, the AutoFixture-based test essentially duplicates the implementation code. It's only appropriate to question its value.

Before looking at alternatives, it's important to step back and review why you should trust tests in the first place. One of the reasons is that when a test is simpler than the implementation, we can review each test independently. The implementation may be complex enough that a review wouldn't uncover all potential bugs, but a review of each test case has a better chance of ensuring that each test is correct - particularly if you write them using TDD and start by seeing the test fail.

I often use Cyclomatic Complexity when determining whether a test is warranted. Tests should have a Cyclomatic Complexity of 1. When an implementation also has a Cyclomatic Complexity of 1, a test often isn't warranted. After all, instead of reviewing the test for correctness, you can review the implementation itself for correctness, and your chance of spotting any defects is better, because without a test, you'll have less code to review.

The above discussion hinges on the cost of failure. If you're building rocket guidance software or a pacemaker system, you'll want to make your software as fail-safe as possible; in other cases, there may be safe-fail alternatives.

In the latter case, I'd simply omit the test for this particular method.

In the former case, you have other alternatives.

Example-Driven Tests

On example is to fall back to Example-Driven Tests, like this one:

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

Notice that AutoFixture is nowhere in sight here.

Property-Based Testing

Another alternative is to take a cue from Property-Based Testing, and use AutoFixture or FsCheck to provide values unknown at design-time, but decompose the problem into properties (traits, qualities), and test each decomposed property in isolation.

For this particular conversion, I can identify the following properties:

  • Fullname must be all upper case
  • Fullname must contain at least one space
  • Fullname must start with FirstName, using a case-insensitive comparison
  • Fullname must end with Surname, using a case-insensitive comparison
  • Fullname must contain FirstName only once
  • Fullname must contain Surname only once

There may be others.

You can implement each property as an independent test method. This is a bit of work, so not something I'd normally do with a conversion as simple as this one. I just wanted to point out the option.

Upvotes: 2

Related Questions