
笔者:风起怨江南 出处:https://blog.csdn.net/JackMengJin 笔者原创,文章转载需注明,如果喜欢请点赞+关注,感谢支持!
导读:python官方自带的单元测试框架——unittest框架。上手简单但功能强大,是python自动化测试必须要掌握的技能,希望本文对大家学习unittest框架有些许帮助。
目录
unittest框架入门
一、测试阶段——单元测试
单元测试
集成测试
系统测试
验收测试
二、unittest框架四件套
TestCase
断言
TestSuite
TestRunner
Fixture
三、扩展内容
FunctionTestCase
Skipping
subTest
四、基础总结
最终效果
unittest框架是python内置的单元测试框架,类似Java的JUnit框架、.Net的NUnit框架等。
unittest单元测试框架是受到 JUnit 的启发,与其他语言中的主流单元测试框架有着相似的风格。其支持测试自动化,配置共享和关机代码测试。支持将测试样例聚合到测试集中,并将测试与报告框架独立。
点击进入官网:
python自动化测试的学习中unittest框架是基础,要理解unittest框架的工作原理就要先清楚单元测试的概念。
单元测试(Unit Testing)又称为模块测试,简单来说就是对软件里最小的单元进行测试。
单元:函数、类、方法、一段代码块都可以称之为单元,单元是应用的最小可测试部件。单元测试其实就是测的类和函数,是否是按照自己定义好的去执行。
单元测试意义:为了从底层发现bug,减少合成后出现的问题。优点就是投入小,收益大,能够精准更早的发现问题。黑盒测试方法进行功能测试,而白盒测试方法进行单元测试,一般由开发自测。
而除了单元测试还有集成测试、系统测试和验收测试。
单元测试举例
用一个简单的登陆功能函数来说明下:
def login_check(username=None, password=None): """ 登录校验 :param username:账号 :param password:密码 :return:dict type """ if username and password: if username == "Jack" and password == "Ab987654": return {"code": 0, "msg": "登录成功"} else: return {"code": 1, "msg": "账号或密码错误"} else: return {"code": 1, "msg": "参数不能为空"} 设计几个场景的测试用例来对该函数进行单元测试。
场景1:账号密码正确
入参:账号JackMeng 密码Ab987654
预期结果:{'code':0,'msg':'登陆成功'}
场景2:账号正确,密码错误
入参:账号JackMeng 密码Ab654321
预期结果:{'code':0,'msg':'账户或密码不正确'}
场景3:账号错误,密码正确
入参:账号JackMenk 密码Ab987654
预期结果:{'code':0,'msg':'账户或密码不正确'}
场景4:账号为空
入参:账号空 密码Ab987654
预期结果:{'code':0,'msg':'所有参数不能为空'}
场景5:密码为空
入参:JackMeng 密码空
预期结果:{'code':0,'msg':'所有参数不能为空'}
unittest具体实现
模块导入:import unittest
import unittestdef login_check(username=None, password=None): """ 登录校验 :param username:账号 :param password:密码 :return:dict type """ if username and password: if username == "Jack" and password == "Ab987654": return {"code": 0, "msg": "登录成功"} else: return {"code": 1, "msg": "账号或密码错误"} else: return {"code": 1, "msg": "参数不能为空"}class TestLogin(unittest.TestCase): def test_login_success(self): """ 账号密码正确 """ username = "Jack" password = "Ab987654" expect_res = "登录成功" actual_res = login_check(username, password).get("msg") self.assertEqual(expect_res, actual_res) def test_login_error_password(self): """ 错误密码 """ username = "Jack" password = "Ab123456" expect_res = "账号或密码错误" actual_res = login_check(username, password).get("msg") self.assertEqual(expect_res, actual_res) def test_login_empty_password(self): """ 密码为空 """ username = "Jack" password = "" expect_res = "参数不能为空" actual_res = login_check(username, password).get("msg") self.assertEqual(expect_res, actual_res)if __name__ == '__main__': unittest.main() 单个的功能模块测试通过之后,才是集成测试(Intergration Testing)。而黑盒测试,白盒测试和灰盒测试方法都可以进行集成测试。
集成也叫组装测试或联合测试。 在单元测试的基础上,将所有模块按照设计要求(如根据结构图)组装成为子系统或系统,进行集成测试。
定义:单元测试通过后把单个功能模块集成起来做集成测试。
目的:虽然软件的所有模块已经在单元测试中进行了测试,但由于以下原因仍然存在错误:
系统测试是将需测试的软件作为整个基于计算机系统的一个元素,与计算机硬件、某些支持软件、数据和人员等其他系统元素及环境结合在一起测试。
定义:系统测试(System Testing)是一系列不同类型的测试用来针对集成软件的全部工作,包括测试完全集成的软件系统。
测试内容:系统测试是测试整个系统的功能和性能,相较之集成测试则是测试各个单元模块之间的接口。
测试角度:系统测试是偏重于业务的角度进行测试,也就是纯黑盒测试方法验证。对比下集成测试则偏重于技术的角度进行测试。
定义:对要交付的产品根据验收细则进行验收测试(Acceptance Testing),也称为交付测试,验收测试通过后会交给客户验收产品。
验收测试是向客户表明系统能够像预定要求那样工作。经过集成测试和系统测试后已经按照设计把所有的模块组装成一个完整的软件系统,接口错误也已经基本排除了,接着就应该进一步验证软件的有效性,这就是验收测试的任务,即软件的功能和性能如同客户所合理期待的那样。
Beta 测试:Beta测试由最终用户实施,通常开发(或其他非最终用户)组织对其的管理很少或不进行管理。Beta测试是所有验收测试策略中最主观的。
β测试:β测试是软件的多个用户在一个或多个用户的实际使用环境下进行的测试。开发者通常不在测试现场,Beta测试不能由程序员或测试员完成。
unittest框架的核心内容就是unittest四件套:TestCase、TestSuite、Fixture、TextTestRunner。
单元测试的构建单位是testcase:独立的、包含执行条件与正确性检查的方案。
定义:在unittest中测试用例表示为 unittest.TestCase 的实例。通过编写TestCase的子类或使用 FunctionTestCase 编写你自己的测试用例。
TestCase表示为测试用例,一个TestCase的实例就是一个测试用例。一个TestCase实例的测试代码完全可以独立运行,或与其它任意组合任意数量的测试用例一起运行。
TestCase最简单的子类需要实现一个测试方法以执行特定的测试代码,命名以test开头的方法:
import unittestclass TestLogin(unittest.TestCase): def test_login_success(self): """ 账号密码正确 """ username = "Jack" password = "Ab987654" expect_res = "登录成功" actual_res = login_check(username, password).get("msg") self.assertEqual(expect_res, actual_res) 这里TestCase提供的assert*() 方法进行断言处理。
定义:自动去检查预期结果和实际结果是否相等。
判断一句话是否为真,如果为真就是成功,为假就是失败。
断言机制:测试不通过会引发一个带有说明信息的异常,并且unittest会将这个测试用例标记为测试不通过。判断预期结果和实际结果是否存在某种关系,也就是断言 assert。测试用例通过,预期结果等于实际结果,说明断言成功,用例也就成功,反之为失败。
assertEqual:判断两个参数是否相等。
assertTrue:表达式。
self.assertTrue(4>3)等价于self.assertGreater(4,3)
断言优点:assert断言简单直接,如果失败直接报错并抛出异常。单用例出现异常,不会影响其他用例,能保证每个测试用例的独立性。
异常报错:AssertionError,断言只关注用例失败的操作,对于unittest框架来说一个用例一个断言。后续会单独出文章详细讲解 assert*() 各种断言方法的使用。
定义:TestSuite表示为测试套件,一系列的测试用例集合在一起生成测试集。而多个测试运行的顺序由内置字符串排序方法也就是ASCII映射关系表对测试名进行排序的结果决定。
执行顺序:由于是通过ASCII映射关系决定的顺序,所以可以增加数字来改变执行顺序。
unittest.TestSuite()用法
addTest():添加单个测试用例方法。
# 测试套件suite = unittest.TestSuite()# 测试用例加载到套件case = test_demo.TestCount("test_success_add1")suite.addTest(case) addTests([..]):添加多个测试用例方法,方法名存在一个列表。
case1 = test_demo.TestCount("test_success_add1")case2 = test_demo.TestCount("test_success_add2")suite.addTests([case1, case2]) TestLoader
定义:加载器,用来加载TestCase到TestSuite,和TestSuite配合使用。
初始化加载器对象:unittest.TestLoader()
loader = unittest.TestLoader()
unittest.TestLoader()用法
loadTestsFromTestCase(测试类名):添加一个测试类。
# 添加测试用例类loader = unittest.TestLoader()suite.addTest(loader.loadTestsFromTestCase(test_demo.TestCount))
loadTestsFromModule(模块名):FromModule(),通过模块加载。
loader.loadTestsFromModule(),参数传入列表:[suite_login,suite_register]
suit_total = unittest.TestSuite()suit_total.addTests([suite_login,suite_register])suite_login = loader.loadTestsFromModule(test_login)suite_register = loader.loadTestsFromModule(test_register)
discover(测试用例的所在目录):指定目录去加载,会自动寻找这个目录下所有符合命名规则(test*)的测试用例。test
loader = unittest.TestLoader()suite.addTest(loader.discover(r"F:codepython_codejack_api"))
module和discover区别:discover将所有用例都放在suit集合里,而module是单独加载的方式,需要一个个手动添加,最后再合入。
定义:TestRunner是一个用于执行和输出测试结果的组件,表示为测试运行程序。用来执行测试用例,Runner运行器可能使用图形接口或者文本接口来生成测试报告。
TextTestRunner:TextTestRunner是unittest自带的TestRunner,可以生成.txt测试报告。了解即可,一般用HTMLTestRunner代替TextTestRunner。
with open('report.txt', mode='w', encoding='utf-8') as f: runner = unittest.TextTestRunner(f) runner.run(suite) 结果表示—— . f E
通过:.表示通过,断言成功。
失败:f表示失败,断言失败,发现bug或者预期结果不对时用f表示。
错误:E表示程序错误,自己代码错误而不是断言失败。
HTMLTestRunner:HTMLTestRunner是 Python 标准库的 unittest 框架的一个扩展,需要下载(需要的朋友可以私信)。HTMLTestRunner可以生成HTML测试报告,用wb二进制模式写入文件。生成的HTML格式的测试报告效果在最后展示。
with open('reports.html',mode='wb') as f: runner = HTMLTestRunner( f, title= '测试报告', description= '描述', tester= 'JackMeng' ) runner.run(test_suit) 定义:测试夹具,也称为测试脚手架。测试用例环境的搭建和销毁,测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown)。
运行机制
1.setUp(self):前置条件执行每个用例前要做的事情。
def setUp(self): print("检查数据库能否连上,等等操作") 2.setUpClass(cls):一个测试类只执行一次的前置条件,由于是类方法,setUpClass(cls)和tearDownClass(cls)使用时必须带@classmethod(),。
3.tearDown(self):后置条件,执行每个用例后要做的事情,比如断开数据库,善后工作。即使测试方法引发异常也会调用。
4.tearDownClass(cls):一个测试类只执行一次的后置条件。
特点:setUp和tearDown每一个用例都会执行一遍。
setUpClass(cls)和tearDownClass(cls)在一个测试类,只执行一次的后置。
import unittestclass TestCount(unittest.TestCase): def setUp(self) -> None: print("每条用例开始时都会执行!") def tearDown(self) -> None: print("每条用例结束后都会执行!") @classmethod def setUpClass(cls) -> None: print("只在开始的时候运行一次!") @classmethod def tearDownClass(cls) -> None: print("只在结束的时候运行一次!") def test_success_add1(self): """ 正确 """ num_a = 1 num_b = 2 expect_res = 3 actual_res = num_a + num_b self.assertEqual(expect_res, actual_res) def test_success_add2(self): """ 正确 """ num_a = 5 num_b = 5 expect_res = 10 actual_res = num_a + num_b self.assertEqual(expect_res, actual_res)if __name__ == '__main__': unittest.main() 运行效果如下:
定义:如果需要用unittest运行已经存在的测试用例,也就是复用已有的测试代码,则要用到FunctionTestCase类去实现,可以创建等价的测试用例。
具体用法:
def testEat(): Eating = eat()testcase = unittest.FunctionTestCase(Eating, setUp=eatDB, tearDown=deleteEatDB)
用FunctionTestCase可以快速将现有的测试转换成基于 unittest 的测试,好处是不需要再把已有的每个测试函数转化为一个TestCase的子类,同时还支持设置前置和后置函数。
python3.1以后,unittest框架支持跳过单个或整组的测试用例,同时可以将测试标注为预期失败,当这些被标注的用例断言失败后是不计入最终测试结果的。
import unittestclass TestCount(unittest.TestCase): def test_success(self): """ 正确 """ num_a = 1 num_b = 2 expect_res = 3 actual_res = num_a + num_b self.assertEqual(expect_res, actual_res) def test_error(self): """ 错误 """ num_a = 1 num_b = 2 expect_res = 2 actual_res = num_a + num_b self.assertEqual(expect_res, actual_res)if __name__ == '__main__': unittest.main()
加上skip装饰器标记为预期失败后,再次运行:
@unittest.skip("demonstrating skipping") def test_error(self): """ 错误 """ num_a = 1 num_b = 2 expect_res = 2 actual_res = num_a + num_b self.assertEqual(expect_res, actual_res)
同样的方法也可以跳过测试类,甚至setup。
被跳过的测试的 setUp() 和 tearDown() 不会被运行。被跳过的类的 setUpClass() 和 tearDownClass() 不会被运行。被跳过的模组的 setUpModule() 和 tearDownModule() 不会被运行。
最后献上skip装饰器的源码:
def skip(reason): """ Unconditionally skip a test. """ def decorator(test_item): if not isinstance(test_item, type): @functools.wraps(test_item) def skip_wrapper(*args, **kwargs): raise SkipTest(reason) test_item = skip_wrapper test_item.__unittest_skip__ = True test_item.__unittest_skip_why__ = reason return test_item if isinstance(reason, types.FunctionType): test_item = reason reason = '' return decorator(test_item) return decorator
subTest上下文管理器是python3.4版本新功能,用来当测试差异很小的一组参数时,如果不希望在第一次就断言失败而终止后面参数的测试时,可以使用subTest方法:
import unittestclass TestNum(unittest.TestCase): def test_num(self): num = 7 for i in range(10): self.assertEqual(num, i)if __name__ == '__main__': unittest.main()
类似上面的例子,运行后第一个参数(0)断言失败后后面的参数就会被终止:
使用subTest的效果不言而喻:
unittest框架其他更多扩展的用法可以点击文章开头的官网链接学习。
1.unittest导包:import unittest
2.继承unittest.TestCase:测试用例需要继承unittest.TestCase。在unittest 中测试用例表示为 unittest.TestCase 的实例,通过编写TestCase的子类或使用FunctionTestCase编写测试用例。
3.unittest运行:用例执行runner.run()。
unittest.TextTestRunner()runner = unittest.TextTestRunner()runner.run(test_suit)
运行unittest需要在代码空白行运行,否则有概率无法触发unittest运行机制。推荐直接在main方法里运行unittest.main()。调用unittest.main() 执行所有用TestCase分组的测试。
if __name__ == '__main__': unittest.main()
4.命名要求:模块名称要以test开头,下划线拼接,比如test_demo.py。
而测试用例(也就是TestCase类)要求首字母大写,驼峰命名,如下:
class TestLogin(unittest.TestCase): pass
5.assert断言:unittest框架的TestCase基类提供assert*()方法用来断言测试结果。
6.测试夹具:setUp前置条件和tearDown后置条件,在运行每个测试时,setUp() 、tearDown() 和 __init__() 会被调用一次。
7.用例执行:runner.run(),调用 unittest.main() 执行所有用TestCase分组的测试。用例执行顺序按照ASCII顺序执行。
8.整体流程:loader收集用例,unittest.TestLoader(),得到suite测试集,suite = discover(),runner运行,test_runner = HTMLTestRunner( ),runner.run(suite)。
1.创建test_demo.py模块用来存放测试用例,run.py模块用来执行用例,并生产report测试报告。
2.test_demo.py
import unittestclass TestCount(unittest.TestCase): def setUp(self) -> None: print("每条用例开始时都会执行!") def tearDown(self) -> None: print("每条用例结束后都会执行!") @classmethod def setUpClass(cls) -> None: print("只在开始的时候运行一次!") @classmethod def tearDownClass(cls) -> None: print("只在结束的时候运行一次!") def test_success_add1(self): """ 正确 """ num_a = 1 num_b = 2 expect_res = 3 actual_res = num_a + num_b self.assertEqual(expect_res, actual_res) def test_success_add2(self): """ 正确 """ num_a = 5 num_b = 5 expect_res = 10 actual_res = num_a + num_b self.assertEqual(expect_res, actual_res)if __name__ == '__main__': unittest.main() 2.run.py模块
import unittestfrom HTMLTestRunnerNew import HTMLTestRunner# 测试套件suite = unittest.TestSuite()# 添加测试用例类loader = unittest.TestLoader()suite.addTest(loader.discover(r"F:codepython_codejack_api"))# 启动器runner = HTMLTestRunner(stream=open("report.html", "wb"), tester="Jack", description="test_report", title="世界和平" )# 启动器执行测试套件用例runner.run(suite) 3.生成report.html测试报告
以上便是《Python学习24:unittest框架入门》的所有内容。
原创不易,如果喜欢请点赞和关注,谢谢大家的支持!想获得免费的学习资料请添加微信公众号——风起怨江南之。