0

Given a little hierarchy of classes with some multi-inheritances and/or mixins (however you may call it).

I have a detailed test case (unittest.TestCase) for each individual base class and mixins.

How can I reuse those test cases for classes derived from the Base classes and mixins? I feel, that testing/asserting the derived class inherits from certain base classes isn't sufficient to ensure that it inherits the specific behavior of the base class(es).

I'd like to write something like:

class Base(object): # provides certain behavior class Derived(Base, Mixin): # does some additional stuff compared to Base and Mixin class BaseTest(unittest.TestCase): # tests on Base's behavior class DerivedTest(unittest.TestCase): def setUp(self): self.default = Derived() def test_has_behavior_of_base(self): # instead of self.assertIsInstance(self.default, Base) self.assertIsInstance(self.default, Mixin) # write self.assertPasses(BaseTest, Derived) 

Is there a pythonic way of achieving this with Pythons unittest module and a maybe a little help of the nose package?

2 Answers 2

1

In general, since Python is a duck-typed language, it's best to minimize the checking for explicit types/classes (at least if you're going for a more Pythonic approach).

Since duck-typing relies primarily on behavior, and nose offers auto-detection of tests if you invoke it using a directory and name your test classes and methods accordingly (i.e. containing the word test, among others), I would recommend the following:

Define one test class for your base class, testing all of the behaviors of the base class. Factor out the common code into a non-test method, say baseClassCore, which calls the actual methods, gets the results, and then returns them. Then set up one test for each derived class which calls the baseClassCore code and asserts the expected results (if the assertion code is also common to all of them, that can also be factored out and then called from the test method).

Then, for the specific derived classes, create a test class for each of those which test just the behaviors unique to those classes.

You could, alternatively, call baseClassCore in a test for that specific type in the derived class test instead of the base class test -- it's a matter of preference.

Once these are all defined (and many of them will simply be wrappers around the core code so they get split out as separate tests in the JUnit results and are passed the specific types desired), running nose against the whole directory should detect and run them all. If you run with the -v flag, you'll get the method name as it runs, or, if it exists, the docstring underneath the method signature instead of just status letters.

Note that you don't need to use unittest explicitly. If you derive your test classes from unittest.TestCase, that's sufficient for nose to get that JUnit-compatible behavior.

As an aside, if you have any config that's needed (cutoff dates, servers to use, etc.) nose-testconfig is very convenient (https://pypi.python.org/pypi/nose-testconfig). I usually use it with a JSON file, which is then fed into a dict named config.

Sign up to request clarification or add additional context in comments.

Comments

0

Here's a filled out example that runs. Square is derived from Rectangle. If we derive TestSquare from TestRectangle, where TestRectangle is derived from unittest.TestCase, then all the test methods from TestRectangle will also be run from TestSquare.

import unittest class Rectangle(object): def __init__(self, width, height): self._width = width self._height = height def get_area(self): return self.get_width() * self.get_height() def get_width(self): return self._width def get_height(self): return self._height def set_width(self, width): self._width = width def set_height(self, height): self._height = height class Square(Rectangle): def __init__(self, side): self._side = side def get_width(self): return self._side def set_width(self, width): self._side = width def get_height(self): return self._side def set_height(self, height): self._side = height class TestRectangle(unittest.TestCase): def setUp(self): self.default = Rectangle(2, 3) def test_area(self): self.assertEqual( self.default.get_area(), self.default.get_width() * self.default.get_height()) def test_set_get(self): self.default.set_width(4) self.default.set_height(5) self.assertEqual(self.default.get_width(), 4) self.assertEqual(self.default.get_height(), 5) class TestSquare(TestRectangle): def setUp(self): self.default = Square(3) 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.