Wednesday, September 4, 2013

Run Unit Test automatically for Python code

Have you ever thought how often you write the tests for the newly developed code? I believe that more likely -- every time. Even a tiny tool which will never leave the developer's sandbox, which is supposed to show that the implemented logic is correct might be called a "test"; it is not perfect, not maintainable, but it is intended to do at least one thing -- to check the code. I did so. Currently I prefer to write unit tests rather than such tiny tools; for the new as well as for the legacy code. If there is a file with the implementation -- there should be a corresponding file with the tests.

I am still Emacs user but sometimes for reasons unknown I use Sublime Text to create something using Python. So currently it is become boring a bit to:
  1. Introduce the change in a file with a SUT;
  2. Switch to the next tab in Sublime Text with a source code of the test;
  3. Press a hot key to run the test to check the fixes.
I would prefer to replace these steps with the one. Here goes a simple straightforward solution to run unit tests for Python code in Sublime Text automatically; these tips are also applicable to other editors.

The test we have implemented for our code might look like:

import unittest
import hello as SUT

class TestHello(unittest.TestCase):
 def test_returnsHelloGretting(self):
  self.assertEqual("hello", SUT.greet())

The code we work on:
def greet():
 return "hello"

Way #0: Unit Tests inside


The most obvious way is to keep the SUT and the tests in the same file. In short I do not like this because of possible problems with the further maintainability.

Way #1: Native/Python


If we want to reduce the amount of switches between the tabs in the editor we need to make the SUT run the associated unit tests when the SUT is run as main python source. This could be done by introducing an intermediate helper layer to run the tests. Call it test.py:

import unittest
import inspect
import os

def main(**kwargs):
 ### stack[0] contains the frame information about the current function, since it was called first
 ### stack[1] contains the frame information about the caller
 callerFullPath = inspect.stack()[1][1]
 callerFileName = os.path.basename(callerFullPath)
 associatedTestFileName = "test_{0}".format(callerFileName)
 associatedTest = os.path.splitext(associatedTestFileName)[0]

 return unittest.main(associatedTest, verbosity = 2, **kwargs)

The function main() simply does the following:
  1. Gets the file name of the SUT it was called from;
  2. Composes a name of the file where the unit test for SUT is located in;
  3. Redirects the call to the real test runner -- unittest.main()

... and slightly update contents of the file with the SUT:

def greet():
 return "hello"

if "__main__" == __name__:
 import test
 test.main()

Since now the each press of Ctrl-B (default hot key to run the build in Sublime Text) will lead to the run of unit tests.

Way #2: Custom build script


Sublime Text provides with a good ability to set up a custom build system for the projects a developer works on.

The final configuration might look like:

where python-wrapper.sh is a shell script to launch Python unit tests directly from Sublime Text on Ctrl-B. The script supposes that:

  1. The tests are located in the same directory where the SUT is;
  2. The file with a test has a prefix 'test_'.
... and it is compatible with bash:

#!/bin/bash

RUN_DIRECTORY="${1}"
SOURCE_NAME="${2}"
TEST_OF_SOURCE_NAME="test_${SOURCE_NAME}"

pushd "${RUN_DIRECTORY}"

if [ -e "${TEST_OF_SOURCE_NAME}.py" ]; then
 python -m unittest "${TEST_OF_SOURCE_NAME}"
else
 echo "[Warning] Associated test '${TEST_OF_SOURCE_NAME}' not found"
 python "${SOURCE_NAME}.py"
fi

RC=$?

popd

exit ${RC}

The simplicity of the solution might make you write and launch unit tests more often! But the solution should not be a reason not to run all unit tests periodically in the project.

No comments:

Post a Comment