Yes, it is a good idea to give your tests names of the example scenarios you are testing. And using your unit testing tool for more than just unit tests maybe ok, too, lots of people do this with success (me too).
But no, it is definitely not a good idea to write your tests in a fashion where the order of execution of the tests matters. For example, NUnit allows the user to select interactively which test he/she wants to be executed, so this will not work the way intended any more.
You can avoid this easily here by separating the main testing part of each test (including the "assert") from the parts which set your system in the correct initial state. Using your example above: write methods for creating an account, logging on and post a comment - without any assert. Then reuse those methods in different tests. You will also have to add some code to the [Setup]
method of your test fixtures to make sure the system is in a properly defined initial state (for example, no accounts so far in the database, noone connected so far etc.).
EDIT: Of course, this seems to be against the "story" nature of your tests, but if you give your helper methods meaningful names, you find your stories within each test.
So, it should look like this:
[TestFixture]
public class Authentication_Bill
{
[Setup]
public void Init()
{ // bring the system in a predefined state, with noone logged in so far
}
[Test]
public void Test_if_Bill_can_create_account()
{
CreateAccountForBill();
// assert that the account was created properly
}
[Test]
public void Test_if_Bill_can_post_comment_after_login()
{
// here is the "story" now
CreateAccountForBill();
LoginWithBillsAccount();
AddCommentForBill();
// assert that the right things happened
}
private void CreateAccountForBill()
{
// ...
}
// ...
}
There are already lots of good answers to this question, and I've commented and upvoted several of them. Still, I'd like to add some thoughts.
Flexibility isn't for novices
The OP clearly states that he's not experienced with TDD, and I think a good answer must take that into account. In the terminology of the Dreyfus model of skill acquisition, he's probably a Novice. There's nothing wrong with being a novice - we are all novices when we start learning something new. However, what the Dreyfus model explains is that novices are characterized by
- rigid adherence to taught rules or plans
- no exercise of discretionary judgement
That's not a description of a personality deficiency, so there's no reason to be ashamed of that - it's a stage we all need to go through in order to learn something new.
This is also true for TDD.
While I agree with many of the other answers here that TDD doesn't have to be dogmatic, and that it can sometimes be more beneficial to work in an alternative way, that doesn't help anyone just starting out. How can you exercise discretionary judgement when you have no experience?
If a novice accepts the advice that sometimes it's OK not to do TDD, how can he or she determine when it's OK to skip doing TDD?
With no experience or guidance, the only thing a novice can do is to skip out of TDD every time it becomes too difficult. That's human nature, but not a good way to learn.
Listen to the tests
Skipping out of TDD any time it becomes hard is to miss out of one of the most important benefits of TDD. Tests provide early feedback about the API of the SUT. If the test is hard to write, it's an important sign that the SUT is hard to use.
This is the reason why one of the most important messages of GOOS is: listen to your tests!
In the case of this question, my first reaction when seeing the proposed API of the Yahtzee game, and the discussion about combinatorics that can be found on this page, was that this is important feedback about the API.
Does the API have to represent dice rolls as an ordered sequence of integers? To me, that smell of Primitive Obsession. That's why I was happy to see the answer from tallseth suggesting the introduction of a Roll
class. I think that's an excellent suggestion.
However, I think that some of the comments to that answer get it wrong. What TDD then suggests is that once you get the idea that a Roll
class would be a good idea, you suspend work on the original SUT and start working on TDD'ing the Roll
class.
While I agree that TDD is more aimed at the 'happy path' than it's aimed at comprehensive testing, it still helps to break the system down into manageable units. A Roll
class sounds like something you could TDD to completion much more easily.
Then, once the Roll
class is sufficiently evolved, would you go back to the original SUT and flesh it out in terms of Roll
inputs.
The suggestion of a Test Helper doesn't necessarily imply randomness - it's just a way to make the test more readable.
Another way to approach and model input in terms of Roll
instances would be to introduce a Test Data Builder.
Red/Green/Refactor is a three-stage process
While I agree with the general sentiment that (if you are sufficiently experienced in TDD), you don't need to stick to TDD rigorously, I think it's pretty poor advice in the case of a Yahtzee exercise. Although I don't know the details of the Yahtzee rules, I see no convincing argument here that you can't stick rigorously with the Red/Green/Refactor process and still arrive at a proper result.
What most people here seem to forget is the third stage of the Red/Green/Refactor process. First you write the test. Then you write the simplest implementation that passes all tests. Then you refactor.
It's here, in this third state, that you can bring all your professional skills to bear. This is where you are allowed to reflect on the code.
However, I think it's a cop-out to state that you should only "Write the simplest thing possible that isn't completely braindead and obviously incorrect that works". If you (think you) know enough about the implementation on beforehand, then everything short of the complete solution is going to be obviously incorrect. As far as advice goes, then, this is pretty useless to a novice.
What really should happen is that if you can make all tests pass with an obviously incorrect implementation, that's feedback that you should write another test.
It's surprising how often doing that leads you towards an entirely different implementation than the one you had in mind first. Sometimes, the alternative that grows like that may turn out to be better than your original plan.
Rigour is a learning tool
It makes a lot of sense to stick with rigorous processes like Red/Green/Refactor as long as one is learning. It forces the learner to gain experience with TDD not just when it's easy, but also when it's hard.
Only when you have mastered all the hard parts are you in a position to make an informed decision on when to deviate from the 'true' path. That's when you start forming your own path.
Best Answer
Some suggestions:
Avoid to use a random generator directly inside unit tests if there is no compelling reason for it. This has a high risk of producing non-reproducible cases. If you want to use a random string, choose the string randomly once and hardcode that choosen string into your test.
An old wisdom of testing is that you cannot test every possible combination of input data if the allowed range of input values is too huge. Choosing good test cases is brainwork, the idea is to use as few test cases as needed to test the largest possible number of failure cases.
For example, classify your input domain into categories:
And as a second classification
Now, make sure you have at least one test case for each category. Of course, this example shows only finding tests by using "equivalence classes". Other techniques are designing your tests for edge cases (like the ones in this answer), or tests for getting full code coverage and branch coverage.