Unittest单元测试框架

一,前言

1,单元测试

软件测试通常按阶段划分为:单元测试,集成测试,系统测试。单元测试(unit testing)是指对软件中的最小可测试单元进行检查和验证。 单元测试中单元的含义,单元就是人为规定的最小的被测功能模块,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中能够指一个窗口或一个菜单等。在实际项目中,单元测试每每由开发人员完成。html

2,单元测试框架

  • 单元测试其实就是构造数据使用一段代码去测试另外一段代码,理论上来讲,不使用单元测试框架也能进行单元测试。但若是用于单元测试的代码(即测试用例)增多,在没有测试框架的状况下会变得拥挤、不可管理,这个时候引入测试框架就变得尤其重要。python

  • 单元测试框架提供了一种统一的编程模型,能够将测试定义为一些简单的类,这些类中的方法能够调用但愿测试的应用程序代码。利用单元测试框架,能够很轻松地插入、设置和分解有关测试的功能,能够直观方便地管理测试用例。web

  • 主流的单元测试框架,如Java的Junit、TestNg,python的Unittest、Pyunit、Pytest,通用的自动化测试框架Robot Framework等。编程

3,单元测试框架做用

  • 提供用例组织与执行浏览器

  • 提供丰富的断言方法app

  • 提供丰富的日志与测试结果框架

二,Unittest 测试框架

1,Unittest 简介

Unittest是Python自带的单元测试框架,不只适用于单元测试,还可用于Web、Appium、接口自动化测试用例的开发与执行。该测试框架可组织执行测试用例,而且提供丰富的断言方法,判断测试用例是否经过,并最终生成测试结果。函数

Unittest官方文档:https://docs.python.org/3/library/unittest.html单元测试

2,Unittest 核心要素

  • TestCase:即测试用例,Unittest提供testCase类来编写测试用例,一个TestCase的实例就是一个测试用例。一条测试用例就是一个完整的测试流程,包括测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown),经过运行一条测试用例,能够对某一个问题进行验证。测试

  • Fixture:即测试固件,用于测试用例环境的搭建和销毁。在测试步骤执行前须要为该测试用例准备环境(SetUp),如启动app或打开浏览器,测试步骤执行后须要恢复环境 (TearDown),如关闭app或浏览器,这时候就须要用到Fixture,使代码更简洁。

  • TestSuite:即测试套件,把须要执行的测试用例集合在一块儿就是TestSuite。使用TestLoader来加载TestCase到TestSuite中。

  • TextTestRunner:即测试执行器,用于执行测试用例。该模块中提供run方法执行TestSuite中的测试用例,并返回测试用例的执行结果,如运行的用例总数、用例经过数、用例失败数。

  • report:即测试报告。unittest框架没有自带的用于生成测试报告的模块或接口,须要使用第三方的扩展模块HTMLTestRunner。

3,Unittest 断言

断言在自动化测试脚本中是很重要的内容,只有设置正确合适的断言才能获取正确的测试结果。Unittest框架提供了本身的断言方法,以下:

断言方法
判断内容
assertEqual(a, b) 判断 a == b
assertNotEqual(a, b) 判断 a != b
assertTrue(x) 判断 bool(x) is True
assertFalse(x) 判断 bool(x) is False
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
assertIsInstance(a, b) 判断 isinstance(a, b)
assertNotIsInstance(a, b) 判断 not isinstance(a, b)

注意

  • 若是断言成功则该条测试用例经过,断言失败则该条测试用例执行失败,且会抛出AssertionError错误。

  • 以上提供的断言方法中,都有一个msg参数,默认为None。若是msg参数有对应的值,则断言失败后该msg的值会做为失败信息返回,如 assertEqual(a, b, msg="a与b不相等!") 。

三,Unittest 框架使用方法

1,测试需求

测试对象:构造一个类Math,其中包含整数的加、减法运算。

calculator.py

class Math():
    def __init__(self, a, b):
        self.a = int(a)
        self.b = int(b)

    def sum(self):
        '''和'''
        return self.a + self.b

    def sub(self):
        '''差'''
        return self.a - self.b

测试需求:对Math类进行单元测试。接下来针对这个测试需求,使用unittest框架编写测试用例。

项目目录结构:后面的例子中,项目结构以下所示。

2,编写TestCase(测试用例)

在Unittest框架下建立测试用例,步骤以下:

  • 1),导入unittest模块。

  • 2),建立测试类。测试类的命名不作要求,但须要继承unittest.TestCase类

  • 3),添加setUp()、tearDown()函数,即测试固件。固然还有setUpClass()、tearDownClass() 函数,区别后面会有介绍。

  • 4),定义测试方法,即测试用例。测试方法名称必须以test开头,不然测试时该方法将不会被执行。测试方法里须要添加断言。

  • 5),调试执行测试用例。执行当前模块的测试用例时,调用unittest.main()方法,该方法会搜索该模块下全部以test开头的测试用例方法,并执行。其余方法后面介绍。

针对测试需求,编写测试用例。目录结构以下:

test_sum.py

import unittest
from calculator import Math

class SumTest(unittest.TestCase):
    '''测试Math类中的sum函数'''
    def setUp(self):
        print("开始执行测试用例{}...".format(self))

    def test_sum01(self):
        m = Math(3, 4)
        self.assertEqual(m.sum(), 7)

    def test_sum02(self):
        m = Math(2, 8)
        self.assertEqual(m.sum(), 11)

    def tearDown(self):
        print("测试用例{}执行结束...".format(self))

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

运行结果:

.F
======================================================================
FAIL: test_sum02 (__main__.SumTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:/Users/xiaoqq/Desktop/test_project/demo/testSum.py", line 15, in test_sum02
    self.assertEqual(m.sum(), 11)
AssertionError: 10 != 11

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)
开始执行测试用例test_sum01 (__main__.SumTest)...
测试用例test_sum01 (__main__.SumTest)执行结束...
开始执行测试用例test_sum02 (__main__.SumTest)...
测试用例test_sum02 (__main__.SumTest)执行结束...

Process finished with exit code 0

结果显示:

  • test_sum01经过,test_sum02失败。点"."表示经过,"F"表示失败。

  • 测试类中每一个测试方法(即测试用例)执行前,都先执行setUp()方法,每一个测试方法执行完毕后都要执行tearDown()方法。

  • 断言失败会返回一个AssertionError。

3,在测试用例中添加Fixture(测试夹具)

3.1,测试夹具Fixture的做用对象

在Unittest框架下的测试用例中,使用Fixture有两种方法,做用于两个范围:

  • setUp()、tearDown(),做用于测试方法。即测试类下的每一个测试方法执行前先运行setUp(),每一个测试方法执行完毕后都要执行tearDown()方法,如testSum.py示例。

  • setUpClass()、tearDownClass(),做用于测试类。即只在整个测试类执行开始时运行setUpClass(),测试类下全部测试方法执行完后运行tearDownClass()。

3.2,setUpClass()、tearDownClass() 举例

test_sum.py修改以下,运行

class SumTest(unittest.TestCase):
    '''测试Math类中的sum函数'''
    @classmethod
    def setUpClass(cls):
        print("开始执行测试用例{}...".format(cls))

    def test_sum01(self):
        m = Math(3, 4)
        self.assertEqual(m.sum(), 7)

    def test_sum02(self):
        m = Math(2, 8)
        self.assertEqual(m.sum(), 11)

    @classmethod
    def tearDownClass(cls):
        print("测试用例{}执行结束...".format(cls))

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

运行结果:

开始执行测试用例<class '__main__.SumTest'>...
测试用例<class '__main__.SumTest'>执行结束...
.F

结果显示,setUpClass()、tearDownClass() 都只运行了一次。注意,这里须要使用装饰器@classmethod

3.3,注意

  • 在测试用例中,setUp() 或 setUpClass() 作初始化的工做,不是必须有这个函数。一样tearDown() 和 tearDownClass() 只作清理的工做,在测试类中不是必需要有。

  • 须要测试的Math类,代码比较简单,没有真正须要用到测试夹具的地方,所以这只是个用法演示。

  • 实际自动化过程当中,如Web端UI自动化,通常会将建立浏览器实例放在setUp() ,用例执行完后须要关闭浏览器,将关闭浏览器操做放在tearDown()方法里。示例以下:

import unittest
from selenium import webdriver

class BaiduTest(unittest.TestCase):
    def setUp(self):
        '''打开浏览器,进入百度页面'''
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        self.driver.get('https://www.baidu.com')

    def test_01(self):
        print("操做步骤")

    def tearDown(self):
        '''关闭浏览器'''
        self.driver.quit()

4,将测试用例添加至TestSuite(测试套件)

testSum.py模块中,使用了unittest.main()方法执行当前模块里的测试用例。除此以外,Unittest还能够经过测试套件构造测试用例集,再执行测试用例。构造TestSuite经常使用的方法以下:

4.1,方法一:加载测试用例

  • 1),先经过unittest.TestSuite() 建立测试套件实例对象。

  • 2),再经过addTest() 往测试套件里添加单个测试用例,或经过addTests([...]) 添加多个测试用例(列表中为用例方法名)。

  • 3),执行测试套件里的测试用例

run.py示例:

import unittest
# 导入测试用例模块
from testcase.test_sum import TestDemo

# 第一步:建立TestSuite实例
suite = unittest.TestSuite()

# 第二步:将测试用例添加至TestSuite
# 方式1,添加单条测试用例
suite.addTest(TestDemo('test_sum01'))   # addTest()里参数格式为:测试类('测试方法')
suite.addTest(TestDemo('test_sum02'))

# 方式2,添加多条测试用例
suite.addTests([TestDemo('test_sum01'), TestDemo('test_sum02')])

4.2,方法二:加载测试用例类

  • 1),先经过unittest.TestSuite() 建立测试套件实例对象。

  • 2),再经过unittest.TestLoader()建立加载对象,加载测试用例类

run.py示例:

import unittest
# 导入测试用例模块
from testcase.test_sum import TestDemo

# 建立TestSuite实例
suite = unittest.TestSuite()
# 建立一个加载对象
loader = unittest.TestLoader()
suite.addTest(loader.loadTestsFromTestCase(TestDemo))

4.3,方法三:加载指定路径里的测试用例

  • 1),经过unittest.defaultTestLoader.discover()将指定路径的测试用例加载至测试用例集。注意:这里不须要建立unittest.TestSuite对象

  • 2),以下代码所示,test_dir为指定路径。pattern=test_*.py 表示加载以test_开头的模块中的测试用例,指定运行某模块时pattern参数指定全名便可,如:pattern='test_sum.py'。路径跟pattern参数均可以自定义。

run.py示例

import unittest

test_dir = './testcase'
suite = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')

编写测试项目时,推荐使用方法三。固然还有其余方法,很少作介绍。

5,使用TextTestRunner执行测试用例

unittest框架执行测试用例以前,需先建立TextTestRunner实例,再调用该实例的run()方法。

# 建立TextTestRunner实例
runner = unittest.TextTestRunner()
# 使用run()方法运行测试套件(即运行测试套件中的全部用例)
runner.run(suite)

run.py修改为以下示例:

import unittest

test_dir = './testcase'
suite = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')

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

运行run.py,结果以下:

.F
======================================================================
FAIL: test_sum02 (test_sum.TestDemo)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\xiaoqq\Desktop\test_project\demo\test_sum.py", line 15, in test_sum02
    self.assertEqual(m.sum(), 11)
AssertionError: 10 != 11

----------------------------------------------------------------------
Ran 2 tests in 0.000s

FAILED (failures=1)
开始执行测试用例test_sum01 (test_sum.TestDemo)...
测试用例test_sum01 (test_sum.TestDemo)执行结束...
开始执行测试用例test_sum02 (test_sum.TestDemo)...
测试用例test_sum02 (test_sum.TestDemo)执行结束...

Process finished with exit code 0

四,输出测试报告

unittest框架执行测试用例完成后会在控制台输出如上的结果,但实际测试过程当中,咱们须要输出测试报告,这个时候咱们须要使用第三方模块。

1,HTMLTestRunner

HTMLTestRunner模块能够直接生成html格式的报告

  • 下载地址:http://tungwaiyip.info/software/HTMLTestRunner.html

  • 下载后须要修改:

    • 94行引入的名称要改,从 import StringIO修改为 import io
    • 539行 self.outputBuffer = StringIO.StringIO()修改为self.outputBuffer=io.StringIO()
    • 631行 print >>sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)修改为print (sys.stderr, '\nTime Elapsed: %s' %(self.stopTime-self.startTime))
    • 642行,if not rmap.has_key(cls): 修改为 if not cls in rmap:
    • 766行的uo = o.decode('latin-1'),修改为 uo=o
    • 772行,把 ue = e.decode('latin-1') 直接改为 ue = e
  • 存放路径:将修改完成的模块存放在Python路径下Lib目录里便可。

run.py示例代码以下:

# -*- coding:utf-8 -*-
# @author: 给你一页白纸

import time
import unittest
import HTMLTestRunner

# 获取当前时间并指定时间格式,用于测试报告命名
now = time.strftime("%Y-%m-%d_%H_%M_%S")
# 测试报告存储路径
report_dir = './report/'
# 建立报告文件,并以写的形式打开文件,用于写入报告内容
fp = open(report_dir + now + "_report.html", 'wb')
# 初始化一个HTMLTestRunner实例对象,用来生成报告
runner = HTMLTestRunner.HTMLTestRunner(stream=fp,
                                       title="App自动化测试报告",
                                       description="测试用例状况")

# 定义测试用例路径
test_dir='./testcase'
# 加载测试用例
suite = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')
# 执行测试用例
runner.run(suite)
fp.close()

运行run.py,会看到report中生成了html文件,即为测试报告

浏览器打开该文件,内容以下:

从报告内容中看出HTMLTestRunner.HTMLTestRunner()方法中参数所对应的内容,能够根据项目的实际须要指定参数内容。

2,美化版测试报告

在HTMLTestRunner基础上美化过的报告

run.py示例代码以下:

# -*- coding:utf-8 -*-
# @author: 给你一页白纸

import time
import unittest
import BSTestRunner

# 获取当前时间并指定时间格式,用于测试报告命名
now = time.strftime("%Y-%m-%d_%H_%M_%S")
# 测试报告存储路径
report_dir = './report/'
# 建立报告文件
fp = open(report_dir + now + "_report.html", 'wb')
# 初始化一个HTMLTestRunner实例对象,用来生成报告
runner = BSTestRunner.BSTestRunner(stream=fp,
                                       title="App自动化测试报告",
                                       description="测试用例状况")

# 定义测试用例路径
test_dir='./testcase'
# 加载测试用例
suite = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')
# 执行测试用例
runner.run(suite)
fp.close()

生成报告样式以下:

两种报告模板可根据本身喜爱任意选择。