EXAMPLE
We must now write our first failing test before we can code.
Test 1: An empty list should have a size of zero
import unittest from movie.Movie import *
class TestMovieList(unittest.TestCase):
def testZeroLengthMovieList(self): movieList = MovieList() self.assertEquals(movieList.getSize(),0)
if__name == '__main__': unittest.main()
The test fails with:
=============================================== ERROR: testZeroLengthMovieList (__main__.TestMovieList) ---------------------------------------------------------------------- Traceback (most recent call last): File "c:\python22\lib\movie\TestMovie.py", line 7, in testZeroLengthMovieList movieList = MovieList() NameError: global name 'MovieList' is not defined ---------------------------------------------------------------------- Ran 1 tests in 0.000s
FAILED (errors=1)
Now we will do the simplest thing that will give us a passing test. We will stub out size in the movie object to return zero.
class MovieList:
def getSize(self): return 0
Green barthe test passes with:
---------------------------------------------------------------------- Ran 1 tests in 0.000s
OK
Test 2: Adding a movie to an empty list should result in a list with a size of one
It is now our responsibility to expand and test for other behaviors of the MovieList object. We have also refactored the test to remove redundancy of creating an instance of MovieList and cleaning up the setUp() and tearDown() methods of the test object.
class TestMovieList(unittest.TestCase):
def setUp(self): self.movieList = MovieList()
def testZeroLengthMovieList(self): self.assertEquals(self.movieList.getSize(),0)
def testAddMovie(self): self.movieList.addMovie(Movie('Star Wars')) self.assertEquals(self.movieList.getSize(),1)
def tearDown(self): self.movieList=None
Red barthe test fails with:
========================================= ERROR: testAddMovie (__main__.TestMovieList) ---------------------------------------------------------------------- Traceback (most recent call last): File "c:\python22\lib\movie\TestMovie.py", line 11, in testAddMovie self.movieList.addMovie(Movie('Star Wars')) AttributeError: MovieList instance has no attribute 'addMovie' ---------------------------------------------------------------------- Ran 2 tests in 0.000s
FAILED (errors=1)
Stub out the addMovie(movie) method with a pass instruction. This method returns None when it is called and does nothing.
class MovieList:
def getSize(self): return 0
def addMovie(self, movie): pass
Red bar Movie object is not defined; we must stub out a Movie class. Before we do this we need to add a testMovie class to test the behavior of a Movie and stub out Movie. This class only needs to retain its name for now, so that is the only behavior we will test for.
Add test class:
class testMovie(unittest.TestCase):
def testMovie(self): movie=Movie('Star Wars') self.assertEquals(movie.name, 'Star Wars')
Create the code for the Movie class, a simple class with only a constructor method.
class Movie: def __init__(self, name=None): self.name = name
Red bar MovieList is still returning 0 for all getSize() requests:
=========================================== FAIL: testAddMovie (__main__.TestMovieList) ---------------------------------------------------------------------- Traceback (most recent call last): File "c:\python22\lib\movie\TestMovie.py", line 12, in testAddMovie self.assertEquals(self.movieList.getSize(),1) File "C:\Python22\Lib\unittest.py", line 273, in failUnlessEqual raise self.failureException, (msg or '%s != %s' % (first, second)) AssertionError: 0 != 1 ---------------------------------------------------------------------- Ran 3 tests in 0.000s
FAILED (failures=1)
Modify MovieList to track the size. Yes, we know this is a stupid way to do it but we also know that first we will make it work and then we will make it elegant.
class MovieList:
def __init__(self): self.size=0
def getSize(self): return self.size
def addMovie(self, movie): self.size += 1
Green bar All three of our tests are running at 100 percent. We could now integrate our code with the main codebase depending on our check-in strategy. Now let's add tests for the functionality to see if a movie has already been added to the MovieList.
Test 3: If we add a movie to a list, we should be able to ask if it's there and receive a positive response
class TestMovieList(unittest.TestCase):
def setUp(self): self.movieList = MovieList()
def testZeroLengthMovieList(self): self.assertEquals(self.movieList.getSize(),0)
def testAddMovie(self): self.movieList.addMovie(Movie('Star Wars')) self.assertEquals(self.movieList.getSize(),1)
def testContainsMovie(self): self.movieList.addMovie(Movie('Star Wars')) self.failUnless(self.movieList.containsMovie('Star Wars'))
def tearDown(self): self.movieList=None
Red bar Stub out the functionality in MovieList by simply returning True when the containsMovie(name) method is called.
class MovieList:
def __init __(self): self.size=0
def getSize(self): return self.size
def addMovie(self, movie): self.size += 1
def containsMovie(self, name): return True
Green bar All four of our tests pass. Refactor test objects to be a bit more modular. Notice that we pulled the test for zero length list into another test object and created and then added "Star Wars" to the list of movies. The methods setUp() and teardown() are called for each of the test method invocations in that class so we do not have to worry about test crosstalk in the object.
class TestZeroLengthMovieList(unittest.TestCase):
def testZeroLengthMovieList(self): movieList=MovieList() self.assertEquals(movieList.getSize(),0)
class TestMovieList(unittest.TestCase):
def setUp(self): self.movieList = MovieList() self.movieList.addMovie(Movie('Star Wars'))
def testOneMovieList(self): self.assertEquals(self.movieList.getSize(),1)
def testContainsMovie(self): self.failUnless(self.movieList.containsMovie('Star Wars'))
def tearDown(self): self.movieList=None
Green bar Now we must extend the test to express the true intent of the contains- Movie(name) method by testing to ensure that we get a false if the movie has not been added.
Test 4: Asking about the presence of a movie that wasn't added should result in a negative response
class TestMovieList(unittest.TestCase):
def setUp(self): self.movieList = MovieList() self.movieList.addMovie(Movie('Star Wars'))
def testOneMovieList(self): self.assertEquals(self.movieList.getSize(),1)
def testContainsMovie(self): self.failUnless(self.movieList.containsMovie('Star Wars'))
def testNotContainsMovie(self): self.failIf(self.movieList.containsMovie('Star Trek'))
def tearDown(self): self.movieList=None
Red bar Extend MovieList to check if a movie has been added:
class MovieList:
def __init__(self): self.size=0 self.movies=[]
def getSize(self): return self.size
def addMovie(self, movie): self.size += 1 self.movies.append(movie)
def containsMovie(self, name): for movie in self.movies: if movie.name == name: return True return False
Green bar All tests pass. We are done. Or are we? We still must refactor to make the implementation of our objects as clear as possible and remove any redundancy. Our MovieList class is using a counting scheme rather than checking the length of our internal movie list.
class MovieList:
def __init__(self): self.movies=[]
def getSize(self): return len(self.movies)
def addMovie(self, movie): self.movies.append(movie)
def containsMovie(self, name): for movie in self.movies: if movie.name == name: return True return False
Green bar Done. Let's be a bit more thorough and expand our tests to check if multiple movies in the list bother us.
class TestMovieList(unittest.TestCase):
def setUp(self): self.movieList = MovieList() self.movieList.addMovie(Movie('Star Wars'))
def testOneMovieList(self): self.assertEquals(self.movieList.getSize(),1)
def testContainsMovie(self): self.failUnless(self.movieList.containsMovie('Star Wars'))
def testNotContainsMovie(self): self.failIf(self.movieList.containsMovie('Star Trek'))
def testMultipleMoviesInList(self): self.assertEquals(self.movieList.getSize(),1) self.movieList.addMovie(Movie('Un Chien Andalou')) self.assertEquals(self.movieList.getSize(),2) self.movieList.addMovie(Movie('Run Lola Run')) self.assertEquals(self.movieList.getSize(),3) self.failUnless(self.movieList.containsMovie('Un Chien Andalou')) self.failIf(self.movieList.containsMovie('Star Trek'))
def tearDown(self): self.movieList=None
Green bar This last test is somewhat like an acceptance test since it tests multiple scenarios. Many people would argue against this level of redundancy, but for my money I believe that tests should be fragile and that some level of acceptance test should be expressed while unit-testing the application. The redundant asserts could certainly be factored out into private methods if one wishes.
|
No comments:
Post a Comment