32.Python的单元测试工具——unittest(初级)

文章目录

转载请注明原始出处:http://blog.csdn.net/a464057216/article/details/51882991

后续此博客不再更新,欢迎大家搜索关注微信公众号“测开之美”,测试开发工程师技术修炼小站,持续学习持续进步。
在这里插入图片描述

#简介#
unittest是Python的内建模块,是Python单元测试的事实标准,也叫PyUnit。使用unittest之前,先了解如下几个概念:

  • test case:测试用例,可以通过创建unitest.TestCase类的子类创建一个测试用例。
  • test fixture:包含执行测试用例前的测试准备工作、测试用例执行后的清理工作(分别对应TestCase中的setUp()tearDown()方法),测试准备和测试清理的目的是保证每个测试用例执行前后的系统状态一致。
  • test suite:测试套,是测试用例、测试套或者两者的集合,用来将有关联的测试项打包。
  • test runner:负责执行测试并将结果展示给用户,可以展示图形或文字形式(unittest.TextTestRunner)的结果,或者返回一个错误码标识测试用例的执行结果。test runner提供了一个方法run(),接受一个unittest.TestSuiteunittest.TestCase实例作为参数,执行对应测试项目后返回测试结果unittest.TestResult对象。
    #基本使用方法#
    定义测试用例的方法如下:
#unit.py
import unittest

class TestStringMethods(unittest.TestCase):
    def test_upper(self):
        self.assertEqual('Loo'.upper(), 'LOO')

    def test_isupper(self):
        self.assertTrue('LOO'.isupper())
        self.assertFalse('Loo'.isupper())

    def test_split(self):
        s = 'Mars Loo'
        with self.assertRaises(TypeError):
            s.split(2)

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

执行脚本:

$ python unit.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

每一个测试项目的函数定义以test开头命名,这样test runner就知道哪些函数是应该被执行的。上面的例子展示了验证测试结果常用的三种方法:

  • assertEqual(a, b):比较a == b
  • assertTrue(exp)assertFalse(exp):验证bool(exp)为True或者False
  • assertRaises(Exception):验证Exception被抛出。

之所以不使用Python内建的assert抛出异常,是因为test runner需要根据这些封装后的方法抛出的异常做测试结果统计。

unittest.main()方法会在当前模块寻找所有unittest.TestCase的子类,并执行它们中的所有测试项目。使用-v参数可以看到更详细的测试执行过程:

$ python unit.py -v
test_isupper (__main__.TestStringMethods) ... ok
test_split (__main__.TestStringMethods) ... ok
test_upper (__main__.TestStringMethods) ... ok

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

OK

也可以修改最后两行成如下代码:

suite = unittest.TestLoader().loadTestsFromTestCase(TestStringMethods)
unittest.TextTestRunner(verbosity=2).run(suite)

测试结果如下:

$ python unit.py
test_isupper (__main__.TestStringMethods) ... ok
test_split (__main__.TestStringMethods) ... ok
test_upper (__main__.TestStringMethods) ... ok

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

OK

#从命令行运行unittest#

$ python -m unittest unit          #直接运行模块unit中的测试用例
$ python -m unittest unit.TestStringMethods          #运行模块中的某个类
$ python -m unittest unit.TestStringMethods.test_upper          #运行某个单独的测试方法

混合运行测试模块、类以及测试方法也是可以的。

如果要查看unittest模块命令行的更多参数信息,使用-h参数:

$ python -m unittest -h
  • -b参数:只在测试用例fail或者error时显示它的stdout和stderr,否则不会显示。

  • -f参数:如果有一个测试用例fail或者出现error,立即停止测试。

  • -c参数:捕捉Control-C信号,并显示测试结果。

#自动发现测试用例#

unittest能够自动发现测试用例。为了让测试用例能够被自动发现,测试文件需要是在项目目录中可以import的module或者package,比如如下目录结构:

unittest
├── test_a
│   ├── __init__.py
│   └── test_a.py
└── test_b.py

在unittest目录中运行如下命令,即可运行test_a这个package和test_b这个module中的测试项目:

$ python -m unittest discover
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

unittest discovery默认会搜索名字命名符合test*的module或package,可以添加更多的参数:

  • -v:详细输出。
  • -s:开始自动搜索的目录,默认是.;这个参数也可以指向一个package名,而不是目录,例如unittest.test_a。
  • -p:文件匹配的模式,默认是test*.py
  • -t:项目顶级目录,默认与开始自动搜索的目录相同。

比如:

$ python -m unittest discover -s test_a
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

测试用例发现通过import模块或package执行测试,例如foo/bar/baz.py会被import为foo.bar.baz

#测试代码的组织#
测试用例一定要是自包含的,即测试用例既可以独立运行,也可以和其他测试用例混合执行,测试用例执行前后不能影响系统状态。

建议将被测试代码和测试代码分离,比如一个模块module.py对应的单元测试的文件是test_module.py,这样方便维护。

最简单的测试用例定义,是一个unittest.TestCase的子类只包含一个测试步骤,这个时候只需要定义一个runTest方法,比如:

# unit.py
import unittest

class MyTestCase(unittest.TestCase):
    def runTest(self):
        self.assertEqual(1, 2, 'not equal')

执行测试后结果如下:

$ python -m unittest -v unit
runTest (unit.MyTestCase) ... FAIL

======================================================================
FAIL: runTest (unit.MyTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "unit.py", line 5, in runTest
    self.assertEqual(1, 2, 'not equal')
AssertionError: not equal

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)

如果assert*方法检查失败,会抛出一个异常,unittest会将其算作失败(failure)。任何其他异常都被unittest算作错误(error),比如:

#unit.py
import unittest

class MyTestCase(unittest.TestCase):
    def runTest(self):
        self.assertEqual(notexist, 2, 'not exist')

执行测试结果如下:

$ python -m unittest -v unit
runTest (unit.MyTestCase) ... ERROR

======================================================================
ERROR: runTest (unit.MyTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "unit.py", line 5, in runTest
    self.assertEqual(notexist, 2, 'not exist')
NameError: global name 'notexist' is not defined

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (errors=1)

即failure通常是实际结果与预期结果不符,error通常是因为测试代码有bug导致。
如果很多测试项目的初始化准备工作类似,可以为他们定义同一个setUp方法,比如:

import unittest

class BaseTestCase(unittest.TestCase):
    def setUp(self):
        self._value = 12

class TestCase1(BaseTestCase):
    def runTest(self):
        self.assertEqual(self._value, 12, 'default value error')

class TestCase2(BaseTestCase):
    def runTest(self):
        self._value = 13
        self.assertEqual(self._value, 13, 'change value fail')

如果基类BaseTestCasesetUp方法中抛出异常,unittest不会继续执行子类中的runTest方法。
如果想在测试项目执行结果后进行现场清理,可以定义tearDown()方法:

import unittest

class B(unittest.TestCase):
    def setUp(self):
        self._value = 1
    def test_b(self):
        self.assertEqual(self._value, 1)
    def tearDown(self):
        del self._value

setUp()tearDown()方法的执行过程是:针对每一个测试项目,先执行setUp()方法,如果成功,那么继续执行测试函数,最后不管测试函数是否执行成功,都执行tearDown()方法;如果setUp()方法失败,则认为这个测试项目失败,不会执行测试函数也不执行tearDown()方法。

工作中很多测试项目依赖相同的测试夹具(setUptearDown),unittest支持像这样定义测试用例:

import unittest

class TestCase1(unittest.TestCase):
    def setUp(self):
        self._value = 12

    def test_default(self):
        self.assertEqual(self._value, 12, 'default value error')

    def test_change(self):
        self._value = 13
        self.assertEqual(self._value, 13, 'change value fail')

如果要执行指定的测试用例的话,可以使用TestSuite这个类,包含使用方法名作为参数声明一个测试用例实例,比如:

import unittest

class TestCase1(unittest.TestCase):
    def setUp(self):
        self._value = 12

    def test_default(self):
        self.assertEqual(self._value, 12, 'default value error')

    def test_change(self):
        self._value = 13
        self.assertEqual(self._value, 13, 'change value fail')

test_suite = unittest.TestSuite()
test_suite.addTest(TestCase1('test_default'))

test_runner = unittest.TextTestRunner()
test_runner.run(test_suite)

测试套也可以是测试套的集合,比如:

import unittest

class TestCase1(unittest.TestCase):
    def setUp(self):
        self._value = 12

    def test_default(self):
        self.assertEqual(self._value, 12, 'default value error')

    def test_change(self):
        self._value = 13
        self.assertEqual(self._value, 13, 'change value fail')

test_suite1 = unittest.TestSuite()
test_suite1.addTest(TestCase1('test_default'))

test_suite2 = unittest.TestSuite()
test_suite2.addTest(TestCase1('test_change'))

test_suite = unittest.TestSuite([test_suite1, test_suite2])

test_runner = unittest.TextTestRunner()
test_runner.run(test_suite)

如果想执行测试类中的部分测试用例,可以采用如下方式:

def suite():
    tests = ['test_default', 'test_change']
    return unittest.TestSuite(map(TestCase1, tests))

test_runner = unittest.TextTestRunner()
test_runner.run(suite())

因为将一个测试用例类下面的所有测试步骤都执行一遍的情况非常普遍,unittest提
供了TestLoader类,它的loadTestsFromTestCase()方法会在一个TestCase类中寻找所有以test开头的函数定义,并将他们添加到测试套中,这些函数会按照其名字的字符串排序顺序执行,比如:

import unittest

class TestCase1(unittest.TestCase):
    def setUp(self):
        self._value = 12

    def test_default(self):
        self.assertEqual(self._value, 12, 'default value error')

    def test_change(self):
        self._value = 13
        self.assertEqual(self._value, 13, 'change value fail')

test_suite = unittest.TestLoader().loadTestsFromTestCase(TestCase1)

unittest.TextTestRunner(verbosity=2).run(test_suite)

#忽略测试用例及假设用例失败#
有些情况下需要忽略执行某些测试用例或者测试类,这个时候可以使用unittest.skip装饰器及其变种。需要特别注意的是,可以通过skip某个测试类的setUp()方法而跳过整个测试类的执行,比如:

import unittest, sys

version = (0, 1)

class HowToSkip(unittest.TestCase):
    @unittest.skip('demonstrating skipping')
    def test_nothing(self):
        self.fail('will never be ran')

    @unittest.skipIf(version < (1, 3),
            'not supported version')
    def test_format(self):
        print 'your version is >= (1, 3)'

    @unittest.skipUnless(sys.platform.startswith('win'),
            'requires windows')
    def test_winndows_support(self):
        print 'support windows'

@unittest.skip('class can also be skipped')
class Skipped(unittest.TestCase):
    def test_skip(self):
        pass

class SkippedBySetUp(unittest.TestCase):
    @unittest.skip('Skipped by setUp method')
    def setUp(self):
        pass

    def test_dummy1(self):
        print 'dummy1'

    def test_dummy2(self):
        print 'dummy2'

测试结果如下:

$ python -m unittest -v unit
test_format (unit4.HowToSkip) ... skipped 'not supported version'
test_nothing (unit4.HowToSkip) ... skipped 'demonstrating skipping'
test_winndows_support (unit4.HowToSkip) ... skipped 'requires windows'
test_skip (unit4.Skipped) ... skipped 'class can also be skipped'
test_dummy1 (unit4.SkippedBySetUp) ... skipped 'Skipped by setUp method'
test_dummy2 (unit4.SkippedBySetUp) ... skipped 'Skipped by setUp method'

----------------------------------------------------------------------
Ran 6 tests in 0.001s

OK (skipped=6)

特别地,被忽略的测试用例将不会执行他们的setUp()、tearDown()方法,被忽略的测试类将不会执行他们的setUpClass()、tearDownClass()方法(关于setUpClass()和tearDownClass()的详细介绍,在下一篇博客中)。

有的时候,明知道某些测试用例会失败,这时可以使用unittest.expectedFailure装饰器,被期望失败的测试用例不会加到测试结果的failure统计中,而是加到expected failure统计中,比如:

import unittest

class ExpectedFailure(unittest.TestCase):
    @unittest.expectedFailure
    def test_fail(self):
        self.assertEqual(1, 2, 'not equal')

测试结果如下:

$ python -m unittest -v unit
test_fail (unit.ExpectedFailure) ... expected failure

----------------------------------------------------------------------
Ran 1 test in 0.000s

OK (expected failures=1)

如果被expectedFailure的测试用例成功了,会被加到unexpected success的计数中。
综上所述,unittest执行测试用例结束后,有6种结束状态:ok、failure、error、expected failure、skipped、unexpected success。实际工作中发送自动化测试报告时,需要注意分别这些状态的含义。
目前介绍的这些内容可以应付一些简单工作,如果想要进一步学习unittest模块的类及更多的API和结果检查方法,可以继续看我的下一篇blog

如果觉得我的文章对您有帮助,欢迎关注我(CSDN:Mars Loo的博客)或者为这篇文章点赞,谢谢!

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值