Skip to content

Running unit tests in multiple environments

Posted in Code Snippets

Recently my fiancée, who works as a software test engineer, showed me the Selenium framework. I find it quite useful, as it makes automating the testing of web apps really easy with its Python library. Combined with the unittest lib readily available in every Python install, test cases can be written in a few lines of code.

One of the strengths of Selenium is that it can utilize multiple browsers, and abstracts them using webdrivers. So how should be already written test scripts modified, if they must be run with multiple browsers instead of a single one? Assuming that every test case is derived from a common parent class, which is, in turn, subclassed from unittest.TestCase, it turns out that only a few line of code must be changed.

import unittest

class MultiEnvironmentTestCase(unittest.TestCase):
    """
    Instead of using a single setUp() and tearDown() pair to construct and destruct
    the test environment, MultiEnvironmentTestCase allows its subclasses to define multiple
    methods for them, and runs the test functions in each environment.
    
    To define an environment, write a method whose name begins with "set_up_",
    and/or an other method whose name begins with "tear_down_". In case both of these
    methods are defined for an environment, they will be called before and after
    running every test method defined in the class. If one is missing, the default
    implementation in unittest.TestCase will be called instead.
    """
    
    _set_up_method_prefix = "set_up_"
    _tear_down_method_prefix = "tear_down_"
    
    def run(self, result = None):
        # Build a list of set_up and tear_down pairs
        attributes = dir(self)
        environment_names = set()
        for name in attributes:
            if name.startswith(self._set_up_method_prefix):
                environment_names.add(name[len(self._set_up_method_prefix):])
            elif name.startswith(self._tear_down_method_prefix):
                environment_names.add(name[len(self._tear_down_method_prefix):])
        
        # Fall back to the defalut implementation if no environments are defined
        if not environment_names:
            super(MultiEnvironmentTestCase, self).run(result)
            return
        
        # Run the test within each defined environments
        for env_name in sorted(environment_names):
            set_up_name = self._set_up_method_prefix + env_name
            if set_up_name in attributes:
                self.setUp = getattr(self, set_up_name)
            else:
                self.setUp = super(MultiEnvironmentTestCase, self).setUp
            
            tear_down_name = self._tear_down_method_prefix + env_name
            if tear_down_name in attributes:
                self.tearDown = getattr(self, tear_down_name)
            else:
                self.tearDown = super(MultiEnvironmentTestCase, self).tearDown
            
            super(MultiEnvironmentTestCase, self).run(result)

I’ve designed the MultiEnvironmentTestCase so it can be a drop-in replacement of unittest.TestCase. If its special methods are not implemented, it will simply fall back to the default behaviour defined in its parent class. To utilize its magic, its subclass should – instead of a single setUp() and tearDown() pair – implement multiple function pairs for the management of the test environment. Or single set up functions if no clean-up is needed after the test: to define an environment, it is enough to implement only one from the set_up_ and tear_down_ pair.

from selenium import webdriver
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary

from MultiEnvironmentTestCase import *

class MultiBrowserTestCase(MultiEnvironmentTestCase):
    """
    Run each test function both in Chrome and in Firefox
    """

    def set_up_chrome(self):
        self.driver = webdriver.Chrome()
    def tear_down_chrome(self):
        self.driver.quit()

    def set_up_firefox(self):
        binary = FirefoxBinary('C:\\FirefoxPortable\\FirefoxPortable.exe')
        self.driver = webdriver.Firefox(firefox_binary=binary)
    def tear_down_firefox(self):
        self.driver.quit()

So if every test case was subclassed from a BrowserTestCase class, which implemented the setUp() and tearDown() methods for initializing and closing the webdriver, it can be easily modified following the example code for MultiBrowserTestCase.

from MultiBrowserTestCase import *

class TestExample(MultiBrowserTestCase):

    def test_example(self):
        self.driver.get('http://www.google.com/xhtml')
        self.assertTrue('Google' in self.driver.title)

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

If everything is in its place, running this example should start Chrome, run the test, close the browser, and start again with a portable version of Firefox.

Be First to Comment

Leave a Reply