单元测试在软件开发中的重要性不言而喻,尤其是近些年敏捷开发以及DevOps理念的兴起, 更突显了在代码开发的过程中写单元测试的重要。

本文简单概述了 Python Unittest 的基本用法。 详细内容可以参考官网

What is Unit testing

It’s an automated piece of code that invokes a different method
and then checks some assumptions on the logical behavior of that method or class.

  • It’s written using a unit-testing framework.
  • It can be written easily.
  • It runs quickly.
  • It can be executed repeatedly by anyone on the development team. -- The Art of Unit Testing

Test Driven Development

  • design methodology -> artifacts for testing
  • short cycle repeated many times:
  • write a test
  • watch it fail
  • make it compile
  • make it pass
  • refactor the code
  • refactor the test (and elaborate)
  • rinse and repeat

unittest

unittest-doc

  • unittest — Unit testing framework
  • The unittest test framework is python’s xUnit style framework.
  • It is the foundation of automated testing in the Python world.

The Python unit testing framework, sometimes referred to as “PyUnit,”
is a Python language version of JUnit, by Kent Beck and Erich Gamma.
JUnit is, in turn, a Java version of Kent’s Smalltalk testing framework.
Each is the de facto standard unit testing framework for
its respective language.

Unittest: Basic example

Here is a short script to test three functions from the random module:

import random
import unittest


class TestSequenceFunctions(unittest.TestCase):

    def setUp(self):
        self.seq = range(10)

    def test_shuffle(self):
        # make sure the shuffled sequence does not lose any elements
        random.shuffle(self.seq)
        self.seq.sort()
        self.assertEqual(self.seq, range(10))

        # should raise an exception for an immutable sequence
        self.assertRaises(TypeError, random.shuffle, (1,2,3))

    def test_choice(self):
        element = random.choice(self.seq)
        self.assertTrue(element in self.seq)

    def test_sample(self):
        with self.assertRaises(ValueError):
            random.sample(self.seq, 20)
        for element in random.sample(self.seq, 5):
            self.assertTrue(element in self.seq)

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

Unittest: setUp()

Method called to prepare the test fixture.
This is called immediately before calling the test method;
any exception raised by this method will be considered an error
rather than a test failure. The default implementation does nothing.

Unittest: tearDown()

Method called immediately after the test method has been called
and the result recorded. This is called even if the test method
raised an exception, so the implementation in subclasses
may need to be particularly careful about checking internal state.
Any exception raised by this method will be considered an error
rather than a test failure. This method will only be called
if the setUp() succeeds, regardless of the outcome of the test method.
The default implementation does nothing.

assertXXX

  • assertEqual(a, b)
  • assertNotEqual(a, b)
  • assertTrue(x)
  • assertFalse(x)
  • assertIs(a, b)
  • assertIsNot(a, b)
  • assertIsNone(x)
  • assertIsNotNone(x)
  • assertIn(a, b)
  • assertNotIn(a, b)
  • assertIsInstance(a, b)
  • assertNotIsInstance(a, b)

There are also other methods used to perform more specific checks, such as:

  • assertAlmostEqual(a, b)
  • assertNotAlmostEqual(a, b)
  • assertGreater(a, b)
  • assertGreaterEqual(a, b)
  • assertLess(a, b)
  • assertLessEqual(a, b)
  • assertRegexpMatches(s, r)
  • assertNotRegexpMatches(s, r)
  • assertItemsEqual(a, b)
  • assertDictContainsSubset(a, b)

Command-Line Interface

$ python -m unittest test_module1 test_module2
$ python -m unittest test_module.TestClass
$ python -m unittest test_module.TestClass.test_method

Grouping tests

class unittest.TestSuite(tests=())

example:

widgetTestSuite = unittest.TestSuite()
widgetTestSuite.addTest(WidgetTestCase('test_default_size'))
widgetTestSuite.addTest(WidgetTestCase('test_resize'))

it is a good idea to provide in each test module a callable object that
returns a pre-built test suite:

def suite():
    suite = unittest.TestSuite()
    suite.addTest(WidgetTestCase('test_default_size'))
    suite.addTest(WidgetTestCase('test_resize'))
    return suite

or even:

def suite():
    tests = ['test_default_size', 'test_resize']
    return unittest.TestSuite(map(WidgetTestCase, tests))

or

suite = unittest.TestLoader().loadTestsFromTestCase(WidgetTestCase)

Skipping tests and expected failures

class MyTestCase(unittest.TestCase):

    @unittest.skip("demonstrating skipping")
    def test_nothing(self):
        self.fail("shouldn't happen")

Loading and running tests

class unittest.TestLoader

TestLoader objects have the following methods:

  • loadTestsFromTestCase(testCaseClass)
  • loadTestsFromModule(module)
  • loadTestsFromName(name, module=None)
  • loadTestsFromNames(names, module=None)
  • getTestCaseNames(testCaseClass)
  • unittest.main

Running with increased verbosity

It is easy to adjust the test runner to print out every test method
as it is run.

if __name__ == "__main__":
    suite = unittest.TestLoader().loadTestsFromTestCase( \
    RomanNumeralConverterTest)
    unittest.TextTestRunner(verbosity=2).run(suite)

Exercises

class RomanNumeralConverter(object):
    def __init__(self):
        self.digit_map = {"M":1000, "D":500, "C":100, "L":50, "X":10, "V":5, "I":1}

    def convert_to_decimal(self, roman_numeral):
        val = 0
        for char in roman_numeral:
            val += self.digit_map[char]
        return val

Nose

nose-doc

Nose is nicer testing for python. nose extends unittest to make testing easier.

unittest comes with the standatd library, but I would recomend you nosetests.
"nose extends unittest to make testing easier."

"nosetests example_unit_test.py" - to execute a single file of tests "nosetests /path/to/tests" - to execute a suite of tests in a folder

nose isn't really a unit testing framework.
It's a test runner and a great one at that.
It can run tests created using pyUnit, py.test or doctest.

What does nose offer that unittest does not?

  • automatic test discovery and a useful plugin API.
  • There are many nose plugins that provide everything from specially
    formatted test reports to integration with other tools.
  • Do not need write runner(such as unittest.main())
  • Nose is extensible
  • Nose is embeddable

Nose options

Some useful command line options that you may wish to keep in mind include:

  • -v: gives more verbose output, including the names of the tests being executed
  • -s or -nocapture: allows output of print statements, which are normally captured and hidden while executing tests. Useful for debugging.
  • --nologcapture: allows output of logging information
  • --rednose: an optional plugin, which can be downloaded here, but provides colored output for the tests.
  • --tags=TAGS: allows you to place an @TAG above a specific test to only execute those, rather than the entire test suite.

Embedding nose inside Python

It's very convenient to embed nose inside a Python script.
This lets us create higher level test tools besides letting the developer add testing to an existing tool.

if __name__ == "__main__":
    import nose
    nose.run(argv=["", "recipe12", "--verbosity=2"])

instead of

if __name__ == "__main__":
    import unittest
    from recipe12 import *
    suite = unittest.TestLoader().loadTestsFromTestCase(\
    ShoppingCartTest)
    unittest.TextTestRunner(verbosity=2).run(suite)

Writing nose extension

see examples

py.test

py.test

pytest is a mature full-featured Python testing tool that helps you write better programs.

py.test example

# content of test_sample.py
def func(x):
    return x + 1

def test_answer():
    assert func(3) == 5

That’s it. You can execute the test function now:

more examples and usages,

click here

Reporter

HTMLTestRunner

def suite():
    modules_to_test = (
                      # 'PreparData',
                        'tc_my_test1',
                        'tc_my_test2'
                        # and so on
                      )
    alltests = unittest.TestSuite()
    for module in map(__import__, modules_to_test):
        alltests.addTest(unittest.findTestCases(module))
    return alltests

fp = file(report_path + report_name, 'wb')
runner = HTR.HTMLTestRunner(stream=fp, title=title, description=description)

runner.run(unittest.TestSuite([selectcase.suite()]))

OpenStack's Unit Testing

  • Unit testing Framework

    • from unittest to testtools, which support fixtures pattern
  • Tests management

    • from nose to testr, which support Parallel testing and more Test automation using tox,
    • which, based on virtualenv, can easily configure for different environment and merge CI.

Other things about Code Quality

  • PEP8
  • Pylint, pyflakes
  • docstr

Learn more to

  • http://docs.python.org/2/library/unittest.html
  • http://www.diveintopython.net/unit_testing
  • http://cgoldberg.github.io/python-unittest-tutorial/
  • http://code.tutsplus.com/tutorials/beginning-test-driven-development-in-python--net-30137
  • http://halfcooked.com/presentations/pyconau2013/why_I_use_pytest.html
  • http://stackoverflow.com/questions/191673/preferred-python-unit-testing-framework


Comments

comments powered by Disqus