Test-Driven Development – How Do I Start?
Posted: July 22nd, 2010 | Author: Giv | Filed under: Python, Tutorials | 2 Comments »There seem to be a lot of developers who like the idea of Test-Driven Development (TDD) and can clearly see the benefit of having tests written for their code but can’t seem to get their head around the process. How do you start writing unit tests before writing the actual code?
Let’s start with an example. You want to write a method that takes a URL as an argument and have it tell you through a boolean return if it’s the correct domain or not. It seems simple enough. Just write your method, pass the URL through some regular expression and you’re done.
But you yourself already know which domains are allowed and which are not so before writing the actual code you can run some tests in your head. E.g. I only want www.bbc.co.uk and its sub-domains on http and https. Nothing else should be allowed. So https://beta.bbc.co.uk/iplayer should return TRUE and http://www.bbcbb.com should return FALSE etc.
The process behind TDD is that you first write a failing test. Then you write the actual code and adjust until the test passes.
So let’s write some tests for our domain checker. I’m using Python and Unittest here:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | import unittest # this is what we're going to be testing class Utils(): def is_bbc(self, val): pass #placeholder # this is the actual test class TestUtils(unittest.TestCase): def setUp(self): self.u = Utils() def test_is_bbc(self): self.assertTrue(self.u.is_bbc('http://www.bbc.co.uk/iplayer')) self.assertTrue(self.u.is_bbc('http://www.bbc.co.uk/food')) self.assertTrue(self.u.is_bbc('http://www.bbc.co.uk')) self.assertTrue(self.u.is_bbc('https://www.bbc.co.uk')) self.assertTrue(self.u.is_bbc('http://beta.bbc.co.uk')) self.assertFalse(self.u.is_bbc('http://www.bbc.com')) self.assertFalse(self.u.is_bbc('http://www.bbbc.co.uk')) self.assertFalse(self.u.is_bbc('http://.bbc.co.uk')) suite = unittest.TestLoader().loadTestsFromTestCase(TestUtils) unittest.TextTestRunner(verbosity=2).run(suite) |
I’ve created a an empty method where our domain checker is going to live but as you can see it doesn’t do anything. The tests should immediately make sense. We pass a bunch of domain variations and we know which ones should pass or fail. Naturally, running the test right now will fail:
$ python sample.py test_is_bbc (__main__.TestUtils) ... FAIL ====================================================================== FAIL: test_is_bbc (__main__.TestUtils) ---------------------------------------------------------------------- Traceback (most recent call last): File "sample.py", line 14, in test_is_bbc self.assertTrue(self.u.is_bbc('http://www.bbc.co.uk/iplayer')) AssertionError ---------------------------------------------------------------------- Ran 1 test in 0.000s FAILED (failures=1)
Now you can start writing the actual code and keep running the same tests until it passes:
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 | import unittest import re # this is what we're going to be testing class Utils(): def is_bbc(self, val): return re.match('^https?://([^/]+)?\.bbc\.co\.uk', val) # this is the actual test class TestUtils(unittest.TestCase): def setUp(self): self.u = Utils() def test_is_bbc(self): self.assertTrue(self.u.is_bbc('http://www.bbc.co.uk/iplayer')) self.assertTrue(self.u.is_bbc('http://www.bbc.co.uk/food')) self.assertTrue(self.u.is_bbc('http://www.bbc.co.uk')) self.assertTrue(self.u.is_bbc('https://www.bbc.co.uk')) self.assertTrue(self.u.is_bbc('http://beta.bbc.co.uk')) self.assertFalse(self.u.is_bbc('http://www.bbc.com')) self.assertFalse(self.u.is_bbc('http://www.bbbc.co.uk')) self.assertFalse(self.u.is_bbc('http://.bbc.co.uk')) #this should fail suite = unittest.TestLoader().loadTestsFromTestCase(TestUtils) unittest.TextTestRunner(verbosity=2).run(suite) |
The regex may look like it’s correct but running the test will fail again:
$ python sample.py test_is_bbc (__main__.TestUtils) ... FAIL ====================================================================== FAIL: test_is_bbc (__main__.TestUtils) ---------------------------------------------------------------------- Traceback (most recent call last): File "sample.py", line 22, in test_is_bbc self.assertFalse(self.u.is_bbc('http://.bbc.co.uk')) AssertionError ---------------------------------------------------------------------- Ran 1 test in 0.001s FAILED (failures=1)
It has failed on the final assert because our code will also allow http://.bbc.co.uk and we obviously don’t want that. But as you can see we’ve caught this edge case before deploying our app so we can promptly fix our code.
Hopefully this example demonstrates why it’s a good idea to start with tests. This is obviously a simple example but on bigger projects predicting the outcome of your system can save you a lot of debugging time in the future.
2 Comments »