Unittest是python单元测试框架,是受到 JUnit 的启发,与其他语言中的主流单元测试框架有着相似的风格。其支持测试自动化,配置共享和关机代码测试。支持将测试样例聚合到测试集中,并将测试与报告框架独立。它不仅适用于单元测试,还在自动化测试领域占有一席之地。借助它组织执行测试用例,使用它提供的丰富的断言方法进行测试结果的比对,并结合HTMLTestRunner生成测试报告完成整个自动化测试流程。
二.简单使用示例
创建被测类calc.py
class count: def __init__(self, a, b): self.a = a self.b = b #计算加法 def add(self): return self.a + self.b #计算减法 def Subtraction(self): return self.a - self.b
通过unittest单元测试框架编写单元测试用例 test.py
from calc import count import unittest class CountTest(unittest.TestCase): def setUp(self) -> None: print("in case setUP") def tearDown(self) -> None: print("in case tearDow") def test_add(self): c = count(4, 5) self.assertEqual(c.add(), 9) def test_subtraction(self): f = count(9, 8) self.assertEqual(f.Subtraction(), 1) if __name__ == '__main__': CountTest.main()
说明:
-
首先引入unittest模块,创建testcount类继承unitest的testcase类。
-
setUp():用于测试用例执行前的初始化工作,与tearDown()相呼应,用于执行后的善后工作。
-
test_add中调用count类并传入要计算的数,通过调用add()方法得到两数相加的返回值,这里不再使用繁琐的异常处理,而是调用unitest框架所提供的assertEqual()对add()的返回值进行断言判断两者是否相等()。assertEqual()方法是由testcase类继承而来的。
-
main():unittest提供了全局的main()方法,使用它可以方便的将一个单元测试模块变成可以直接运行的测试脚本。main()方法使用Testloader类来搜索所有包含在该模块中以“test”命名开头的测试方法。
-
name:作为模块的内置属性,简单地说就是.py文件的调用方式。.py文件有两种使用方式作为模块调用和直接使用,如果它等于“main”就表示是直接使用
三.unittest模块说明
-
TestCase:一个TestCase的实例就是一个测试用例,是一个完整的测试流程,包括测试前准备环境的搭建(setUp),实现测试过程的代码(run),测试后环境的还原(tearDown).
-
Test Suite:把多个测试用例集合在一起来执行。可以通过addTest加载TestCase到Test Suite(测试套件)中,从而返回一个TestSuite实例。
-
Test Runner:测试的执行,通过TextTestRunner类提供的run()方法来执行Test Suite/TestCase。Test Runner可以使用图形界面,文本界面,或者返回一个特殊的值的方式来表示测试执行的结果。
-
Test Fixture:对一个测试用例环境的初始化和清除。通过覆盖TestCase的setUp()和tearDown()方法来实现。tearDown()为下一个测试用例提供一个干净的环境。
四.断言方法
unittest框架的TestCase类提供的断言方法用于测试结果的判断
1.判断第一个参数和第二个参数是否相等
-assertEqual(first,second,msg=None) # 如果不相等则测试失败,msg为可选参数,用于定义测试失败时打印的信息。 self.assertEqual(j.add(),15,msg="测试结果不等于15")
格式:-assertNotEqual(first,second,msg=None)则与之相反
2.判断表达式是true(或false)
创建count.py用于判断质数的
def is_prime(n): if n<=1: return False for i in range(2,n): if n % i = = 0: return False retun True
测试用例:调用is_prime()函数和unittest
self.assertTrue(is_prime(7),msg="is not prime")
3.判断第一个参数是否在第二个参数中,简言之也就是检查第二个参数是否包含第一个参数。
-assertIn(first,second,msg=None)
-assertNotIn(first,second,msg=None)
测试用例(部分):
def test_case(self): a = "hello" b = "hello world" self.assertIn(a,b,msg="a is not in b")
4.判断第一个参数和第二个参数是否为同一对象
-assertIs(first,second,msg=None)
-assertIsNot(first,second,msg=None)
5.判断表达式是否为None对象
-assertIsNone(expr,msg=None)
-assertIsNotNone(expr,msg=None)
6.判断obj是否为cls的一个实例格式:
-assertIsInstance(obj,cls,msg=None)
-assertNotIsInstance(obj,cls,msg=None)
五.测试用例的组织
1.初始化和清除
fixtures可以形象的看作是夹心饼干外层的两片饼干,这两片饼干就是setUp/tearDown,中间的心就是测试用例,除此以外,unittest还提供了更大范围的fixtures,例如对于测试类和模块的fixtures。
-
-
-
setUpModule/tearDownModule:在整个模块的开始和结束时被执行。
-
setUpClass/tearDownClass: 在测试类的开始和结束时被执行。
-
setUp/tearDown:在测试用例的开始与结束时被执行 注意:setUpClass/tearDownClass的写法稍有不同,首先通过@classmethod进行装饰,其次方法的参数为cls,也可以是别的。每一个上面都要进行装饰
-
-
2.创建测试套件
测试套件是用例的集合,在实际测试工作中,经常会听说冒烟测试、全量测试、回归测试等测试版本,每个版本所选取的测试用例也是不一样,
如果测试用例数量比较多,可以将这些用例按照所测试的功能进行拆分,分散到不同的测试文件中 最后再创建用于执行所有测试用例的runtest.py文件。
方法1:可以通过addTest()加载TestCase到TestSuite中。用于少量的测试用例
from test import CountTest import unittest #创建套件 suite = unittest.TestSuite() #方法一:添加单个测试用例到套件之中 suite.addTest(CountTest('test_add')) suite.addTest(CountTest('test_subtraction')) runner = unittest.TextTestRunner() runner.run(suite)
方法2:同时添加多个测试用例到套件之中
from test import CountTest import unittest #创建套件 suite = unittest.TestSuite() #测试用例集合,放在列表中 case = [CountTest('test_add'), CountTest('test_subtraction')] #将测试用例集合添加到套件中 suite.addTests(case) #创建运行器 runner = unittest.TextTestRunner() #执行套件 runner.run(suite)
方法3:模糊匹配给套件指定需要执行的测试用例
使用TestLoader类提供的discover()方法来加载所有的测试用例。正常情况下,不需要创建这个类的实例,unittest提供了可以共享的defaultTestLoader类,可以使其子类和方法创建实例,discover()方法就是其中之一。
discover(start_dir,pattern='test*.py',top_level_dir=None)
start_dir:要测试的模块名或测试用例的目录
pattern='test.py':表示用例文件名的匹配原则,此处文件名以“test”开头的“.py”类型的文件,“”表示任意多个字符。
top_level_dir=None:测试模块的顶层目录,如果没有顶层目录,默认为None.
#指定路径 test_dir = './' #指定项目根目录下,所有的以tes开头的.py文件中的测试用例 discover = unittest.defaultTestLoader.discover(start_dir=test_dir, pattern='tes*.py') #创建运行器 runner = unittest.TextTestRunner() #执行套件 runner.run(discover)
方法4:添加整个类中的测试用例到套件中
#CountTest是一个测试用例类 from test import CountTest import unittest #创建套件 suite = unittest.TestSuite() #将CountTest类中所有用例添加到套件 suite.addTests(unittest.TestLoader().loadTestsFromTestCase(CountTest)) #创建运行器 runner = unittest.TextTestRunner() #执行套件 runner.run(suite)
3.用例执行的原则
unittest框架默认根据ASCII码的顺序加载测试用例,数字与字母的顺序为:0-9,A-Z,a-z。所以TestAdd会优于TestBdd类被执行,test_aaa()方法会优于test_ccc被执行,因而它并没有按照用例从上到下的顺序执行。
对于测试目录和测试文件来说,unittest框架同样是按照这个规则来加载测试用例的。 如果按照指定的顺序执行,可以通过TestSuite类的addTest()方法按照一定的顺序加载。不能默认main()方法了。需要构造测试集,然后通过run()方法执行测试。
注意:discover()的加载测试用例的规则与main()方法相同,所以只能通过测试用例的命名来提高被执行的优先级。
4.执行多级的用例
discover()方法中的start_dir只能加载当前目录下的.py文件,如果加载子目录下的.py文件,需在每个子目录下放一个init.py文件。
5.跳过测试和预期失败
unittest提供了实现某些需求的装饰器,在执行测试用例时每个装饰前面加@符号。
-
@unittest.skip(reason):无条件的跳过装饰的测试,说明跳过测试的原因
-
@unittest.skipIf(condition,reason):跳过装饰的测试,如果条件为真。
-
@unittest.skipUnless(condition,reason):跳过装饰的测试,除非条件为真。
-
@unittest.expectedFailure():测试标记为失败,不管执行结果是否失败,统一标记为失败,但不会抛出错误信息。
-
@unittest.expectedFailure #如果断言失败,不计入执行case数目中
六.测试报告生成
1.HTMLTestRunner介绍
HTMLTestRunner是python标准库unittest单元测试框架的一个拓展,它生成易于使用的HTML测试报告。HTMLTestRunner下载地址:http://tungwaiyip.info/software/HTMLTestRunner.html,在GitHub上也有一个人在这个基础上做过改动的,可以自己去下载即可 下载下来是一个HTMLTestRunner.py文件,选中后单击鼠标右键,在弹出的快捷菜单中选择目标另存为,将它保存到本地。安装方法是将其复制到python安装目录下即可。
windows:将下载的文件保存到...\python36\Lib目录下
HTMLTestRunner.py文件是基于python2开发的,若要支持python3环境需要对其中的部分内容进行修改。目前可以在GitHub上找到可供python3使用已经修改过的版本
2.生成HTML测试报告
-
将HTMLTestRunner模块用import导入进来
-
通过open()方法以二进制写模式打开当前目录下的result.html,如果没有,则自动创建该文件。
-
调用HTMLTestRunner模块下的HTMLTestRunner类,stream指定测试报告文件,title用于定义测试报告的标题, description用于定义测试报告的副标题。
-
最后,通过HTMLTestRunner的run()方法来运行测试套件中所组装的测试用例。
-
通过close()关闭测试报告文件。
from test import CountTest import unittest import os from HTMLTestRunner import HTMLTestRunner import time report_title = '冒烟测试' report_desc = '本次测试描述' report_path = './report/' #创建测试报告存放的位置和名字 report_file = report_path + 'report8.html' #判断项目中是否有report_path所对应的目录,如果没有则新建。有了直接存放结果即可 if not os.path.exists(report_path): os.mkdir(report_path) else: pass #创建测试套件 suite = unittest.TestSuite() #执行套件内容,将结果写入 open(report_file,'wb') as report: #添加用例到套件 suite.addTests(unittest.TestLoader().loadTestsFromTestCase(CountTest)) #生成运行器 runner = HTMLTestRunner(stream=report, title=report_title, description=report_desc) #执行套件 runner.run(suite) # 关闭文件流,不关的话生成的报告是空的(看) # report.close()
3.测试报告名称
在每次运行测试之前,都要手动修改报告的名称,如果忘记修改,就会把之前的报告覆盖,为了使每次生成的报告名称都不重复并且有意义,可以在报告名称中加入当前时间,这样生成的报告既不会重叠,又能更清晰的知道报告的生成时间。
time.time():获取当前时间戳 比如:1601886309.2652433
time.ctime():当前时间的字符串形式 比如:'Mon Oct 5 16:25:31 2020'
time.localtime():当前时间的struct_time形式 比如:time.struct_time(tm_year=2020, tm_mon=10, tm_mday=5, tm_hour=16, tm_min=25......等等。
time.strftime("%Y-%m-%d %H:%M:%S"):用来获得当前时间,可以将时间格式化为字符串。比如:'2020-10-05 16:27:17'
方法:通过时间操作的方法以指定的格式获取当前时间,将当前时间的字符串赋值给rtime变量,将rtime通过字符串格式化操作拼接到生成的测试报告的文件名中,再次运行测试用例,即可生成测试报告文件名。
import unittest, os, time from webDriver.driver_factory import DriverFactory from common.HTMLTestRunner_cn import HTMLTestRunner report_title = '冒烟测试' report_desc = '本次测试描述' report_path = './report/' rtime = time.strftime("%Y%m%d%H%M%S") #创建测试报告存放的位置和名字 report_file = report_path + f'report{rtime}.html' # #判断项目中是否有report_path所对应的目录,如果没有则新建。有了直接存放结果即可 if not os.path.exists(report_path): os.mkdir(report_path) else: pass #添加用例到套件,采用模糊匹配,在test_case目录下查找 test_dir = './test_case' #指定项目根目录下,所有的以tes开头的.py文件中的测试用例 discover = unittest.defaultTestLoader.discover(start_dir=test_dir, pattern='test_*.py') #执行套件内容,将结果写入 with open(report_file, 'wb') as report: #生成运行器 runner = HTMLTestRunner(stream=report, title=report_title, description=report_desc) #执行套件 runner.run(discover) #测试套件执行结束关闭浏览器 DriverFactory.driver.quit()