Re: Iterating over test data in unit tests
Ben Finney wrote: Ben Finney [EMAIL PROTECTED] wrote: Summary: I'm looking for idioms in unit tests for factoring out repetitive iteration over test data. Thanks to those who've offered suggestions, especially those who suggested I look at generator functions. This leads to:: Here's another way (each test should independently test one feature): class Test_Game(unittest.TestCase): Test case for the Game class score = 0 throws = [] frame = 1 def setUp(self): Set up test fixtures self.game = bowling.Game() def test_score_throws(self): Game score should be calculated from throws for throw in self.throws: self.game.add_throw(throw) self.assertEqual(self.score, self.game.get_score()) def test_current_frame(self): Current frame should be as expected frame = dataset['frame'] for throw in self.throws: self.game.add_throw(throw) self.assertEqual(self.frame, self.game.current_frame) class Test_one(Test_Game): score = 5 throws = [5] frame = 1 class Test_two(Test_Game): score = 9 throws = [5, 4] frame = 2 class Test_three(Test_Game): score = 14 throws = [5, 4, 5] frame = 2 class Test_strike(Test_Game): score = 26 throws = [10, 4, 5, 7] frame = 3 --Scott David Daniels [EMAIL PROTECTED] -- http://mail.python.org/mailman/listinfo/python-list
Iterating over test data in unit tests
Howdy all, Summary: I'm looking for idioms in unit tests for factoring out repetitive iteration over test data. I explain my current practice, and why it's unsatisfactory. When following test-driven development, writing tests and then coding to satisfy them, I'll start with some of the simple tests for a class. import unittest import bowling # Module to be tested class Test_Frame(unittest.TestCase): def test_instantiate(self): Frame instance should be created instance = bowling.Frame() self.failUnless(instance) class Test_Game(unittest.TestCase): def test_instantiate(self): Game instance should be created instance = bowling.Game() self.failUnless(instance) As I add tests for more interesting functionality, they become more data dependent. class Test_Game(unittest.TestCase): # ... def test_one_throw(self): Single throw should result in expected score game = bowling.Game() throw = 5 game.add_throw(throw) self.failUnlessEqual(throw, game.get_score()) def test_three_throws(self): Three throws should result in expected score game = bowling.Game() throws = (5, 7, 4) game.add_throw(throws[0]) game.add_throw(throws[1]) game.add_throw(throws[2]) self.failUnlessEqual(sum(throws), game.get_score()) This cries out, of course, for a test fixture to set up instances. class Test_Game(unittest.TestCase): def setUp(self): Set up test fixtures self.game = bowling.Game() def test_one_throw(self): Single throw should result in expected score throw = 5 score = 5 self.game.add_throw(throw) self.failUnlessEqual(score, game.get_score()) def test_three_throws(self): Three throws should result in expected score throws = [5, 7, 4] score = sum(throws) for throw in throws: game.add_throw(throw) self.failUnlessEqual(score, game.get_score()) def test_strike(self): Strike should add the following two throws throws = [10, 7, 4, 7] score = 39 for throw in throws: game.add_throw(throw) self.failUnlessEqual(score, game.get_score()) So far, this is just following what I see to be common practice for setting up *instances* to test. But the repetition of the test *inputs* also cries out to me to be refactored. I see less commonality in doing this. My initial instinct is just to put it in the fixtures. class Test_Game(unittest.TestCase): def setUp(self): Set up test fixtures self.game = bowling.Game() self.game_data = { 'one': dict(score=5, throws=[5]), 'three': dict(score=17, throws=[5, 7, 5]), 'strike': dict(score=39, throws=[10, 7, 5, 7]), } def test_one_throw(self): Single throw should result in expected score throws = self.game_data['one']['throws'] score = self.game_data['one']['score'] for throw in throws: self.game.add_throw(throw) self.failUnlessEqual(score, game.get_score()) def test_three_throws(self): Three throws should result in expected score throws = self.game_data['three']['throws'] score = self.game_data['three']['score'] for throw in throws: game.add_throw(throw) self.failUnlessEqual(score, game.get_score()) def test_strike(self): Strike should add the following two throws throws = self.game_data['strike']['throws'] score = self.game_data['strike']['score'] for throw in throws: game.add_throw(throw) self.failUnlessEqual(score, game.get_score()) But this now means that the test functions are almost identical, except for choosing one data set or another. Maybe that means I need to have a single test: def test_score_throws(self): Game score should be calculated from throws for dataset in self.game_data: score = dataset['score'] for throw in dataset['throws']: self.game.add_throw(throw) self.failUnlessEqual(score, self.game.get_score()) Whoops, now I'm re-using a fixture instance. Maybe I need an instance of the class for each test case. def setUp(self): Set up test fixtures self.game_data = { 'one': dict(score=5, throws=[5]), 'three': dict(score=17, throws=[5, 7, 5]), 'strike': dict(score=39,
Re: Iterating over test data in unit tests
Ben Finney wrote: Summary: I'm looking for idioms in unit tests for factoring out repetitive iteration over test data How about something like: import unittest, bowling class Test_Game(unittest.TestCase): def setUp(self): Set up test fixtures self.game = bowling.Game() def runs(self, throws): Run a series of scores and return the result for throw in throws: self.game.add_throw(throw) return self.game.get_score() def test_one_throw(self): Single throw should result in expected score self.assertEqual(5, self.runs([5])) def test_three_throws(self): Three throws should result in expected score self.assertEqual(5 + 7 + 4, self.runs([5, 7, 4])) def test_strike(self): Strike should add the following two throws self.assertEqual(39, self.runs([10, 7, 4, 7])) There is no reason you cannot write support functions. -- -Scott David Daniels [EMAIL PROTECTED] -- http://mail.python.org/mailman/listinfo/python-list
Re: Iterating over test data in unit tests
Scott David Daniels [EMAIL PROTECTED] wrote: Ben Finney wrote: Summary: I'm looking for idioms in unit tests for factoring out repetitive iteration over test data How about something like: class Test_Game(unittest.TestCase): [...] def runs(self, throws): Run a series of scores and return the result [...] def test_one_throw(self): Single throw should result in expected score self.assertEqual(5, self.runs([5])) def test_three_throws(self): Three throws should result in expected score self.assertEqual(5 + 7 + 4, self.runs([5, 7, 4])) def test_strike(self): Strike should add the following two throws self.assertEqual(39, self.runs([10, 7, 4, 7])) Yes, I'm quite happy that I can factor out iteration *within* a single data set. That leaves a whole lot of test cases identical except for the data they use. The question remains: how can I factor out iteration of *separate test cases*, where the test cases are differentiated only by the data they use? I know at least one way: I wrote about it in my (long) original post. How else can I do it, with less ugliness? -- \ I went to a garage sale. 'How much for the garage?' 'It's not | `\ for sale.' -- Steven Wright | _o__) | Ben Finney -- http://mail.python.org/mailman/listinfo/python-list
Re: Iterating over test data in unit tests
On Tue, 6 Dec 2005 12:19:40 +1100 (EST), Ben Finney [EMAIL PROTECTED] wrote: Howdy all, Summary: I'm looking for idioms in unit tests for factoring out repetitive iteration over test data. I explain my current practice, and why it's unsatisfactory. Does this do what you want? http://codespeak.net/py/current/doc/test.html#generative-tests-yielding-more-tests Regards, Bengt Richter -- http://mail.python.org/mailman/listinfo/python-list
Re: Iterating over test data in unit tests
Ben Finney wrote: Maybe I need to factor out the iteration into a generic iteration function, taking the actual test as a function object. That way, the dataset iterator doesn't need to know about the test function, and vice versa. def iterate_test(self, test_func, test_params=None): Iterate a test function for all the sets if not test_params: test_params = self.game_params for key, params in test_params.items(): dataset = params['dataset'] instance = params['instance'] test_func(key, dataset, instance) def test_score_throws(self): Game score should be calculated from throws def test_func(key, dataset, instance): score = dataset['score'] for throw in dataset['throws']: instance.add_throw(throw) self.failUnlessEqual(score, instance.get_score()) self.iterate_test(test_func) That's somewhat clearer; the test function actually focuses on what it's testing. Those layers of indirection are annoying, but they allow the data sets to grow without writing more code to handle them. Don't know if this helps, but I'd be more likely to write this as something like (untested):: def get_tests(self, test_params=None): Iterate a test function for all the sets if not test_params: test_params = self.game_params for key, params in test_params.items(): dataset = params['dataset'] instance = params['instance'] yield key, dataset, instance def test_score_throws(self): Game score should be calculated from throws for key, dataset, instance in self.get_tests() score = dataset['score'] for throw in dataset['throws']: instance.add_throw(throw) self.failUnlessEqual(score, instance.get_score()) That is, make an interator to the various test information, and just put your test_func code inside a for-loop. STeVe -- http://mail.python.org/mailman/listinfo/python-list
Re: Iterating over test data in unit tests
Ben Finney [EMAIL PROTECTED] wrote: Summary: I'm looking for idioms in unit tests for factoring out repetitive iteration over test data. Thanks to those who've offered suggestions, especially those who suggested I look at generator functions. This leads to:: import unittest import bowling # Module to be tested class Test_Game(unittest.TestCase): Test case for the Game class def setUp(self): Set up test fixtures self.game_data = { 'none': dict(score=0, throws=[], frame=1), 'one': dict(score=5, throws=[5], frame=1), 'two': dict(score=9, throws=[5, 4], frame=2), 'three': dict(score=14, throws=[5, 4, 5], frame=2), 'strike': dict(score=26, throws=[10, 4, 5, 7], frame=3), } self.game_params = {} for key, dataset in self.game_data.items(): params = {} instance = bowling.Game() params['instance'] = instance params['dataset'] = dataset self.game_params[key] = params def iterate_params(test_params=None): Yield the test parameters if not test_params: test_params = self.game_params for key, params in test_params.items(): dataset = params['dataset'] instance = params['instance'] yield key, dataset, instance def test_score_throws(self): Game score should be calculated from throws for key, dataset, instance in self.iterate_params(): score = dataset['score'] for throw in dataset['throws']: instance.add_throw(throw) self.failUnlessEqual(score, instance.get_score()) def test_current_frame(self): Current frame should be as expected for key, dataset, instance in self.iterate_params(): frame = dataset['frame'] for throw in dataset['throws']: instance.add_throw(throw) self.failUnlessEqual(frame, instance.current_frame) That's much better. Each test is now clearly about looping through the datasets, but the infrastructure to do so is factored out. Adding a test case modelled on the existing cases just means adding a new entry to the game_data dictionary. Setting up a different kind of test -- e.g. for invalid game data -- just means setting up a new params dictionary and feeding that to the same generator function. I like it. Can it be improved? Are there readability problems that can be fixed? Is the test fixture setup too complex? Should the iterator become even more general, and be refactored out to a test framework for the project? -- \ Those who can make you believe absurdities can make you commit | `\ atrocities. -- Voltaire | _o__) | Ben Finney -- http://mail.python.org/mailman/listinfo/python-list