Better Software - July/August 2008 - (Page 17) Code Craft [Test] public void Test() { RecentlyUsedList list = new RecentlyUsedList(); Assert.AreEqual(0, list.Count); list.Add(“Aardvark”); Assert.AreEqual(1, list.Count); Assert.AreEqual(“Aardvark”, list[0]); list.Add(“Zebra”); list.Add(“Mongoose”); Assert.AreEqual(3, list.Count); Assert.AreEqual(“Mongoose”, list[0]); Assert.AreEqual(“Zebra”, list[1]); Assert.AreEqual(“Aardvark”, list[2]); list.Add(“Aardvark”); Assert.AreEqual(3, list.Count); Assert.AreEqual(“Aardvark”, list[0]); Assert.AreEqual(“Mongoose”, list[1]); Assert.AreEqual(“Zebra”, list[2]); bool thrown; try { string unreachable = list[3]; thrown = false; } catch(ArgumentOutOfRangeException) { thrown = true; } Assert.IsTrue(thrown); } Listing 2 The difficulty in understanding what they are testing can greatly reduce the velocity at which a codebase can be changed. This consideration is perhaps more explicit and more obviously enabled with TDD, but the sense and sensibility of treating tests as specs are also relevant when using other unittesting approaches. Style and Substance Consider a simple example: a recently used list, which is a collection that holds strings uniquely and in reverse order of their insertion. The C# code in listing 1 shows the key features for using such a class: a default constructor, an Add method, a Count property to query size, and an indexer for subscripting. So what style should our tests adopt in order to communicate functionality to the reader? The NUnit test case in listing 2 certainly exercises the class, but lumping all test logic into an undifferentiated, monolithic slab called Test has little communication value. It can be justified for code with simple behaviors, but it does not scale well to larger tests. Dividing up the single test method into a number of arbitrary test methods—For example, Test1, Test2, and [Test] public void Constructor() { RecentlyUsedList list = new RecentlyUsedList(); Assert.AreEqual(0, list.Count); } [Test] public void Add() { RecentlyUsedList list = new RecentUsedList(); list.Add(“Aardvark”); Assert.AreEqual(1, list.Count); list.Add(“Zebra”); list.Add(“Mongoose”); Assert.AreEqual(3, list.Count); list.Add(“Aardvark”); Assert.AreEqual(3, list.Count); } [Test] public void Indexer() { RecentlyUsedList list = new RecentUsedList(); list.Add(“Aardvark”); list.Add(“Zebra”); list.Add(“Mongoose”); Assert.AreEqual(“Mongoose”, list[0]); Assert.AreEqual(“Zebra”, list[1]); Assert.AreEqual(“Aardvark”, list[2]); list.Add(“Aardvark”); Assert.AreEqual(“Aardvark”, list[0]); Assert.AreEqual(“Mongoose”, list[1]); Assert.AreEqual(“Zebra”, list[2]); bool thrown; try { string unreachable = list[3]; thrown = false; } catch (ArgumentOutOfRangeException) { thrown = true; } Assert.IsTrue(thrown); } Listing 3 Test3—does not really address the issue. Such a division and labeling is, well, arbitrary, so it does not communicate anything useful to the reader. Programmers who have moved beyond the monolithic and arbitrary styles tend to gravitate toward a procedural style. A procedural style can be characterized in terms of “I have a procedure foo, therefore I have a corresponding test procedure that tests foo.” Listing 3 shows procedural unit tests for our RecentlyUsedList class, each test method aligned with a method in the class under test. BETTER SOFTWARE www.StickyMinds.com JULY/AUGUST 2008 17 http://www.StickyMinds.com
For optimal viewing of this digital publication, please enable JavaScript and then refresh the page. If you would like to try to load the digital publication without using Flash Player detection, please click here.