Art of Unittest writing : Auto-generation of unit test cases


How to auto create test cases with minimum coding 

Lets say , you want to run a series of test cases that undergo similar kind of testing  . but you need to generate different test cases for each comparison (or whatever test procedure you are using).

So imagine. We want to test a function. A function that accepts two variables, and an operator for comparison. Here's our test function:

    def _test_func(self, a, b, exp, operator):
        ans = 0
        exec("ans = %d %s %d" % (a, operator, b))
        self.assertEqual(ans, exp)


We want to test above function with various inputs....And...we want each scenario as a test case...You might need it for test report or something...


import unittest
from collections import namedtuple

class TestAuto(unittest.TestCase):
    def setUp(self):
        pass

    def tearDown(self):
        pass

# Adder function to add test case
def _add_test(cls, generator):
    for func, name, doc, a, b, exp, operator in generator():
        test = lambda self, a=a, b=b,  expected=exp, oper=operator, func=func : func(self, a, b, expected, oper)
        test.__name__ = "test_{}".format(name)
        test.__doc__ = doc
        setattr(cls, test.__name__, test)

def _test_generator_normal():
    """Generator function for testing queries"""        


    def _test_func(self, a, b, exp, operator):
        ans = 0
        exec("ans = %d %s %d" % (a, operator, b))
        self.assertEqual(ans, exp)

       
    tr = namedtuple('TestCase', 'name doc a b exp operator')
    test_list = (
        tr(name="sub", doc='Test for sub', a=10, b=20, exp=-10,  operator='-'),
        tr(name="add", doc='Test for add', a=30, b=50, exp=80,  operator='+'),
        tr(name="div", doc='Test for divide', a=10, b=2, exp=5, operator='/')
        )
    for ti in test_list:
        yield _test_func, ti.name, ti.doc, ti.a, ti.b, ti.exp, ti.operator

_add_test(TestAuto, _test_generator_normal)

if __name__ == '__main__':
    unittest.main(verbosity=2)


Results:


>>>
test_add (__main__.TestAuto)
Test for add ... ok
test_div (__main__.TestAuto)
Test for divide ... ok
test_sub (__main__.TestAuto)
Test for sub ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.010s

OK

Recently , we (me and my friend Sumant)came up with one more simpler model for the same purpose.


import unittest

def generator(test_class, a, b):
    def test(self):
        self.assertEqual(a, b)
    return test

def add_test_methods(test_class):
    #First element of list is variable "a", then variable "b", then name of test case that will be used as suffix.
    test_list = [[2,3, 'one'], [5,5, 'two'], [0,0, 'three']]
    for case in test_list:
        test = generator(test_class, case[0], case[1])
        setattr(test_class, "test_%s" % case[2], test)
        

class TestAuto(unittest.TestCase):
    def setUp(self):
        print 'Setup'
        pass

    def tearDown(self):
        print 'TearDown'
        pass

_add_test_methods(TestAuto)  # It's better to start with underscore so it is not detected as a test itself

if __name__ == '__main__':
    unittest.main(verbosity=1)


RESULT:



>>>
Setup
FTearDown
Setup
TearDown
.Setup
TearDown
.
======================================================================
FAIL: test_one (__main__.TestAuto)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:/inchowar/Desktop/PyTrash/test_auto_3.py", line 5, in test
    self.assertEqual(a, b)
AssertionError: 2 != 3

----------------------------------------------------------------------
Ran 3 tests in 0.019s

FAILED (failures=1)

In case , you want to be extra cautious, you can use the decorator @nottest to indicate that the function is not a test method.

"from nose.tools import notttest"

Total Pageviews