john pfeiffer
  • Home
  • Categories
  • Tags
  • Archives

unittest data driven dynamic test cases

### Dynamically generating test classes and test cases (nosetest and unittest compatible)

#!/usr/bin/env python3
import os
import unittest
# TODO: tests are maybe brittle due to whitespace sensitivity: regex?


def create_in_method(target_string, data):
    """do not include 'test' in the method name here otherwise nose will try to run it,
    assumes class inheritance from unittest.TestCase (for assertIn)"""
    def test(self):
        self.assertTrue(target_string in data)
    return test


# Begin the global parts so that nosetests and unittest can compatibly run
data_file = os.environ.get('TEST_DATA_FILE', 'build.log')
with open(data_file) as f:
    data = f.read()


test_cases = dict()

# TODO: refactor to parse from a data file each test class, test name and test string
tests = dict()
test_string = 'tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN'
tests['test_listening_on_port_22'] = create_in_method(test_string, data)
test_string = '/usr/sbin/sshd'
tests['test_sshd_ps_exists'] = create_in_method(test_string, data)
test_cases['test_sshd_installation'] = tests


tests = dict()
test_string = 'tcp        0      0 0.0.0.0:25              0.0.0.0:*               LISTEN'
tests['test_listening_on_port_25'] = create_in_method(test_string, data)
test_string = '/usr/lib/postfix/master'
tests['test_postfix_ps_exists'] = create_in_method(test_string, data)
test_cases['test_mail_postfix_installation'] = tests

for test_case_name, tests in test_cases.items():
    globals()[test_case_name] = type(test_case_name, (unittest.TestCase, ), tests)


if __name__ == '__main__':
    # python -m unittest test_build ... OR
    # nosetests --verbose --with-xunit --xunit-file=results.xml test_build.py
    unittest.main(verbosity=2)

- - -
### Python Unit Tests that can be built from a domain specific language like data file

import time
import unittest

# tests will be attached to the Class dynamically
class TestCases(unittest.TestCase):
    pass


def make_function(name, strings, data):
    def test(self):
        for s in strings:
            self.assertIn(s, data, name)
    return test

if __name__ == '__main__':
    """
         execute using: python test_data_assertions.py

         DATA FORMAT DEFINITION
         # starts with a hash means a section of tests and the name should only contain ascii letters
         each line represents a string that should be in the data file
         TODO: ! at the beginning of a line means the data file should not contain the string
    """
    # TODO: not compatible with nosetests so no junit xml =(
    # e.g. nosetests --verbose --with-xunit --xunit-file=results.xml test_some_log.py
    # https://nose.readthedocs.org/en/latest/writing_tests.html#test-generators
    # http://eli.thegreenplace.net/2014/04/02/dynamically-generating-python-test-cases

    # http://programming.nullanswer.com/question/24709208
    # TODO: get data file and test files from the CLI


    data_file = 'my.log'
    with open(data_file) as f:
        data = f.read()

    tests_file = 'build.tests'
    test_names = dict()
    with open(tests_file) as f:
        for line in f:
            if line.startswith('#'):
                raw_name = line[1:].strip()
                name = raw_name.replace(' ', '_')
                test_names['test_' + name] = list()

    tests = list()
    tests.append('SELECT COUNT(*) FROM accounts; 1')
    tests.append('SELECT COUNT(*) FROM users; 1')
    test_names['test_default_account_and_user_in_MySQL'] = tests

    print('DEBUG')
    for test_name, assertions in test_names.items():
        print(test_name)
        for i in assertions:
            print('    {}'.format(i))

    time.sleep(1)

    for test_name, assertions in test_names.items():
        test_function = make_function(test_name, assertions, data)
        setattr(TestCases, test_name, test_function)

    unittest.main(verbosity=2)


- - -
### Nosetests compatible version

import unittest


class TestCases(unittest.TestCase):
    pass


def create_method(a, b):
    """do not include 'test' in the method name here otherwise nose will try to run it"""
    def dynamic_method(self):
        self.assertEqual(a, b)
    return dynamic_method


dynamic_method = create_method(1,1)
dynamic_method.__name__ = 'test_{0}'.format('1_equals_1')
setattr(TestCases, dynamic_method.__name__, dynamic_method)
# remove the global dynamic_method since we only want to run those injected into TestCases
del dynamic_method


if __name__ == '__main__':
    # python -m unittest test_build ... OR
    # nosetests --verbose --with-xunit --xunit-file=results.xml test_build.py
    unittest.main()



- - -
### Nosetests generator

> note this eschews class names and the in_data.description should be from a dict of tests

import os


data_file = os.environ.get('TEST_DATA_FILE', 'build.log')
with open(data_file) as f:
    data = f.read()


def in_data(target_string, data):
    assert(target_string in data)


def test_sshd_installation():
    for s in [
        'tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN',
        '/usr/sbin/sshd'
    ]:
        in_data.description = 'test_string_in_data'
        yield in_data, s, data

  • « Bash pipe redirect sudo tee append double pipe true shell command line math zero truncate file begins with dash
  • Mysql mysqldump csv »

Published

Aug 11, 2015

Category

python

~481 words

Tags

  • cases 1
  • data 6
  • driven 1
  • dynamic 3
  • python 180
  • test 29
  • unittest 12