HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Method injection into TestClass local namespace for automatic generation of Python unittest "test_xxx" methods

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
localmethodintounittestmethodsgenerationforpythoninjectionautomatic

Problem

I am writing a series of unit tests in Python 3.5 unittest, which run the exact same test methods on different datasets. The purpose is to validate proper behavior of each tested function over a range of inputs at different extremes of the likely numerical range of use (large values, small values, badly scaled values, etc.). I would like to construct the TestCase so that it dynamically generates all relevant test_xxx methods for the datasets I've entered. This way it leaves me less room for typos, and avoids the need to write a raft of new functions every time I add a dataset.

I first wrote the test code with all of my dataset dict objects housed in a simple list, and a single test_the_thing function within my unittest.TestCase subclass. test_the_thing would iterate over the list of dict datasets, running the test code on each. Two primary problems arose with this approach:

  • unittest considers the entire execution of test_the_thing to be a single test, and thus I have to search within my datasets to figure out which one failed when the test fails/errors.



  • When any given test fails/errors in the middle of the iteration, the remainder of the tests are not run.



What I've now got in its place is the following (the code for the entire class can be perused on GitHub):

```
class TestOpanUtilsVectorProjRejAngle(unittest.TestCase):
import numpy as np
from opan.const import OpanEnum

class DType(OpanEnum):
V1 = 'V1'
V2 = 'V2'
PROJ = 'PROJ'
REJ = 'REJ'
ANG = 'ANG'

class VecType(OpanEnum): # Types of vectors
O1 = 'O1' # Both order-one
LOL = 'LOL' # Both large (large on large)
SOS = 'SOS' # Both small (small on small)
LOS = 'LOS' # Large onto small
SOL = 'SOL' # Small onto large
BS = 'BS' # Badly-scaled

class RelType(OpanEnum): # Type of vector relationship
NS = 'NS' # Nonspecific
PAR = 'PAR' # Nearly parallel

Solution

I think your code is pretty good and you are following good practices with the exception to one (common mistake)

for name, datadict in data.items():
     fxnname = "test_data_{0}".format(name)
     fxn = lambda self, n=name, d=datadict: self.template_test_fxn(n, d)
     locals().update({fxnname: fxn})


Should be:

locals_ = locals()
for name, datadict in data.items():
     fxnname = "test_data_{0}".format(name)
     fxn = lambda self, n=name, d=datadict: self.template_test_fxn(n, d)
     locals_.update({fxnname: fxn})


This will prevent locals() from being evaluated when not necessary, but that's not even that big of a deal.

check out for another users use of locals https://stackoverflow.com/questions/8028708/dynamically-set-local-variable-in-python

i encourage other developers to review this, as it is a very interesting project.

Code Snippets

for name, datadict in data.items():
     fxnname = "test_data_{0}".format(name)
     fxn = lambda self, n=name, d=datadict: self.template_test_fxn(n, d)
     locals().update({fxnname: fxn})
locals_ = locals()
for name, datadict in data.items():
     fxnname = "test_data_{0}".format(name)
     fxn = lambda self, n=name, d=datadict: self.template_test_fxn(n, d)
     locals_.update({fxnname: fxn})

Context

StackExchange Code Review Q#126660, answer score: 2

Revisions (0)

No revisions yet.