TDD: How would you work on this class, test-first style?

393 Views Asked by At

I am writing a small app to teach myself ASP.NET MVC, and one of its features is the ability to search for books at Amazon (or other sites) and add them to a "bookshelf".

So I created an interface called IBookSearch (with a method DoSearch), and an implementation AmazonSearch that looks like this

public class AmazonSearch : IBookSearch
{
   public IEnumerable<Book> DoSearch(string searchTerms)
   {  
      var amazonResults = GetAmazonResults(searchTerms);
      XNamespace ns = "http://webservices.amazon.com/AWSECommerceService/2005-10-05";
      var books= from item in amazonResults.Elements(ns + "Items").Elements(ns + "Item")
                 select new Book
                 {
                      ASIN = GetValue(ns, item, "ASIN"),
                      Title = GetValue(ns, item, "Title"),
                      Author = GetValue(ns, item, "Author"),
                      DetailURL = GetValue(ns, item, "DetailPageURL")
                 };
      return books.ToList();
  }

  private static XElement GetAmazonResults(string searchTerms)
  { 
      const string AWSKey = "MY AWS KEY";
      string encodedTerms = HttpUtility.UrlPathEncode(searchTerms);
      string url = string.Format("<AMAZONSEARCHURL>{0}{1}",AWSKey, encodedTerms);
      return XElement.Load(url);
  }

  private static string GetValue(XNamespace ns, XElement item, string elementName)
  {
     //Get values inside an XElement
  }

}

Ideally I would like to have done this TDD-style, writing first a test and all. But I gotta confess I am having trouble getting my head around it.

I could create a FakeSearch that implements DoSearch() and return some ad-hoc books, but I don't think that brings any value at the moment, does it? Maybe later when I have some code that uses a list of books.

What else could I test first? The only test I can think of would be one that mocks the call to the cloud (at GetAmazonResults) and then checks that DoSearch can execute the Linq2XML select correctly and return the correct list. But it seems to me that this type of test can only be written after I have some code in place so I know what to mock.

Any advice on how you guys and girls would go around doing this test-first style?

3

There are 3 best solutions below

7
On BEST ANSWER

It seems that your main issue here is knowing when to write mock code. I see your point: if you haven't written the code yet, how can you mock it?

I think the answer is that you want to start your TDD with very, very simple tests, as Kent Beck does in Test Driven Development. Start by writing a test that calls DoSearch and asserts that what you receive isn't null, and write some code to make that pass. Then write a test that asserts that you're retrieving the proper number of Books for a known search term, and write the code to make that pass. Eventually you'll get to a point where you need to receive actual, valid Book data to pass a test, and at that point, you'll have a portion of DoSearch written, and you can think about mocking it (or portions of it).

3
On

You'll want to write a mock when you're testing code that uses the search, not for testing the search itself.

For the class above, I might test by:

  • searching for a common book and checking that is was found and is valid.
  • searching for a random fixed string "kjfdskajfkldsajklfjafa" and making sure no books were found
  • etc

But.. and here's the big one, I'd never mock out a class I was testing, I'd mock out classes that it used.

Long story short: FakeSearch would be used when testing that the UI was working properly when the Search button was pressed. I could ensure that it was getting invoked, and that the UI was handling the returned books properly.

Hope that helps.

0
On

In this class the main focus appears to be that it integrates correctly with Amazon's web services. Since that web service is not something you own, you shouldn't mock it, because you don't have intimate knowledge of how it works. "Only mock types you own", "don't mock third-party libraries" etc.

Here are some ways to approach the problem:

Write a test which connects to the real web service over the network, perhaps searching for some very popular book which you can trust will be around for years to come. This gives good assurance that you are using the service correctly, but it's also subject to many false positives - for example sometimes the network might be down or then the data in the remote system changes. Thus you will also need tests which...

Write tests against static data files, which are based on data from the real web service. To get the test data, you could manually do requests to the web service and write the responses to file*. You will need to mock the network connection (either using a stub which does no networking, or by starting up an embedded web server in the tests and connecting to it instead of the real URL). This way you can easily test all kinds of corner cases and error conditions, and the data will always be available and stay the same, regardless of what happens to the real web service. One caveat is that if the API of the real web service changes, these tests will not notice it, so you will also need some tests written against the real web service (as mentioned above).

* For example, once I used cron and a little shell script to download every few minutes data from a web service, which contained ever changing schedule information. Gathering such data for a period of a few weeks was very useful as test data. From that data I hand-crafted static responses which contained all kinds of special cases which I noticed in the real data. It was also useful for setting up a fake web service and a "time machine" which replayed that earlier captured data, so that our system could be used without access to the real web service.