当前位置: 首页 > 后端技术 > Python

说说Python的单元测试框架(一):unittest

时间:2023-03-25 19:44:49 Python

作者:HelloGitHub-Prodesire前言说到Python的单元测试框架,接触过Python的朋友首先想到的就是unittest。确实,作为Python的标准库,它非常优秀,被广泛应用于各种项目中。但你知道吗?其实在众多Python项目中,主流的单元测试框架远不止这一款。本系列文章将为您介绍目前流行的Python单元测试框架,谈谈它们的功能和特点,比较它们的异同,让您在面对不同的场景和不同的情况下,权衡利弊,择优选择。需要。单元测试框架。本文默认以Python3为例。如果某些特性在Python2中不可用或不同,将特别说明。1.简介unittest单元测试框架最早受到JUnit的启发,与其他语言的主流单元测试框架有着相似的风格。支持测试自动化,多个测试用例共享前端(setUp)和清理(tearDown)代码,将多个测试用例聚合成一个测试集,分离测试和报告框架。2.Usecase编写以下简单示例来自官方文档,用于测试三种字符串方法:upper,isupper,split:importunittestclassTestStringMethods(unittest.TestCase):deftest_upper(self):self.assertEqual('foo'.upper(),'FOO')deftest_isupper(self):self.assertTrue('FOO'.isupper())self.assertFalse('Foo'.isupper())deftest_split(self):s='你好world'self.assertEqual(s.split(),['hello','world'])#当分隔符不是字符串时检查s.split是否失败self.assertRaises(TypeError):s.split(2)if__name__=='__main__':unittest.main()在上面的例子中,通过继承unittest.TestCase创建了一个测试用例。在这个类中,定义以test开头的方法,测试框架会把它作为一个独立的测试来执行。每个用例使用unittest内置的断言方法来判断被测对象的行为是否符合预期,例如:在test_upper测试中,在test_isupper测试中使用assertEqual检查是否为预期值,使用assertTrue或assertFalse来验证是否满足条件,在test_split测试中,使用assertRaises来验证是否抛出特定异常。可能有人会好奇,为什么不用内置的断言语句assert,而是提供那么多的断言方法并使用呢?原因是通过使用unittest提供的断言方法,测试框架可以聚合所有的测试结果,并在运行后生成一个信息丰富的测试报告。虽然直接使用assert也可以达到验证被测对象是否符合预期的目的,但是当用例失败时,错误信息不够丰富。3、用例发现和执行unittest支持自动(递归)发现用例:默认情况下,找到当前目录下所有符合test*.py的测试用例。使用python-munittest或者python-munittestdiscover通过-s参数指定自动发现的目录。-p参数指定用例文件的命名模式python-munittestdiscover-sproject_directory-p"test_*.py"通过position参数python-m指定自动发现的目录和用例文件的命名模式unittestdiscoverproject_directory"test_*.py"unittest支持执行指定用例:指定测试模块python-munittesttest_module1test_module2指定测试类python-munittesttest_module.TestClass指定测试方法python-munittesttest_module.TestClass.test_method指定测试文件路径(仅限Python3)python-munittesttests/test_something.py4.测试夹具(Fixtures)测试夹具是测试pre-setUp和tearDown的方法。预测试方法setUp()用于做一些准备工作,比如建立数据库连接。它会在测试用例执行前被测试框架自动调用。测试清理方法tearDown()用于做一些清理工作,比如断开数据库连接。测试用例(包括失败用例)执行完毕后,由测试框架自动调用。测试准备和清理方法可以有不同的执行级别。4.1有效级别:测试方法如果我们要在每个测试方法前后执行测试前测试和清理方法,需要在测试类中定义setUp()和tearDown():classMyTestCase(unittest.TestCase):defsetUp(self):passdeftearDown(self):pass4.2有效级别:测试类如果我们想在单个测试类中只执行一次pre-method,那么执行测试类中的所有测试,以及最后执行cleanup方法,然后需要在测试类中定义SetUpClass()和tearDownClass():classMyTestCase(unittest.TestCase):defsetUpClass(self):passdeftearDownClass(self):pass4.3有效级别:测试模块如果我们想要单个测试模块只在模块中执行一次pre-method,然后执行模块中所有测试类的所有测试,最后执行cleanup方法,那么就需要定义setUpModule()和tearDownModule()inthetestmodule:defsetUpModule():passdeftearDownModule():pass5.跳过测试和预期的failureunittest支持直接跳过或有条件跳过测试,也支持预测测试失败:通过skip装饰器或SkipTest直接跳过测试通过skipIf或skipUnless有条件地跳过或不跳过测试通过expectedFailure和预期测试失败classMyTestCase(unittest.TestCase):@unittest.skip("直接跳过")deftest_nothing(self):self.fail("不应该发生")@unittest.skipIf(mylib.__version__<(1,3),"如果满足条件则跳过")deftest_format(self):#适用于on的测试只是某个版本的库。pass@unittest.skipUnless(sys.platform.startswith("win"),"如果满足条件则不跳过")deftest_windows_support(self):#windowsspecifictestingcodepassdeftest_maybe_skipped(self):ifnotexternal_resource_available():self.skipTest("skip")#依赖外部资源的测试代码pass@unittest.expectedFailuredeftest_fail(self):self.assertEqual(1,0,"Thiscurrentlyafailure")六、子测试有的时候,你可能想写这样一个测试:在一个测试方法中传入不同的参数来测试同一块逻辑,但是会被视为一个测试,但是如果你使用子测试,则可以查看asN(即参数个数)测试下面是一个例子:classNumbersTest(unittest.TestCase):deftest_even(self):"""测试0到5之间的数字都是偶数。"""foriinrange(0,6):withself.subTest(i=i):self.assertEqual(i%2,0)例子中使用withself.subTest(i=i)方法定义子测试,在这种情况下,即使单子测试执行失败,不影响后续子测试的执行。这样,我们可以在输出中看到三个子测试失败:==============================================================================失败:test_even(__main__.NumbersTest)(i=1)--------------------------------------------------------------------回溯(最近调用最后一次):文件“subtests.py”,第32行,在test_evenself.assertEqual(i%2,0)断言错误:1!=0===========================================================================失败:test_even(__main__.NumbersTest)(i=3)--------------------------------------------------------------------回溯(最近调用最后):文件“subtests.py”,第32行,在test_evenself.assertEqual(i%2,0)AssertionError:1!=0=========================================================================失败:test_even(__main__.NumbersTest)(i=5)---------------------------------------------------------------回溯(最近调用最后):文件“subtests.py”,第32行,在test_evenself.assertEqual(i%2,0)Assertion错误:1!=07.测试结果输出基于简单例子部分提到的例子,说明unittest运行测试后的结果输出默认输出非常简单,显示运行了多少个案例以及花费了多长时间:......-------------------------------------------------------------------在0.000sOK中运行3个测试通过指定-v参数,你可以得到详细的输出。除了默认输出外,还会显示测试用例名称:test_isupper(__main__.TestStringMethods)...oktest_split(__main__.TestStringMethods)...oktest_upper(__main__.TestStringMethods)...ok----------------------------------------------------------------------在0.001sOK跑了3次测试假设test_upper测试失败,在verbose输出模式下,结果如下:test_isupper(tests.test.TestStringMethods)...oktest_split(tests.test.TestStringMethods)...oktest_upper(tests.test.TestStringMethods)...失败============================================================================失败:test_upper(测试.test.TestStringMethods)---------------------------------------------------------------------Traceback(最近调用最后):文件“/Uvsers/prodesire/projects/tests/test.py",line6,intest_upperself.assertEqual('foo'.upper(),'FOO1')AssertionError:'FOO'!='FOO1'-FOO+FOO1?+-----------------------------------------------------------------在0.001s内跑了3次测试FAILED(failures=1)如果我们把test_upper测试方法中的self.assertEqual改成assert,那么测试结果输出将缺少有助于故障排除的上下文信息:test_isupper(tests.test.TestStringMethods)...oktest_split(tests.test.TestStringMethods)...oktest_upper(tests.test.TestStringMethods)...FAIL=========================================================================失败:test_upper(tests.test.TestStringMethods)----------------------------------------------追溯(最近一次通话最后一次):文件“/Users/prodesire/projects/tests/test.py”,第6行,在test_upperassert'foo'.upper()=='FOO1'AssertionError------------------------------------------------------------------------Ran3testsin0.001sFAILED(failures=1)如果要生成HTML格式的报告,需要使用第三方库(如HtmlTestRunner)运行在安装第三方库后,不能直接使用python-munittestplus--htmlreport.html生成HTML报告,而是需要编写少量代码运行测试用例,获取HTML报道。具体请参考HtmlTestRunner手册。8.总结作为Python标准库提供的单元测试框架,unittest简单易用,功能强大,可以满足日常的测试需求。是不引入第三方库进行单元测试的最佳选择。下一篇我们将介绍第三方单元测试框架nose和nose2,并说说它们相对于unittest的改进,让很多开发者优先选择。《讲解开源项目系列》——让对开源项目感兴趣的人不再害怕,让开源项目的发起者不再孤单。关注我们的文章,您将发现编程的乐趣,使用并发现参与开源项目是多么容易。欢迎留言联系我们,加入我们,让更多人爱上开源,为开源做贡献~