Dark theme

Unit Testing


There are a wide variety of ways of testing software, from getting users to try different tasks; releasing public alpha and beta versions for community testing; to docttests. However, most of these assume the software is already written and we are testing for potential errors. Test-driven development turns this on its head, and allows the tests to drive software development, ensuring that at each stage, software is as close as possible to being able to complete its tasks.

The process centres on developing Unit Tests defining what successful software will pass, and then writing the code to pass the tests. The tests themselves are like doctests in Python, though generally more sophisticated frameworks are used. In Python there is a unittest library for building such more sophisticated tests. There is nothing to stop you writing software first and then the tests to check it, but that risks missing tests that are important and encouraging "feature creep" – where users keep demanding new features rather than concentrating on the core functionality. Writing the tests first ensures everyone is concentrating on what is core to the system. You can then write further tests/code when the core is built, knowing you can test whether it will break older code.


The test components of the framework look like this for a simple test (for docs.py):
# From docs.py
def add(num1, num2):
    return num1 + num2
-----------------------------------------
# From tests.py
import docs
def test_add(self):
    a = docs.Calc()
    self.assertEqual(a.add(1,2), 3)

Notice that the test is made up of an assertion: this checks components against an operator. In this case it checks that if we run docs.add() with the arguments 1 and 2, the result EQUALS 3. So far, so much like doctests. The difference is that there are a wider range of assertion types with Unit Tests, saving you from having to work out how to present the tests as EQUALS relationships. You can find a list in the library documentation.


The tests themselves are generally embedded in a separate class which can therefore be run independent of the tested code, and removed from the software before distribution. Here's an example (docs.py; tests.py):
import unittest
import docs

class TestDocs(unittest.TestCase):

    def test_add(self):
        a = docs.Calc()
        self.assertEqual(a.add(1,2), 3)

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

Note that the class runs within a bigger framework within the unittest library. We can tell this because a) no where in here is the TestDocs class turned into an object and run in any way, and b) we have a if __name__ == '__main__' statement. This is used to turn a module into a script that can run. Usually here we'd make TestDocs into an object and run functions inside it, but we don't, we call an unknown function unittest.main(); c) TestDocs inherents unittest.TestCase. Together this suggests that at some point unittest.main() will scan through the file looking for classes subclassing unittest.TestCase, and will then run through their functions, running them and letting the various assertion statements report the results.

You run the tests thus (download the files from above and try it):
python tests.py
however, that minimises the messages. You can run it in a more verbose fashion (though, to be honest you'll rarely notice the difference), thus: python -m unittest -v tests.py
In which case you can delete the if __name__ == '__main__' if-statement and clause, as unittest will search the file without it.

When you run the file, after the file has run, you should see something like this:
test_add (tests.TestDocs) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

So, here we've been testing a pre-existing class. Let's now think up a new one from a test-led perspective.