« .NET vs. Java? It's All About Choice | Main | sofake »

2005.09.09

Principles for Test-Driven Development

As I mentioned earlier in a post about TDD with Visual Studio, I recently watched some smart guys at MS preach TDD tips and techniques.  They had some great points so I thought I'd distill them into bullets for future reference.

1) Tests serve as examples of how to use a class or method.  Once used to having tests that show how things work (and that they work), developers start using the key phrase, "Do you have a test for that?"

2) Developer tests (they call them "coder tests") are distinctly different from QA test and should be kept separate.  QA tests target features and treat the system as a black box.  Unit test created by the developer operate at a lower level and test different things.

3) Name your tests carefully.  I'll expand on that and give some of my own guidelines here.

  • Name test packages like the package being tested with a suffix.  For example, the "DataAccess" project/package is tested by "DataAccess.Test".
  • Name test classes the same as the class under test with the suffix "Test".  For example, the class "PrintManager" is tested by the test class "PrintManagerTest".  This convention makes it easy to find the related class and keeps the class name a noun.
  • Name test methods the same as the method being tested with the prefix "Test".  For example, the method "PrintProductOrder()" is tested by the method "TestPrintProductOrder()".  This convention keeps the method name a verb that reads as and English phrase.  It's also compatible with NUnit's feature that assumes any method of a test class starting with the word Test is a test method.

4) Write each test before writing the method under test.  This ensures that you don't waste time writing code that isn't going to be tested and used.  It also encourages the developer to think as a user of the target method before thinking about implementation, which usually results in a cleaner, easier-to-use interface.

5) Follow the "3-As" pattern for test methods: Arrange, Act, Assert. Specifically, use separate code paragraphs (groups of lines of code separated by a blank line) for each of the As.  Arrange is variable declaration and initialization.  Act is invoking the code under test.  Assert is using the Assert.* methods to verify that expectations were met.  Following this pattern consistently makes it easy to revisit test code.

6) When writing application code, only write enough code to make a test work.  If you know there should be more code to handle other logic cases, go write the tests for those cases.  This technique prevents gold-plating and ensures that you always have a test for the code you write. 

7) When you find you need to refactor working code, refactor and re-test prior to writing new code.  This technique ensures your refactoring is correct prior to adding new functionality and applies to creating new methods, introducing inheritance, everything.  I equate this principle to the practice of running all tests in a solution after getting the latest code from the repository and prior to writing anything new.  I don't want to spend time debugging my newly written code under the false assumption that the system wasn't already broken.

I feel the urge to point out that in the webcast they are preaching test-driven development to the point of test-driven analysis & design.  While I know it's the XP way, I disagree the idea that you shouldn't implement the correct long-term design when you know it's appropriate.  In other words, analyzing your problem domain and producing a conceptual model prior to writing code is a good thing.  And, the major benefit of OOP is that your code should reflect the concepts in the problem domain.  So, when your TDD causes you to think, "I need a new class here", refer to your conceptual model and add the right class in the right structure.  Improper inheritance and other kludges along the way just increase the number of iterations and amount of refactoring required to get to your end-goal.  Iteration is good.  Hyper-iteration is a waste.

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/services/trackback/6a00d83451806669e200d8348bb7f969e2

Listed below are links to weblogs that reference Principles for Test-Driven Development:

Comments

Good advice, except for the method naming convention: A method can be used in different contexts, and the test code would become messy if we try to cram everything into one test method. I like to use my goals to name my test methods, like: PrintOrder PrintOrderWithInvalidInput LogInvalidPrintedOrder And what would you do if you rename the method? Would you look up all test methods and rename them too? void PrintOrderTest() { order.PrintOrder(); } oops, silly naming. I rename... void PrintOrderTest() { order.Print(); } Hmm. "PrintOrder" looks like a nice test name, but my method is now named "Print"
Yeah, I agree that matching the test name to the method being tested isn't *that* important. It's much more important that the name describe the test case. However, I do find myself lumping several tests together in a single test method just by taking several different actions and asserting after each. It could be bad form, but I do split them out if I get more than 3 or so and always split them for different topics. And, it's just a nit, but I do like the 'Test' prefix as opposed to suffix on the TestMethod. But in the end, it's far more important that people do unit testing at all than creating denominational religious wars over such conventions.
glad to see ms preaching tdd.
The reality is that if test driven development is not enforced, i.e. it is not governed by peer review, unit tests get left behind in the wake of tighter project deadlines. Tools to speed up TDD like Resharper etc., and also using TDD only on the complex parts of the system that pay off will help make the efforts in ensuring quality succeed I think. Granular tests are fine if you have the time, but you have to question testing CRUD methods where no constraints exist. That being said subtle constraints may be added over time and the test was left behind. Again design and code reviews can help mitigate this. A culture of wanting it to work is very much needed and must be bought into by project managers etc. when estimating iterations.

Verify your Comment

Previewing your Comment

This is only a preview. Your comment has not yet been posted.

Working...
Your comment could not be posted. Error type:
Your comment has been saved. Comments are moderated and will not appear until approved by the author. Post another comment

The letters and numbers you entered did not match the image. Please try again.

As a final step before posting your comment, enter the letters and numbers you see in the image below. This prevents automated programs from posting comments.

Having trouble reading this image? View an alternate.

Working...

Post a comment

Comments are moderated, and will not appear until the author has approved them.

November 2008

Sun Mon Tue Wed Thu Fri Sat
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30            
Blog powered by TypePad

We Like