Proper unit tests have a naming convention that helps you immediately identify what has failed:
public void AddNewCustomer_CustomerExists_ThrowsException()
This is why you have one assertion per test, so that each method (and it's name) corresponds to the condition that you are asserting.
As you've correctly pointed out, each new test is going to have similar setup code. As with any code, you can refactor the common code into its own method to reduce or eliminate the duplication and make your code more DRY. Some testing frameworks are specifically designed to allow you to put that setup code in one place.
In TDD, no test is YAGNI, because you write tests based only on what you require your code to do. If you don't need it, you won't write the test.
You never said what Clamp()
is supposed to do, so I'm assuming that it returns value
, unless it is outside of the range, in which case it returns one of the two bounds.
I don't see any reason to think that -1, 0, or 1 are corner cases. They may often be corner cases, but there's no reason they'd act strangely in this function. If you want a 'normal' value, 42 or -63 works, but there is no need for both of them, unless you suspect that >
and <
don't work properly on negative numbers in C#. (I don't think you need to worry about that.)
So we could just use −2147483648, 'a normal value', and 2147483647. (We could even say that testing with the max/min integer values aren't really necessary. Presumably, C# >
and <
work up to the minimum and maximum; there isn't any danger of integer overflow.)
There are 6 permutations of 3 values, so we're down to 6 testcases. 6 testcases is not much, and we can easily just write them down and use them, but we don't know for certain that we've selected test cases that cover everything (all we've done so far is reduce the original set of test cases to something smaller).
If we want to be sure we've caught all the cases that matter, we could reduce the massively large set of input values (4 billion cubed) by partitioning them into equivalence classes. Then we only need 1 test per equivalence class, since the equivalence class would be defined as a set of inputs that all act alike.
The value of Clamp(a, b, c)
depends on whether a
is in the range, or above it, or below it. There should be 3 equivalence classes: [a < b and a < c], [a > b and a > c], and otherwise. The return value will be b
, c
, or a
, respectively. This tells us not only what the tests should be, but how to write the code.
(There is one little thing that we haven't run into: what if the lower bounds is higher than the upper bounds. What I said in the previous paragraph applies if the assumption I made up at the top is right, but not if it isn't. It can be fixed easily, though, by swapping b and c or by returning Clamp(a, c, b)
if b > c.)
Best Answer
Unless you are specifically testing the
equals()
method of a class, there is usually no reason for a unit test to assert the equality of two objects. A unit test should test one behaviour and not all features of a code unit, whether that is a module, a class or even a method (a method with many possible inputs usually needs many unit tests and not just one).Therefore, there is often no need to create such a "comparison object". If you're verifying that a transaction puts your customer in the correct status, obtain the customer and
assert
things about its status, not that it's exactly equal to another complex object that the test provides. That is almost certainly testing too much an in a very brittle way, since any change to the customer class might break the "status" test.