PyUnit简介

PyUnit是Python 2.1版本新增功能。

Python单元测试框架,又叫PyUnit。是JUnit的Python实现,是由Kent Beck和Erich Gamma共同开发的。同样,JUnit是Kent的Smalltalk测试框架的Java实现。PyUnit和JUnit都是各自怨言的标准测试框架。

PyUnit模块支持自动化测试,通用的setup和shutdown的代码,测试用例整合为测试集,还有独立的测试报告框架。PyUnit提供的类能够很容易的使以上特性应用于测试。PyUnit是借由以下概念支持单元测试的:

测试fixture

测试fixture是指运行测试前的准备工作和运行测试后的清理工作。例如,创建临时或代理数据库、目录,或者启动服务进程。

测试用例

测试用例是最小的测试单元,检查特定的输入会产生预期的结果。PyUnit提供一个测试用例几类TestCase,继承基类可以创建新的测试用例。

测试集

测试集是测试用例的集合,同时也可以是其他测试集的结合,或者是测试用例和测试集的混合集合。用于批量执行测试用例。

执行器

执行器用来执行测试用例,并且把测试结果呈现给用户。执行器可以使用图形界面,文本界面,甚至特殊值来显示测试结果。

测试用例和测试fixture通过TestCase和FunctionTestCase两个类来实现的。TestCase用来创建新的测试用例,而FunctionTestCase是用来把已有测试用例整合为PyUnit结构用例的(译者注:项目原来已经有测试用例了,后来想改用PyUnit测试框架,这时候就要用到FunctionTestCase了)。使用TestCase类创建新测试用例,需要覆盖setUp()和tearDown()方法,他们分别用来初始化fixture和清理fixture。而要使用FunctionTestCase类为已有函数创建测试用例,需要符合以下条件:我们不关心已有函数的测试结果,只关心正确的测试流程fixture初始化->执行测试步骤->测试固件清理。每个TestCase只能执行一个测试方法,所以最好每个测试用例有单独的测试fixture。

TestSuite类实现测试套件功能,可以整合单独的测试用例或者其他测试套件。执行测试套件,测试套件中所有的测试用例和子测试套件都会被执行。

执行器提供一个方法run(),该方法接受TestCase或者TestSuite对象最为参数,并且返回TestResult结果对象。PyUnit提供一个使用TextTestRunner执行器的例子,该例子汇报默认的标准错误流测试结果。想要更改其他环境的执行器(例如图形界面环境)并不需要派生自特定的类。

另请参阅:

Module doctest

Another test-support module with a very different flavor.

unittest2: A backport of new unittest features for Python 2.4-2.6

Many new features were added to unittest in Python 2.7, including test discovery. unittest2 allows you to use these features with earlier versions of Python.

Simple Smalltalk Testing: With Patterns

Kent Beck’s original paper on testing frameworks using the pattern shared by unittest.

Nose and py.test

Third-party unittest frameworks with a lighter-weight syntax for writing tests. For example, assert func(10) == 42.

The Python Testing Tools Taxonomy

An extensive list of Python testing tools including functional testing frameworks and mock object libraries.

Testing in Python Mailing List

A special-interest-group for discussion of testing, and testing tools, in Python.

简单例子

PyUnit模块提供了大量的工具来构造和运行测试,本部分的例子可以满足大部分用户的需求。

以下脚本是测试random模块的3个函数的例子:

import random
import unittest

class TestSequenceFunctions(unittest.TestCase):

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

    def test_shuffle(self):
        # make sure the shuffied 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.assertRaise(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.assertRaise(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.TestCase类来创建测试用例,测试用例中3个独立的测试函数以“test"为开头命令。通过这种命名方式,执行器可以知道哪些方法是测试方法。 需要注意的是3个测试方法分别调用assertEqual()函数来检查预期结果;调用assertTure()函数来判断条件;调用assertRaises()函数来验证是否触发了预期的异常。这3个方法作为断言语句判断用例执行的正确性,以便执行器手机测试结果并产生测试报告。

如果定义setUp()方法,每个测试用例执行前都会执行setUp();同样的,如果定义的tearDown()方法,每个测试用例执行完后都会执行tearDown()方法。上面的例子,setup()方法用来为每个用例创建一个新序列。

例子的最后一段介绍了一种简单调用测试用例的方法-unittest.main().它为用例提供了命令行界面运行,脚本运行会输出以下内容:

...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

连续3个点"..."表示3个测试用例都运行通过,如果运行失败显示“F”,例如“.FF"表示后2个用例运行失败。

有很多更易管理,输出信息更简洁,并且不在命令行运行的方法来替代unittest.main()方法运行测试用例。例如以下方法,替换例子的最后一行unittest.main():

suite = unittest.TestLoader().loadTestsFormTestCase(TestSequenceFunctions)
unittest.TestTestRunner(verbosity=2).run(suite)

修改后的脚本如下:

test_choice (__main__.TestSequenceFunctions) ... ok
test_sample (__main__.TestSequenceFunctions) ... ok
test_shuffle (__main__.TestSequenceFunctions) ... ok

下面是一个简短的例子用于测试3个字符方法:

import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

    def test_isupper(self):
        self.assertTure('FOO'.isupper())
        self.assertFalse('Foo'.isupper())

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])
        # check that s.split fails when the separator is not a TestStringMethods
        with self.assertRaise(TypeError):
            s.split(2)

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

测试用例通过继承unittest.TestCase创建。测试用例中3个独立的测试函数以"test"为开头命名。通过使用这种命名方式,执行器可以知道哪些方法是测试方法。

3个测试方法同样调用assertEqual()函数来检查预期结果;调用assertTrue()函数来判断条件;调用assertRaises()函数来验证是否触发了预期的异常。这3个方法作为断言语句判断用例执行正确性,以便于执行器收集测试结果并产生测试报告。

by 李鹏