温欣爸比

  • 主页
  • Alfred Workflow
  • 《Vim 练级手册》
  • 常用命令
  • 代码笔记
  • 合辑
  • 在线工具
所有文章 友链 关于我

温欣爸比

  • 主页
  • Alfred Workflow
  • 《Vim 练级手册》
  • 常用命令
  • 代码笔记
  • 合辑
  • 在线工具

Python 单元测试模块 unittest

2018-03-21

不写测试用例的程序员,不是好测试



  • 第一个 TestCase
  • 函数运行状态
  • 跳过某条测试
  • 环境的准备和还原
  • 使用 TestSuite
  • 输出到文件
  • 检测方法


以前在 Java 中 junit 是必用的单元测试框架,而 Python 中的 junit 就是 unittest

第一个 TestCase

少废话,先看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: wxnacy(wxnacy@gmail.com)

import unittest

class Test(unittest.TestCase):

def test_hello_world(self):
self.assertEqual(11, len('Hello World'))

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

1
2
3
4
5
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

我们实现了一个最简单的测试用例,unittest 中最基本的结构就是 TestCase,新建一个类继承 unittest.TestCase,类中每个 test 开头的方法都是一个测试函数(一定要 test 开头)

一个测试通过的用例,运行结果除了分割线,共三行

  • 第一行,每个测试函数的运行状态,共四种状态,. 为成功
  • 第二行,运行的测试条数及用时
  • 第三行,测试最终结果,一条失败则为 FAILED,并显示各种状态的条数

函数运行状态

测试函数的运行状态分四种

  • . 成功
  • F 失败
  • E 出错
  • s 跳过
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: wxnacy(wxnacy@gmail.com)
# Description:

import unittest

class Test(unittest.TestCase):

def add(self):
return 1 + 1

def divide(self):
return 0 / 0

def test_1(self):
'''test_1 func'''
self.assertEqual(2, self.add())

def test_2(self):
'''test_2 func'''
self.assertEqual(3, self.add())

def test_3(self):
'''test_3 func'''
self.assertEqual(3, self.divide())

@unittest.skip("skip this test case")
def test_4(self):
'''test_4 func'''
self.assertEqual(3, self.divide())

if __name__ == "__main__":
unittest.main()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.FEs
======================================================================
ERROR: test_3 (__main__.Test)
test_3 func
----------------------------------------------------------------------
Traceback (most recent call last):
File "simple.py", line 26, in test_3
self.assertEqual(3, self.divide())
File "simple.py", line 14, in divide
return 0 / 0
ZeroDivisionError: division by zero

======================================================================
FAIL: test_2 (__main__.Test)
test_2 func
----------------------------------------------------------------------
Traceback (most recent call last):
File "simple.py", line 22, in test_2
self.assertEqual(3, self.add())
AssertionError: 3 != 2

----------------------------------------------------------------------
Ran 4 tests in 0.001s

FAILED (failures=1, errors=1, skipped=1)

这是一个包含四种状态的例子,test_3(), test_2() 分别报错和测试失败,在结果中给出了出错的位置和具体原因

现在的测试程序运行结果是简要信息,这取决于 unittest.main() 方法中的 verbosity 参数,默认值为 1,0 则不输出每条结果,2 则为详细信息,如下:

1
2
3
4
5
6
7
8
9
test_1 (__main__.Test)
test_1 func ... ok
test_2 (__main__.Test)
test_2 func ... FAIL
test_3 (__main__.Test)
test_3 func ... ERROR
test_4 (__main__.Test)
test_4 func ... skipped 'skip this test case'
...

跳过某条测试

在上面的例子中,test_4() 方法是跳过的,unittest 中共有三种装饰器可以跳过程序

  • `@unittest.skip(reason)` 无条件跳过
  • `@unittest.skipIf(condition, reason)` 当条件为真时跳过
  • `@unittest.skipUnless(condition, reason)` 当条件为假时跳过
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    # Author: wxnacy(wxnacy@gmail.com)
    # Description:

    import unittest

    class Test(unittest.TestCase):

    def add(self):
    return 1 + 1

    @unittest.skip("skip test_1 case")
    def test_1(self):
    '''test_1'''
    self.assertEqual(2, self.add())

    def test_2(self):
    '''test_2'''
    self.skipTest('skip test_2 case')
    self.assertEqual(3, self.add())

    @unittest.skipIf(1 == 1, 'skip test_3 case')
    def test_3(self):
    self.assertEqual(3, self.add())

    @unittest.skipUnless(1 != 1, 'skip test_4 case')
    def test_4(self):
    self.assertEqual(3, self.add())

    if __name__ == "__main__":
    unittest.main(verbosity=2)
1
2
3
4
5
6
7
8
9
10
11
test_1 (__main__.Test)
test_1 ... skipped 'skip test_1 case'
test_2 (__main__.Test)
test_2 ... skipped 'skip test_2 case'
test_3 (__main__.Test) ... skipped 'skip test_3 case'
test_4 (__main__.Test) ... skipped 'skip test_4 case'

----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK (skipped=4)

在详细结果中,会将跳过原因打印出来,并且在测试程序中,使用 self.skipTest(resaon) 也可以跳过程序

环境的准备和还原

一个完整的测试用例还需要一个环节,准备环境和还原环境,使用 setUp(), tearDown() 两个方法来完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: wxnacy(wxnacy@gmail.com)

import unittest

class Test(unittest.TestCase):

def add(self):
return 1 + 1

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

def tearDown(self):
print('begin tearDown')

def test_1(self):
self.assertEqual(2, self.add())
print('run test_1')

def test_2(self):
self.assertEqual(2, self.add())
print('run test_2')

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

1
2
3
4
5
6
7
8
9
10
11
begin setUp
run test_1
begin tearDown
.begin setUp
run test_2
begin tearDown
.
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

从打印结果中可以看出,每次执行一个测试方法前后都会执行准备和还原操作,如果你想要一个 TestCase 只执行一次准备和还原操作,则需要使用这两个方法

1
2
3
4
5
6
7
@classmethod
def setUpClass(cls):
print('begin setUpClass')

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

至此,我们已经经历一个完整的 TestCase 是什么样的,它有环境准备工作(setUp),运行测试代码(run),还原环境(tearDown),根据条件跳过(skip),共同组成了一个元测试(unit test),这样就是完整的测试用例

同时,我们需要开始考虑两个问题

  • 怎样控制测试程序的执行顺序
  • 如果同时执行多个测试用例

使用 TestSuite

针对刚才提出的问题,我们需要引入 TestSuite 的概念,多个测试用例组合起来,就是 TestSuite,TestSuite 也可以嵌套 TestSuite

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: wxnacy(wxnacy@gmail.com)

import unittest

class Test(unittest.TestCase):

def add(self):
return 1 + 1

def test_1(self):
self.assertEqual(2, self.add())

def test_2(self):
self.assertEqual(2, self.add())

def test_3(self):
self.assertEqual(2, self.add())


if __name__ == "__main__":
suite = unittest.TestSuite()

tests = [Test("test_1"), Test("test_3"), Test("test_2")]
suite.addTests(tests)

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

1
2
3
4
5
6
7
8
test_1 (__main__.Test) ... ok
test_3 (__main__.Test) ... ok
test_2 (__main__.Test) ... ok

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

OK

在将多个 TestCase 调价到 TestSuite 中后,我们用到了 unittest.TextTestRunner() 方法,他会执行 TestSuite 或 TestCase 测 run() 方法,并把结果输出到 TextTestResult 中,后续我们将怎样将结果在打印到文件中

然后如你所见,我们已经可以控制测试程序的执行顺序,同样的,通过这样的方式,我们还可以进行多个 TestCase 同时测试

输出到文件

每次测试结果都输出控制台,从可读性和持久化上都是个问题,我们需要想办法将结果打印到文件中,这里我们需要借助下第三方文件 HTMLTestRunner ,但是原版只支持 python2,我做了下改动,适配了 python3,地址为:https://github.com/wxnacy/study/blob/master/python/unittest_demo/HTMLTestRunner.py

使用方法请见修改 HTMLTestRunner.py 可以在 Python3 运行

检测方法

我们在刚才的例子中只用到了 assertEqual() 方法来检测结果是否相同,还有很多方法可以用来检测测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
assertEqual(a, b)	    # a == b
assertNotEqual(a, b) # a != b
assertTrue(x) # bool(x) is True
assertFalse(x) # bool(x) is False
# 3.1 新加
assertIs(a, b) # a is b
assertIsNot(a, b) # a is not b
assertIsNone(x) # x is None
assertIsNotNone(x) # x is not None
assertIn(a, b) # a in b
assertNotIn(a, b) # a not in b
# 3.2 新加
assertIsInstance(a, b) # isinstance(a, b)
assertNotIsInstance(a, b) # not isinstance(a, b)

更多方法见文档

  • unittest — Unit testing framework
  • Python单元测试框架
  • Python单元测试——深入理解unittest
  • Python必会的单元测试框架 —— unittest
最近更新
Alfred Workflow 命令行帮助工具
最近热读
Go 判断数组中是否包含某个 item
Vim 高级功能 vimgrep 全局搜索文件
办理北京工作居住证的一些细节
Go 语法错误:Non-declaration statement outside function body
Mac 电脑查看字体文件位置
扫码关注公众号,或搜索公众号“温欣爸比” 及时获取我的最新文章
赏

谢谢你请我喝咖啡

支付宝
微信
  • python
JavaScript 获取地址相关信息
修改 HTMLTestRunner.py 可以在 Python3 运行
  1. 1. 第一个 TestCase
  2. 2. 函数运行状态
  3. 3. 跳过某条测试
  4. 4. 环境的准备和还原
  5. 5. 使用 TestSuite
  6. 6. 输出到文件
  7. 7. 检测方法
© 2017 - 2022 温欣爸比 京ICP备15062634号 总访问量3747次 访客数3698人次 本文总阅读量4次
Hexo Theme Yilia by Litten
  • 所有文章
  • 友链
  • 关于我

tag:

  • python
  • flask
  • javascript
  • docker
  • 工具
  • openresty
  • 微信
  • java
  • hexo
  • 杂谈
  • vim
  • git
  • mysql
  • http
  • linux
  • mac
  • tmux
  • ssh
  • 算法
  • 开发
  • node
  • 杂文
  • jinja2
  • maven
  • spring
  • 北京
  • 生活
  • springboot
  • react
  • shell
  • graphql
  • iterm
  • expect
  • nginx
  • sqlalchemy
  • html
  • electron
  • vagrant
  • elastic
  • 宝贝
  • ansible
  • css
  • jquery
  • go
  • markdown
  • awk
  • redis
  • leetcode
  • zsh
  • 漫威
  • ssr
  • android
  • ffmpeg
  • chrome
  • vmware
  • youtube
  • windows
  • jupyter
  • excel
  • jq
  • Mac
  • Homebrew
  • mongo
  • py2
  • HomeBrew
  • movie
  • nodejs

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

  • Guru99
每天看书
每天背单词
每天一篇
写写代码
听听周杰伦
爱爱老婆