Reputation: 17425
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
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 caseFullname
must contain at least one spaceFullname
must start with FirstName
, using a case-insensitive comparisonFullname
must end with Surname
, using a case-insensitive comparisonFullname
must contain FirstName
only onceFullname
must contain Surname
only onceThere 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