Each test module can provide one or more methods that define a different test suite. One method can exercise all the tests in a given module; another method can define a particular subset.
With the following steps, we will create some methods that define test suites using different means:
recipe6.py
in which to put our code for this recipe.class RomanNumeralConverter(object): def __init__(self): self.digit_map = {"M":1000, "D":500, "C":100, "L":50, "X":10, "V":5, "I":1} def convert_to_decimal(self, roman_numeral): val = 0 for char in roman_numeral: val += self.digit_map[char] return val
Test
appended to the end.import unittest class RomanNumeralConverterTest(unittest.TestCase):
setUp
method that creates a new instance of the RomanNumeralConverter
for each test method.import unittest class RomanNumeralConverterTest(unittest.TestCase): def setUp(self): self.cvt = RomanNumeralConverter() def test_parsing_millenia(self): self.assertEquals(1000, self.cvt.convert_to_decimal("M")) def test_parsing_century(self): self.assertEquals(100, self.cvt.convert_to_decimal("C")) def test_parsing_half_century(self): self.assertEquals(50, self.cvt.convert_to_decimal("L")) def test_parsing_decade(self): self.assertEquals(10, self.cvt.convert_to_decimal("X")) def test_parsing_half_decade(self): self.assertEquals(5, self.cvt.convert_to_decimal("V")) def test_parsing_one(self): self.assertEquals(1, self.cvt.convert_to_decimal("I")) def test_empty_roman_numeral(self): self.assertTrue(self.cvt.convert_to_decimal("") == 0) self.assertFalse(self.cvt.convert_to_decimal("") > 0) def test_no_roman_numeral(self): self.assertRaises(TypeError, self.cvt.convert_to_decimal, None) def test_combo1(self): self.assertEquals(4000, self.cvt.convert_to_decimal("MMMM")) def test_combo2(self): self.assertEquals(2010, self.cvt.convert_to_decimal("MMX")) def test_combo3(self): self.assertEquals(4668, self.cvt.convert_to_decimal("MMMMDCLXVIII"))
def high_and_low(): suite = unittest.TestSuite() suite.addTest( RomanNumeralConverterTest("test_parsing_millenia")) suite.addTest( RomanNumeralConverterTest("test_parsing_one")) return suite def combos(): return unittest.TestSuite(map(RomanNumeralConverterTest, ["test_combo1", "test_combo2", "test_combo3"])) def all(): return unittest.TestLoader().loadTestsFromTestCase( RomanNumeralConverterTest)
TextTestRunner
.if __name__ == "__main__": for suite_func in [high_and_low, combos, all]: print "Running test suite '%s'" % suite_func.func_name suite = suite_func() unittest.TextTestRunner(verbosity=2).run(suite)
We pick a class to test and define a number of test methods that check things out. Then we define a few module-level methods such as, high_and_low
, combos
, and all
, to define test suites. Two of them contain fixed subsets of methods while all
dynamically loads the test*
methods from the class. Finally, the main part of our module iterates over a listing of all these functions that generate suites in order to smoothly create and run them.
All of our test suites were run from the recipe's main runner. But this probably wouldn't be the case for a real project. Instead, the idea is to define different suites, and code a mechanism to pick which suite to run. Each suite is geared towards a different purpose, and it is necessary to allow the developer to pick which suite to run. This can be done by coding a command-line script using Python's optparse
module to define command-line flags to pick one of these suites.
If we make these suite-defining methods members of the test
class, we would have to instantiate the test
class. Classes that extend unittest.TestCase
have a specialized init
method that doesn't work well with an instance that is created just to call a non-test method. That is why the methods are outside the test
class. While these methods can be in other modules, it is very convenient to define them inside the module containing the test code, to keep things in proximity.
What if we started our project off by running all tests? Sounds like a good idea, right? But what if the time to run the entire test suite grew to over an hour? There is a certain threshold after which developers tend to stop running tests, and nothing is worse than an un-run test suite. By defining subsets of tests, it is easy to run alternate suites during the day, and then perhaps run the comprehensive test suite once a day.
all
is the comprehensive suitehigh_and_low
is an example of testing the edgescombos
is a random sampling of values used to show that things are generally workingDefining our test suites is a judgment call. It's also worth it to re-evaluate each test suite every so often. If one test suite is getting too costly to run, consider moving some of its more expensive tests to another suite.
18.220.237.24