Python单元测试(一)--Unittest

2人阅读 评论(0) 收藏 举报
分类:

什么是单元测试

单元测试是用来对最小可测试单元(如一个函数、一个类或者一个模块)进行检查和验证。尤其是在对代码进行修改或重构后,可以快速准确的定位出现问题的问题。

为什么要做单元测试

写代码的首要目标就是实现需求。在此基础上,就要尽可能提高代码的质量和可维护性。而单元测试,就是提高代码质量和可维护性的一种方式,也是成本最低的一种方式。因为尽早排除尽可能过的bug,可以减少在后续阶段解决bug的成本(包括时间成本、人力成本等)。

单元测试的基本原则

  • 单元测试代码要非常简单,如果测试代码过于复杂,则测试代码本身也可能存在bug
  • 通过单元测试,并不意味着所测代码没有bug

Python单元测试框架

  • Unittest:Python标准库自带的单元测试工具,也称为PyUnit,是JUnit(Java语言的单元测试)的Python版本。
  • Doctest:Python标准库自带的单元测试工具,适合简单的单元测试。其工作原理是在docstring中寻找测试用例并执行,比较输出结果与期望值是否符合。
  • Pytest:Python的一种单元测试框架,与Unittest测试框架类似。其特点是拥有众多高级功能和插件。
  • Nose:继承自Unittest,但比Unittest易用,而且能够自动识别测试用例。任何符合正则表达式“(?:^|[b_.-])[Tt]est”的类、函数、文件或目录,以及TestCase的子类都会被识别并执行。
  • Unittest2:Unittest的升级版。

Unittest

Unittest是Python的标准测试库,无需额外安装,是目前Python使用较为官方的测试框架。下面通过实例,一步一步了解如何使用Unittest。

待测试代码如下,用于验证email和密码。

# -*- coding: utf-8 -*-

import re


class CheckUserInfo:

    def check_mail(self, mail):
        flag = False
        str = '^[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+){0,4}@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+){0,4}$'
        if re.match(str, mail):
            flag = True

        return flag

    def check_pwd_len(self, pwd):
        '''密码长度不超过8位'''
        return len(pwd)>=8

    def check_pwd_contain_leter(self, pwd):
        '''密码包含大小写英文字母'''
        flag = False
        pattern = re.compile('[A-Z][a-z]+')
        match = pattern.findall(pwd)

        if match:
            flag = True
        return flag

    def check_pwd_contain_num(self, pwd):
        '''密码包含数字'''
        flag = False
        pattern = re.compile('[0-9]+')
        match = pattern.findall(pwd)
        if match:
            flag = True
        return flag

TestCase — 单元测试用例

对上面代码中的每个方法进行测试,就需要实现一个继承unittest.TestCase 测试用例类。

# -*- coding: utf-8 -*-

import unittest

from com.unit_test.check_user_info import CheckUserInfo

class CheckUserInfoTestCase(unittest.TestCase):

    def __init__(self, *args, **kwargs):
        super(CheckUserInfoTestCase, self).__init__(*args, **kwargs)
        self.check_user_info = CheckUserInfo()

    @classmethod
    def setUpClass(cls):
        print('setUpClass\n\n')

    @classmethod
    def tearDownClass(cls):
        print('tearDownClass')

    def setUp(self):
        print('setUp')

    def tearDown(self):
        print('tearDown\n')

    def test_check_mail(self):
        print('test_check_mail')
        self.assertEqual(True, self.check_user_info.check_mail('aa@bb'))
        self.assertEqual(False, self.check_user_info.check_mail('aa.bb'))

    def test_check_pwd_len(self):
        print('test_check_pwd_len')
        self.assertEqual(True, self.check_user_info.check_pwd_len('12345678'))
        self.assertEqual(False, self.check_user_info.check_pwd_len(''))
        self.assertEqual(False, self.check_user_info.check_pwd_len('1'))
        self.assertEqual(True, self.check_user_info.check_pwd_len('123456789'))

    def test_check_pwd_contain_letter(self):
        print('test_check_pwd_contain_leter')
        self.assertEqual(True, self.check_user_info.check_pwd_contain_leter('1qazXSW@'))
        self.assertEqual(False, self.check_user_info.check_pwd_contain_leter('12345678'))
        self.assertEqual(False, self.check_user_info.check_pwd_contain_leter(''))

    def test_check_pwd_contain_num(self):
        print('test_check_pwd_contain_num')
        self.assertEqual(True, self.check_user_info.check_pwd_contain_num('1qazXSW@'))
        self.assertEqual(False, self.check_user_info.check_pwd_contain_num('qwertasdfg'))
        self.assertEqual(False, self.check_user_info.check_pwd_contain_num(''))

    def aaa(self):
        print('test_check_pwd_contain_num')
        self.assertEqual(True, self.check_user_info.check_pwd_contain_num('1qazXSW@'))
if __name__ == '__main__':
    unittest.main()

执行结果如下:

...
setUpClass

setUp
test_check_mail
tearDown

setUp
test_check_pwd_contain_leter
tearDown

setUp
test_check_pwd_contain_num
tearDown

setUp
test_check_pwd_len
tearDown

tearDownClass

Ran 4 tests in 0.013s

OK

Process finished with exit code 0

使用“unittest.main()”,通过观察上面的测试代码以及执行结果,可以发现:

testXXX方法

使用“unittest.main()”执行测试用例时,每个测试方法必须以“test”开头,否则Unittes无法识别。例如,上面代码中的“aaa”方法未执行。

测试方法执行顺序

每个测试方法执行顺序并不是按照代码的书写顺序执行,而是按照前缀“test”后的字母顺序执行的。

setUp()方法

setUp()方法一般用来准备测试环境,在每个测试方法执行之前执行。

tearDown()方法

tearDown()方法一般用来清理测试环境,在每个测试方法执行之后执行,且不论测试方法执行是否成功。

setUpClass()方法

setUpClass()方法一般用来在所有测试方法执行之前准备一次测试环境,必须使用“@classmethod”装饰符,在所有测试方法执行前执行。

tearDownClass()

tearDownClass()方法一般用来在所有测试方法结束之后清理一次测试环境,必须使用“@classmethod”装饰符,在所有测试方法都执行完成否执行。

skip装饰器

不希望执行某个测试方法时,可以使用skip装饰器。skip装饰器共有三种:unittest.skip(reason)、unittest.skipIf(condition, reason)、unittest.skipUnless(condition, reason)。

  • unittest.skip(reason):无条件跳过
# -*- coding: utf-8 -*-

import unittest

from com.unit_test.check_user_info import CheckUserInfo

class CheckUserInfoTestCase(unittest.TestCase):

    ......

    @unittest.skip('skip test_bbb method')
    def test_bbb(self):
        print('test_bbb')

......

部分执行结果如下:

...
Skipped: skip test_bbb method
...
  • unittest.skipIf(condition, reason):当condition为True时跳过
# -*- coding: utf-8 -*-

import unittest

from com.unit_test.check_user_info import CheckUserInfo

class CheckUserInfoTestCase(unittest.TestCase):

    ......

    @unittest.skipIf(1<2, 'skip test_ccc method')
    def test_ccc(self):
        print('test_ccc')

......

部分执行结果如下:

...
Skipped: skip test_ccc method
...
  • unittest.skipUnless(condition, reason):当condition为False时跳过
# -*- coding: utf-8 -*-

import unittest

from com.unit_test.check_user_info import CheckUserInfo

class CheckUserInfoTestCase(unittest.TestCase):

    ......

    @unittest.skipUnless(1>2, 'skip test_ddd method')
    def test_ddd(self):
        print('test_ddd')

......

部分执行结果如下:

...
Skipped: skip test_ddd method
...

那么,如何控制测试方法的执行顺序呢?如何执行非“test”开头的测试方法?接下来了解一下TestSuite。

TestSuite — 单元测试用例合集

通过上面的测试用例代码,可以发现无法指定测试方法的顺序。那么通过unittest.TestSuite,就可以按照业务逻辑顺序执行测试方法。

# -*- coding: utf-8 -*-

import unittest

from com.unit_test.check_user_info_test_case import CheckUserInfoTestCase


if __name__ == '__main__':
    suite = unittest.TestSuite()
    tests = [
        CheckUserInfoTestCase('test_check_mail'),
        CheckUserInfoTestCase('test_check_pwd_len'),
        CheckUserInfoTestCase('test_check_pwd_contain_letter'),
        CheckUserInfoTestCase('test_check_pwd_contain_num')
    ]
    suite.addTests(tests)

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

执行结果如下:

...
Ran 4 tests in 0.002s

OK
setUpClass


setUp
test_check_mail
tearDown

setUp
test_check_pwd_len
tearDown

setUp
test_check_pwd_contain_leter
tearDown

setUp
test_check_pwd_contain_num
tearDown

tearDownClass

Process finished with exit code 0

通过数组指定测试方法的执行顺序

tests = [
    CheckUserInfoTestCase('test_check_mail'),
    CheckUserInfoTestCase('test_check_pwd_len'),
    CheckUserInfoTestCase('test_check_pwd_contain_letter'),
    CheckUserInfoTestCase('test_check_pwd_contain_num')
]
suite.addTests(tests)

通过addTests()方法指定测试方法的执行顺序

suite.addTest(CheckUserInfoTestCase('test_check_mail'))
suite.addTest(CheckUserInfoTestCase('test_check_pwd_len'))
suite.addTest(CheckUserInfoTestCase('test_check_pwd_contain_letter'))
suite.addTest(CheckUserInfoTestCase('test_check_pwd_contain_num'))

无法指定测试方法执行顺序的unittest.TestLoader()

通过unittest.TestLoader()加载TestCase,测试方法是按照前缀“test”后的字母顺序执行的。

suite.addTests(unittest.TestLoader().loadTestsFromTestCase(CheckUserInfoTestCase))

执行非“test”开头的测试方法

在TestCase中的示例代码中,可以看到,测试方法名不是以“test”开头时是不会被执行的。那么,通过TestSuite就可以完美解决这个问题。

suite.addTest(CheckUserInfoTestCase('aaa'))
查看评论

Delphi中的线程类--之(3)

Delphi中的线程类 猛禽[Mental Studio]http://mental.mentsu.com 之三说完构造函数,再来看析构函数:destructor TThread.Destroy;be...
  • Raptor
  • Raptor
  • 2003-12-02 22:00:00
  • 4485

Python 单元测试库 Mock unittest

Mock是什么Mock这个词在英语中有模拟的这个意思,因此我们可以猜测出这个库的主要功能是模拟一些东西。准确的说,Mock是Python中一个用于支持单元测试的库,它的主要功能是使用mock对象替代掉...
  • z_johnny
  • z_johnny
  • 2017-04-07 17:16:37
  • 1201

Python 中 unittest(单元测试)的学习

python 中单元测试 unittest模块学习 unittest主要概念:1. 一个TestCase的实例就是一个测试用例。什么是测试用例呢?就是一个完整的测试流程,包括测试前准备环境的搭建(...
  • qq_25621385
  • qq_25621385
  • 2015-05-14 15:01:45
  • 4062

Python unittest单元测试框架

unittest是python下的单元测试框架,是java JUnit的python版本, 跟其它语言下的单元测试框架风格类似,unittest支持自动化测试、共享setup和shutdown代码、测...
  • guoyajie1990
  • guoyajie1990
  • 2017-08-08 23:21:55
  • 277

Python单元测试(doctest和unittest)

Python单元测试,doctest和unittest,以及测试用例的编写。
  • u012151283
  • u012151283
  • 2017-08-23 21:30:38
  • 648

Python之自动单元测试之一(unittest使用实例)

软件的测试是一件非常乏味的事情,在测试别人编写的软件时尤其如此,程序员通常都只对编写代码感兴趣,而不喜欢文档编写和软件测试这类"没有创新"的工作。既然如此,为什么不让程序员在编写软件时自己加入一些用于...
  • xiaosongbk
  • xiaosongbk
  • 2016-10-21 17:51:57
  • 8567

python单元测试之unittest框架使用总结

一、什么是单元测试 单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。 比如对于函数abs(),我们可以编写的测试用例为: (1)输入正数,比如1、1.2、0.99,期待返...
  • xiaoquantouer
  • xiaoquantouer
  • 2017-07-13 18:21:14
  • 2795

unittest实现HTTP接口功能测试

实际上H5页面接口测试与传统的接口测试的基本思路是一致的,就是通过数据准备(包括正常和异常的参数)、接口调用、结果观察这三个流程来验证接口的功能逻辑符合预期,并且对于未预期的异常情况有较好的容错性和鲁...
  • TMQ1225
  • TMQ1225
  • 2016-07-26 14:56:07
  • 3305

Python基础-单元测试-unittest.TestCase(setUp和tearDown)

单元测试单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。待测试用例# 待测试用例 class MyDict(dict): def __init__(self, **kw)...
  • su749520
  • su749520
  • 2017-12-16 17:33:04
  • 539

Python单元测试——深入理解unittest

单元测试的重要性就不多说了,可恶的是python中有太多的单元测试框架和工具,什么unittest, testtools, subunit, coverage, testrepository, nos...
  • hackerain
  • hackerain
  • 2014-04-19 10:32:09
  • 118481
    个人资料
    持之以恒
    等级:
    访问量: 13万+
    积分: 2082
    排名: 2万+
    最新评论