tags:

views:

200

answers:

3

I'm trying to improve my TDD/OO skills and each time I try and use TDD to influence design I come up against a wall of where to start.

Here is my use case/story:

Identify a subset of clients that are due for a review. Start a review for them and send out a couple of letters.

Now my muscle memory has already opened up a query window written the query, designed a UI and then I have to write some code to glue the bits together.

I want the domain code to be the focus and I want it under test.

So what is the simplest thing to do in this case?

I guess I want my list of Clients. I already have a Client Object (CSLA-style) although this has a load of dependencies that are tough to break. I guess that I could have a ClientReviewClients object and test that I get the right number of reviews. There are a number of factors that I need to consider so it does not seem simple. And anyway how do I mock the fact that I have 10 reviews out of 20 clients?

Can anyone help me on my way?

+3  A: 

Here - I'll start you off with a couple of tests:

class IdentifyClientsDueForReview {
   public void CanStartSearch() {
      var s = new ClientSearcher();
   }

   public void CanSearchClients() {
      var s = new ClientSearcher();
      var r = s.Find(c => c.Id == 1);
      Assert.IsNotNull(r);
   }

   public void Finds10Clients() {
      var db = new MockDB();
      // Clients that need review
      for (int i = 0; i < 10; i++) {
         db.Add(new Client() { 
            NextReview = DateTime.Today.SubtractDays(i) 
         });
      }
      // Clients that don't need review
      for (int i = 0; i < 10; i++) {
         db.Add(new Client() { 
            NextReview = DateTime.Today.AddDays(i) 
         });
      }

      var s = new ClientSearcher(db);
      var r  = s.Find(c => c.NextReview <= DateTime.Today);
      Assert.AreEqual(10, r.Count);
   }
}

That's built with a Linq To Sql or similar backend ORM in mind - otherwise, you'd probably ditch the Find method and have some hardcoded FindBy<Criteria> methods.

This should give you the ClientSearcher class, which uses an interface to hit the database. Both MockDB and your RealDB class would implement that interface.

Mark Brackett
Excellent lots of stuff there. A class to search clients. I guess I would have put that in with a god like clients object.
John Nolan
What does CanStartSearch do there is no Assert in there?
John Nolan
@John - nothing really. Just gets you the first green bar by creating the class.
Mark Brackett
+1  A: 

Unit tests must be fast. If a test touches a database, it's an integration test (which are valuable too), not a unit test.

As for the number of clients who need a review, I wouldn't be particularly interested in knowing that I got 10 out of 20 needing a review but that for a particular client, am I correctly deciding whether that client needs a review based on my business rules?

You may find the two-part series "TDD/Using Mock objects with CSLA.Net" helpful:

You mentioned difficult dependencies, and I highly recommend Working Effectively with Legacy Code by Michael Feathers. The book is full of conservative dependency-breaking techniques useful for bringing code under test.

Greg Bacon
thanks for the link.
John Nolan
+1  A: 

I am not familiar with C#, so i can't help you with the mock question, i guess that depend on your testing framework.

I do do a lot of TDD though and generally my approach is a top down approach. I first think of simply the code I want to write to do something. Lets say in your example I have a class Client and want be able to do some thing like: Client.initiate_reviews

So I write a test that sets up the context (a couple of clients, some with reviews). Then call Client.initiate_reviews, and then write up all assertions to determine whether it has done its job, i.e. for the subset of clients that was due for a review, is there now a review in progress and are all the expected sent?

Depending on how many side effects a method has it might be prudent to split it up in multiple tests.

I then go into the client class and define the method and think of the code I would want to write in it. Maybe like this:

clients = Client.find_all_due_for_review
for_each client in clients {
    review = Review.start_new_for(client)
    Letter.send_for_review(review)
}

I would then write the tests for the methods called in this method that I have to implement. The find_all_due_for_review doesn't have side effects, but return something so of course you would test the return value here and maybe whether nothing has changed. And repeat until the first test succeeds.

This way every aspect is properly tested and you even end up with some methods you can reuse.

Hope this helps!

Arthur
so the difference between your approach and Mark Brackett's is that you use a Client object. Which is what I would do but doesn't this violate single responsibility principle? just our curiosity what language is that too?
John Nolan
Hmm, I guess that is true, but I always try to keep the model simple and just have the entities from the domain, so in this case Client and Review (and maybe letters somehow). And then fit in the functionality in the class it affects. Coming to think of it, i guess the initiate reviews method should then be in Review, which calls a method from Client to gather the clients with a review due.But having a separate class calling methods in both Client and Review can also be prudent from a separation of concerns p.o.v.Especially if there is more related functionality that can be put there.
Arthur
On the language: I'm a ruby programmer, but the code i wrote before is more pseudo code. In ruby it would be more like:Client.find_all_due_for_review.each do |client| Review.start_for(client)endAnd i would probably have the sending of letters as a side effect caused by starting the review, depending on the business rules behind it.
Arthur