Tag: TDD

  • A Brief Rant about TDD

    A Brief Rant about TDD

    success ln / failure dr
    Credit: Flickr / StockMonkeys.com

    I’ve been giving this talk about unit testing UI code lately and of course when you talk about testing, TDD (test driven development) keeps coming up.

    The question ranges from “have you embraced TDD as the One True Way Of Testing, and if not why not because you’re doing it wrong” and “I have heard that TDD is the One True Way Of Testing, but I don’t unit test at all and I’m overwhelmed”.

    Note that neither of these are actually questions, but I digress.

    When it comes to my own workflow, I don’t do strict TDD (gasp!)

    • I don’t debug; I write tests.
    • When I have something that is inputs in -> outputs out I quite often write my tests first. In practise very little application code is like this.
    • Testing is largely an architectural problem, and I architect my code such that it is testable, usually writing my tests concurrently.
      • I don’t consider my code to be “done” until it is tested.
      • A lot of my testing involves testing UI code, e.g. I test that the right thing happens when a button is tapped. I guess technically I could stub out the view element and write tests for it, in practise I tend to complete my train of thought and write the view and then test it.

    Here’s my answer to the second none-question: I don’t believe in telling people to TDD. I believe in testing your code. That’s it. I’m happy to share my workflow, and techniques I use (buy my workshop!) but people should do what works for them.

    I do think though that TDD is something that you work up to (some point of), not what you start with. Because:

    • When you start writing tests you’ll probably find more bugs in your test code than your actual code. This kind of undermines the idea of the tests as the spec.
    • It’s super intimidating. You’re almost certainly going to discover that however you have been writing your code it’s not in a way that is testable. You’re going to need to refine your tests and your code together.
    • The idea that something has to be done “The Right Way” rather than “At All” is a huge impediment to starting. If you want to write more tests don’t wait until you have a new class that you can do TDD on, set aside an hour, choose something simple and get going.

    TL;DR: I don’t really TDD. I don’t care if you TDD. I do think everyone should test their code, though.

  • Test Driven Revision

    For my interview this week, I was sent a list of things to prep. One of them was sorting algorithms.

    The best way for me to remember and understand them is to code them, and I decided to go through the list on Wikipedia and code them in Java – even the recursive ones. I think recursively much better in Haskell but interviewers aren’t always cool when you ask to solve the problem in a non-standard language (I lucked out last time).

    Because I was writing a lot of similar code, I created an interface first, so that I could write parametrized test cases. These methods should really be static, but I compromised on that in order to make my testing easier.

     public interface Sort {
         public abstract void sort(int[] array);
     }

    This was pretty easy, and allowed me to write a parametrized test case that would run all my tests on anything implementing Sort. So I had some code that looked like this:

     private Sort sort;
    
     public TestSort(Sort sort) {
         this.sort = sort;
     }
    
     @Parameters
     public static Collection regExValues() {
         return Arrays.asList(new Object[][] {
             { new BubbleSort() },
             { new InsertionSort() },
             { new ShellSort() },
             { new MergeSort() },
             { new QuickSort() },
             { new CountingSort() } });
     }

    And as I created each new class I just added it to my list of parameters, and all my tests (labeled @Test) would be run on them as well. My tests were pretty repetitive and probably could have been parametrized as well, but because I was testing all these different classes I opted for slightly more code in the tests. (Full test code below)

    This approach really reduced my coding time, because once I’d set up the interface and the tests I could get a new class working very easily without using cut and paste. I also determined what I wanted to test: null, empty, one element, two elements sorted, two elements unsorted, odd number of elements, even number of elements, and longer with duplicates. When I added an extra test case part way through, it applied to all my previous classes. Determining the tests in advance was more effective than ad-hoc testing, for example, the odd number of elements test was helpful in catching an off-by-one bug.

    It also encouraged me to write better code. I might write the function, understand it, and just have a little bug – but I’d keep at it, because I wanted all the tests to pass before I moved on. I also had one bug that was resulting in an array with just two elements reversed – I didn’t notice that staring at the debug statement, it looked fine. But of course, my tests noticed! And the time when I wrote the algorithm right first time? That was pretty nice! Even if it was the super easy CountingSort.

    Sorted
    Credit: flickr / David Singleton

    Full test code is below if you have any use for it! Note – parameterized test cases are JUnit 4, and I’ve just used ints rather than a Generic <extends Comparable> approach – if you decide to do that, just change to Integer or anything else that implements Comparable.

    import java.util.Arrays;
    import java.util.Collection;
    
    import org.junit.Assert;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.junit.runners.Parameterized;
    import org.junit.runners.Parameterized.Parameters;
    
    @RunWith(Parameterized.class)
    public class TestSort {
    
    	private Sort sort;
    
    	public TestSort(Sort sort) {
    		this.sort = sort;
    	}
    
    	@Parameters
    	public static Collection regExValues() {
    		return Arrays.asList(new Object[][] {
    				{new BubbleSort()},
    				{new InsertionSort()},
    				{new ShellSort()},
    				{new MergeSort()},
    				{new QuickSort()},
    				{new CountingSort()}});
    	}
    
    	// null array
    	@Test(expected=IllegalArgumentException.class)
    	public void testNullArray() {
    		sort.sort(null);
    	}
    
    	// empty array
    	@Test
    	public void testEmptyArray() {
    		int[] array = new int[0];
    		sort.sort(array);
    		Assert.assertArrayEquals(new int[0], array);
    	}
    
    	// one element array
    	@Test
    	public void testOneElementArray() {
    		int[] array = {42};
    		int[] test = {42};
    		sort.sort(array);
    		Assert.assertArrayEquals(test, array);
    	}
    
    	// two element array ordered
    	@Test
    	public void testTwoElementOrdArray() {
    		int[] array = {7, 42};
    		int[] test = {7, 42};
    		sort.sort(array);
    		Assert.assertArrayEquals(test, array);
    	}
    
    	// two element array unordered
    	@Test
    	public void testTwoElementUnordArray() {
    		int[] array = {42, 7};
    		int[] test = {7, 42};
    		sort.sort(array);
    		Assert.assertArrayEquals(test, array);
    	}
    
    	// odd numbered element array
    	@Test
    	public void testOddNoElementsArray() {
    		int[] array = {42, 68, 9, 7, 100, 36, 27};
    		int[] test = {7, 9, 27, 36, 42, 68, 100};
    		sort.sort(array);
    		Assert.assertArrayEquals(test, array);
    	}
    
    	// even numbered element array
    	@Test
    	public void testEvenNoElementsArray() {
    		int[] array = {42, 68, 9, 7, 100, 36, 27, 99};
    		int[] test = {7, 9, 27, 36, 42, 68, 99, 100};
    		sort.sort(array);
    		Assert.assertArrayEquals(test, array);
    	}
    
    	// array in reverse order
    	@Test
    	public void testReverseOrder() {
    		int[] array = {100, 99, 68, 42, 36, 27, 9, 7};
    		int[] test = {7, 9, 27, 36, 42, 68, 99, 100};
    		sort.sort(array);
    		Assert.assertArrayEquals(test, array);
    	}
    
    	// longer array
    	@Test
    	public void testLongerArray() {
    		int[] array = {13, 14, 94, 33, 82, 25, 59, 94, 65, 23, 45, 27, 73, 25, 39, 10};
    		int[] test = {10, 13, 14, 23, 25, 25, 27, 33, 39, 45, 59, 65, 73, 82, 94, 94};
    		sort.sort(array);
    		Assert.assertArrayEquals(test, array);
    	}
    }