全面介绍python中很常用的单元测试框架unitest


Posted in Python onDecember 14, 2020

1、unitest主要功能模块介绍

unitest主要包含TestCase、TestSuite、TestLoader、TextTestRunner、TextTestResult这几个功能模块。

  • TestCase:一个TestCase实例就是一个测试用例,一个测试用例就是一个完整的测试流程,包括测试前环境的搭建,测试代码的执行,以及测试后环境的还原或者销毁。元测试的本质也就在这里,一个测试用例是一个完整的测试单元,可以对某一具体问题进行检查验证。
  • TestSuite:多个测试用例集合在一起就是TestSuite,TestSuite也可以嵌套TestSuite。
  • TestLoader:TestLoader的作用是将Testcase加载到TestSuite中。
  • TextTestRunner:TextTestRunner是用来执行测试用例的,其中的run(test)会执行TestSuite/TestCase中的run(result)方法。
  • TextTestResult:TextTestResult用来保存测试结果,其中包括运行了多少测试用例,成功了多少,失败了多少等信息。

整个流程为:写好TestCase,然后由TestLoader加载TestCase到TestSuite,然后由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中。

2、实例介绍

首先准备几个待测的方法,写在test_func.py中。

def add(a, b):
  return a + b


def multi(a, b):
  return a * b


def lower_str(string):
  return string.lower()


def square(x):
  return x ** 2

准备好几个待测的方法之后,为这些方法写一个测试用例,写入our_testcase.py中。

import unittest
from test_func import *


class TestFunc(unittest.TestCase):
  """Test test_func.py"""

  def test_add(self):
    """Test func add"""
    self.assertEqual(3, add(1, 2))
    self.assertNotEqual(3, add(1, 3))

  def test_multi(self):
    """Test func multi"""
    self.assertEqual(6, multi(2, 3))
    self.assertNotEqual(8, multi(3, 3))

  def test_lower_str(self):
    """Test func lower_str"""
    self.assertEqual("abc", lower_str("ABC"))
    self.assertNotEqual("Dce", lower_str("DCE"))

  def test_square(self):
    """Test func square"""
    self.assertEqual(17, square(4)) # 这里故意设计一个会出错的用例,测试4的平方等于17,实际上并不等于。
    self.assertNotEqual(35, square(6))


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

这里写好之后,进入命令行终端,执行python our_testcase.py,执行结果如下。

...F
======================================================================
FAIL: test_square (__main__.TestFunc)
Test func square
----------------------------------------------------------------------
Traceback (most recent call last):
 File "our_testcase.py", line 27, in test_square
  self.assertEqual(17, square(4))
AssertionError: 17 != 16

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

FAILED (failures=1)

这里分析一下这个执行结果。首先能够看到一共运行了4个测试用例,失败了1个,并且给出了失败原因,AssertionError: 17 != 16,这是我们故意留下的错误漏洞,被测试用例测试出来了。

第一行...F中,一个点.代表测试成功,F代表失败,我们的测试结果中,前三个成功了,第四个失败了,总共是四个测试,其余的符号中E代表出错,S代表跳过。

特别说明的一点是,测试的执行顺序跟方法的顺序没有关系,四个测试是随机先后执行的。

每个测试方法编写的时候,都要以test开头,比如test_square,否则是不被unitest识别的。

在unitest.main()中加上verbosity参数可以控制输出的错误报告的详细程序,默认是1,如果设为0,则不输出每一用例的执行结果,即上面的第一行的执行结果内容。如果设为2,则输出详细的执行结果。

修改our_testcase.py中主函数。

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

执行结果如下。

test_add (__main__.TestFunc)
Test func add ... ok
test_lower_str (__main__.TestFunc)
Test func lower_str ... ok
test_multi (__main__.TestFunc)
Test func multi ... ok
test_square (__main__.TestFunc)
Test func square ... FAIL

======================================================================
FAIL: test_square (__main__.TestFunc)
Test func square
----------------------------------------------------------------------
Traceback (most recent call last):
 File "our_testcase.py", line 27, in test_square
  self.assertEqual(17, square(4))
AssertionError: 17 != 16

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

FAILED (failures=1)

可以看到,每一个用例的详细执行情况以及用例名,用例描述均被输出了出来,在测试方法下加代码示例中的"""Doc String""",在用例执行时,会将该字符串作为此用例的描述,加合适的注释能够使输出的测试报告更加便于阅读。

3、组织TestSuite

按照上面的测试方法,我们无法控制用例执行的顺序,这样显然是不合理的,因为在一些测试过程中,我们肯定需要控制先测试某些用例,再测试某些用例,这些用例有先后的因果关系。在这里,我们就需要用到TestSuite。我们添加到TestSuite中的case是会按照添加的顺序执行的。

还有一个问题是,我们现在只有一个测试文件,我们直接执行该文件即可,但如果有多个测试文件,怎么进行组织,总不能一个个文件执行吧,答案也在TestSuite中。

新建一个文件,test_suite.py。

import unittest
from our_testcase import TestFunc

if __name__ == '__main__':
  suite = unittest.TestSuite()
  tests = [TestFunc("test_square"), TestFunc("test_lower_str"), TestFunc("test_multi")]
  suite.addTests(tests)
  runner = unittest.TextTestRunner(verbosity=2)
  runner.run(suite)

执行结果如下。

test_square (our_testcase.TestFunc)
Test func square ... FAIL
test_lower_str (our_testcase.TestFunc)
Test func lower_str ... ok
test_multi (our_testcase.TestFunc)
Test func multi ... ok

======================================================================
FAIL: test_square (our_testcase.TestFunc)
Test func square
----------------------------------------------------------------------
Traceback (most recent call last):
 File "/Users/luyuze/projects/test/our_testcase.py", line 27, in test_square
  self.assertEqual(17, square(4))
AssertionError: 17 != 16

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

FAILED (failures=1)

这样,用例执行的顺序就是按照我们添加进去的顺序来执行的了。

上面使用的是TestSuite的addTests()方法,并直接传入TestCase列表,也有一些其他的方法可以向TestSuite中添加用例。

# 直接用addTest方法添加单个TestCase
suite.addTest(TestMathFunc("test_multi"))


# 使用loadTestFromName,传入模块名.TestCase名,下面俩方法效果相同
suite.addTests(unittest.TestLoader().loadTestsFromName('our_testcase.TestFunc'))
suite.addTests(unittest.TestLoader().loadTestsFromNames(['our_testcase.TestFunc']))


# loadTestsFromTestCase(),传入TestCase
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestFunc))

用TestLoader的方法是无法对case进行排序的,同时,suite中也可以套suite。

4、输出文件

用例组织好了,但是结果只能输出到控制台,这样没办法查看之前的执行记录,我们想将结果输出到文件。

修改test_suite.py。

import unittest
from our_testcase import TestFunc

if __name__ == '__main__':
  suite = unittest.TestSuite()
  tests = [TestFunc("test_square"), TestFunc("test_lower_str"), TestFunc("test_multi")]
  suite.addTests(tests)

  with open('UnitestTextReport.txt', 'a') as f:
    runner = unittest.TextTestRunner(stream=f, verbosity=2)
    runner.run(suite)

5、测试前后的处理

在之前的测试中,可能会存在这样的问题:如果要在测试之前准备环境,测试完成之后做一些清理怎么办?这里需要用到的是setUp()和tearDown()。

修改our_testcase.py。

import unittest
from test_func import *


class TestFunc(unittest.TestCase):
  """Test test_func.py"""
  
  def setUp(self):
    print("do something before testcase")

  def test_add(self):
    """Test func add"""
    self.assertEqual(3, add(1, 2))
    self.assertNotEqual(3, add(1, 3))

  def test_multi(self):
    """Test func multi"""
    self.assertEqual(6, multi(2, 3))
    self.assertNotEqual(8, multi(3, 3))

  def test_lower_str(self):
    """Test func lower_str"""
    self.assertEqual("abc", lower_str("ABC"))
    self.assertNotEqual("Dce", lower_str("DCE"))

  def test_square(self):
    """Test func square"""
    self.assertEqual(17, square(4))
    self.assertNotEqual(35, square(6))
    
  def tearDownClass(self):
    print("do something after testcase")


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

执行结果:

test_add (__main__.TestFunc)
Test func add ... do something before testcase
do something after testcase
ok
test_lower_str (__main__.TestFunc)
Test func lower_str ... do something before testcase
do something after testcase
ok
test_multi (__main__.TestFunc)
Test func multi ... do something before testcase
do something after testcase
ok
test_square (__main__.TestFunc)
Test func square ... do something before testcase
do something after testcase
FAIL

======================================================================
FAIL: test_square (__main__.TestFunc)
Test func square
----------------------------------------------------------------------
Traceback (most recent call last):
 File "our_testcase.py", line 30, in test_square
  self.assertEqual(17, square(4))
AssertionError: 17 != 16

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

FAILED (failures=1)

可以发现setUp()和tearDown()在每个case前后都执行了一次。如果要在所有case执行之前和所有case执行之后准备和清理环境,我们可以使用setUpClass() 与 tearDownClass()。

class TestFunc(unittest.TestCase):
  """Test test_func.py"""

  @classmethod
  def setUpClass(cls):
    print "This setUpClass() method only called once."

  @classmethod
  def tearDownClass(cls):
    print "This tearDownClass() method only called once too."

6、跳过case

如果我们临时想要跳过某个case不执行,unitest也有相应的方法。

1、skip装饰器

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

import unittest
from test_func import *


class TestFunc(unittest.TestCase):
  """Test test_func.py"""

  @unittest.skip('do not run this case')
  def test_add(self):
    """Test func add"""
    self.assertEqual(3, add(1, 2))
    self.assertNotEqual(3, add(1, 3))

  def test_multi(self):
    """Test func multi"""
    self.assertEqual(6, multi(2, 3))
    self.assertNotEqual(8, multi(3, 3))

  def test_lower_str(self):
    """Test func lower_str"""
    self.assertEqual("abc", lower_str("ABC"))
    self.assertNotEqual("Dce", lower_str("DCE"))

  def test_square(self):
    """Test func square"""
    self.assertEqual(17, square(4))
    self.assertNotEqual(35, square(6))


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

执行结果:

test_add (__main__.TestFunc)
Test func add ... skipped 'do not run this case'
test_lower_str (__main__.TestFunc)
Test func lower_str ... ok
test_multi (__main__.TestFunc)
Test func multi ... ok
test_square (__main__.TestFunc)
Test func square ... FAIL

======================================================================
FAIL: test_square (__main__.TestFunc)
Test func square
----------------------------------------------------------------------
Traceback (most recent call last):
 File "our_testcase.py", line 28, in test_square
  self.assertEqual(17, square(4))
AssertionError: 17 != 16

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

FAILED (failures=1, skipped=1)

结果显示为,总共执行4个测试,1个失败,1个被跳过。

skip装饰器一共有三个 unittest.skip(reason)、unittest.skipIf(condition, reason)、unittest.skipUnless(condition, reason),skip无条件跳过,skipIf当condition为True时跳过,skipUnless当condition为False时跳过。

2、TestCase.skipTest()方法

class TestFunc(unittest.TestCase):
  """Test test_func.py"""

  def test_add(self):
    """Test func add"""
    self.skipTest("do not run this case")
    self.assertEqual(3, add(1, 2))
    self.assertNotEqual(3, add(1, 3))

效果与第一种是一样的。

以上就是全面介绍python中很常用的单元测试框架unitest的详细内容,更多关于python 单元测试框架unitest的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
python绘制简单折线图代码示例
Dec 19 Python
python学习基础之循环import及import过程
Apr 22 Python
python多进程使用及线程池的使用方法代码详解
Oct 24 Python
pycharm在调试python时执行其他语句的方法
Nov 29 Python
python pandas写入excel文件的方法示例
Jun 25 Python
Python Web框架之Django框架Form组件用法详解
Aug 16 Python
python 单线程和异步协程工作方式解析
Sep 28 Python
python 实现仿微信聊天时间格式化显示的代码
Apr 17 Python
利用Python将图片中扭曲矩形的复原
Sep 07 Python
Python性能测试工具Locust安装及使用
Dec 01 Python
利用Python+OpenCV三步去除水印
May 28 Python
python字符串的一些常见实用操作
Apr 06 Python
python读写数据读写csv文件(pandas用法)
Dec 14 #Python
详解Python中@staticmethod和@classmethod区别及使用示例代码
Dec 14 #Python
Python 找出英文单词列表(list)中最长单词链
Dec 14 #Python
Python 排序最长英文单词链(列表中前一个单词末字母是下一个单词的首字母)
Dec 14 #Python
Python实现Kerberos用户的增删改查操作
Dec 14 #Python
python-地图可视化组件folium的操作
Dec 14 #Python
python多线程和多进程关系详解
Dec 14 #Python
You might like
php的array_multisort()使用方法介绍
2012/05/16 PHP
php判断正常访问和外部访问的示例
2014/02/10 PHP
windwos下使用php连接oracle数据库的过程分享
2014/05/26 PHP
laravel 5 实现模板主题功能(续)
2015/03/02 PHP
百度地图经纬度转换到腾讯地图/Google 对应的经纬度
2015/08/28 PHP
WordPress过滤垃圾评论的几种主要方法小结
2016/07/11 PHP
火狐textarea输入法的bug的触发及解决
2013/07/24 Javascript
JS实现两表格里数据来回转移的方法
2015/05/28 Javascript
简单实现jquery焦点图
2016/12/12 Javascript
vue.js实现仿原生ios时间选择组件实例代码
2016/12/21 Javascript
node.js实现微信JS-API封装接口的示例代码
2017/09/06 Javascript
nodejs实现连接mongodb数据库的方法示例
2018/03/15 NodeJs
微信小程序登录换取token的教程
2018/05/31 Javascript
vue-resource请求实现http登录拦截或者路由拦截的方法
2018/07/11 Javascript
Vue 按照创建时间和当前时间显示操作(刚刚,几小时前,几天前)
2020/09/10 Javascript
浅谈python数据类型及类型转换
2017/12/18 Python
Python绘制的二项分布概率图示例
2018/08/22 Python
快速解决pyqt5窗体关闭后子线程不同时退出的问题
2019/06/19 Python
python使用递归的方式建立二叉树
2019/07/03 Python
Python 画出来六维图
2019/07/26 Python
python爬虫 urllib模块发起post请求过程解析
2019/08/20 Python
python实现字典嵌套列表取值
2019/12/16 Python
Python 实现opencv所使用的图片格式与 base64 转换
2020/01/09 Python
matplotlib对象拾取事件处理的实现
2021/01/14 Python
html5构建触屏网站之touch事件介绍
2013/01/07 HTML / CSS
专注澳大利亚特产和新西兰特产的澳洲中文网:0061澳洲制造
2019/03/24 全球购物
小区消防演习方案
2014/02/21 职场文书
社会实践的活动方案
2014/08/22 职场文书
农民工工资支付承诺书
2015/05/04 职场文书
2015年乡镇民政工作总结
2015/05/13 职场文书
合同补充协议书
2016/03/24 职场文书
2016年教育局“我们的节日——端午节”主题活动总结
2016/04/01 职场文书
JS数组的常用方法整理
2021/03/31 Javascript
解决sql server 数据库,sa用户被锁定的问题
2021/06/11 SQL Server
用 Python 定义 Schema 并生成 Parquet 文件详情
2021/09/25 Python
深入理解pytorch库的dockerfile
2022/06/10 Python