Wednesday, July 19, 2017

Summer of code 2017: Python, Day 32 unit testing in Python with unittest


As explained in my Summer of code 2017: Python post I decided to pick up Python

This is officially day 32. today I decided to take a look at unit testing. I decided to look at the unittest unit testing framework that ships with Python

The unittest unit testing framework was originally inspired by JUnit and has a similar flavor as major unit testing frameworks in other languages. It supports test automation, sharing of setup and shutdown code for tests, aggregation of tests into collections, and independence of the tests from the reporting framework.

So let's see what it all looks like. First we are going to create a simple method and save it in a file named utils.py

This method will return what was passed in if it was none or a string, it will convert to utf-8 if bytes were passed in, for everything else a type error will be returned

def ToString(data):
    if isinstance(data, str):
        return data
    elif isinstance(data, bytes):
        return data.decode('utf-8')
    elif data is None:
         return data
    else:
        raise TypeError('Must supply string or bytes,'
                        ' found: %r' % data)

To unit test this method, we are creating our test class and we will save this in a file named UnitTestSample.py

Here is what it looks like

from unittest import TestCase, main
from utils import ToString
 
 
class UtilsTestCase(TestCase):
    def test_ToString_bytes(self):
        self.assertEqual('hello', ToString(b'hello'))
 
    def test_ToString_str(self):
        self.assertEqual('hello', ToString('hello'))
 
    def test_ToString_bad(self):
        self.assertRaises(TypeError, ToString, object())
 
    def test_ToString_none(self):
        self.assertIsNone(  ToString(None))
 
    def test_ToString_not_none(self):
        self.assertIsNotNone(  ToString('some val'))
 
if __name__ == '__main__':
   main()

We need to import unittest as well as our ToString method

As you can see, we have a couple of AssertEqual calls,  AssertEqual tests that first and second are equal. If the values do not compare equal, the test will fail.

We also have assertIsNone and assertIsNotNone, these test that something is none or is not none

Finally we use assertRaises. AssertRaises tests that an exception is raised when callable is called with any positional or keyword arguments that are also passed to assertRaises(). The test passes if exception is raised, is an error if another exception is raised, or fails if no exception is raised.

Running the code above will give us this output, all 5 tests have passed

Running C:\Python\Projects\UnitTestSample\UnitTestSample.py
.....
----------------------------------------------------------------------
Ran 5 tests in 0.000s

To print out the name of each test, we need to change main and pass in verbosity level 2

In the code main will now look like this

main(verbosity=2)

Running the same test class again will give us also the test methods that were called, here is the output

The interactive window has not yet started.
Running C:\Python\Projects\UnitTestSample\UnitTestSample.py
test_ToString_bad (__main__.UtilsTestCase) ... ok
test_ToString_bytes (__main__.UtilsTestCase) ... ok
test_ToString_none (__main__.UtilsTestCase) ... ok
test_ToString_not_none (__main__.UtilsTestCase) ... ok
test_ToString_str (__main__.UtilsTestCase) ... ok

----------------------------------------------------------------------
Ran 5 tests in 0.016s

OK
The interactive Python process has exited.
>>> 


That is all for this post, if you want to know more about unit testing with Python, I suggest you start here: https://docs.python.org/3/library/unittest.html#module-unittest

You might also want to look at Nose2, you can find that here: http://nose2.readthedocs.io/en/latest/

nose2 is the next generation of nicer testing for Python, based on the plugins branch of unittest2. nose2 aims to improve on nose by:

  • providing a better plugin api
  • being easier for users to configure
  • simplifying internal interfaces and processes
  • supporting Python 2 and 3 from the same codebase, without translation
  • encouraging greater community involvement in its development

No comments: