宁波网络营销推广公司,aso搜索排名优化,微信注册网站入口,wordpress 分页 增加class[单元测试的含义]
Unittest单元测试框架的设计灵感来源于Junit(Java语言的单元测试框架)#xff0c;它与其他语言的单元测试框架风格相类似#xff0c;支持自动化测试、为测试共享setUp和shutDown、它可以在将测试用例聚合到一起形成一个集合一起执行的同时在测试报告中展示…[单元测试的含义]
Unittest单元测试框架的设计灵感来源于Junit(Java语言的单元测试框架)它与其他语言的单元测试框架风格相类似支持自动化测试、为测试共享setUp和shutDown、它可以在将测试用例聚合到一起形成一个集合一起执行的同时在测试报告中展示独立的测试结果。 为了达到此目的unittest支持几个重要的面向对象式的概念
test fixture一个test fixture所做的事情是执行单个或多个测试用例时的准备工作和执行结束后的一些相关清理工作这包括创建临时或代理数据库、目录或开始服务器进程test case一个test case是一个独立的测试单元针对于一组特定的输入得到的特殊相应的验证通过继承unittest提供的基类TestCase 然后可以创建新的测试用例test suite一个test suite是一组测试用例的集合也可以是一组test suite的集合也可以两者混合的集合test suite就是用来聚合你想要一起执行的测试用例的test runner一个test runner是一个协调测试执行并向用户提供执行结果的组建它可以使用图形界面、文本界面或返回一个特殊值标识测试执行的结果
实例代码
下面我们看Python官方给的一个简单的小例子来测试三个字符串
import unittestclass TestStringMethods(unittest.TestCase): # 测试类继承了unittest.TestCase类因此在该类里可以创建新的测试用例def test_upper(self):self.assertEqual(foo.upper(), FOO) # ‘foo’是一个字符串upper()方法会将这个字符串转成大写assertEqual是unittest提供的一个断言方法用来比较逗号前后两个值是否相等def test_isupper(self):self.assertTrue(FOO.isupper()) # assertTrue也是unittest提供的断言方法用来判断括号内的内容是真是假如果是真则断言成功否则为失败FOO是个字符串并且是大写调用isupper()方法返回结果self.assertFalse(Foo.isupper()) # assertFalse则正好相反如果括号内返回为假则断言成功否则为失败def test_split(self):s hello worldself.assertEqual(s.split(), [hello, world])# check that s.split fails when the separator is not a stringwith self.assertRaises(TypeError):s.split(2)if __name__ __main__:unittest.main()代码解析
在第三个用例里定义了一个字符串shello world, 然后进行了断言断言的条件里调用了一个split()方法实际上初学者看到这会比较懵s.split()到底返回的是什么它是否等于逗号后边[hello, world]?学这个东西就是要多试试那么接下来我们进行一系列尝试来认识一下split()
启动命令行CMD进入python环境
尝试1定义一个字符串s hello world, 然后print(s)回车结果应该是hello world尝试2如果我们敲入print(s.split())然后回车呢呢结果应该是[hello, world], 到此我们就得到了答案第三条用例里的断言self.assertEqual(s.split(), [hello, world])是成功尝试3如果我们print(s.split(2))会是什么结果 如果我们print(s.split(o))又会是什么结果
with self.assertRaises(TypeError):s.split(2)在第三个用例里我们也看到了两行代码split()函数我们已经知道它能干什么了那么with是什么assertRaises在这又在干什么 with我们总提python之美那么这就是美丽之处with语句提供一个有效的处理异常和完成清理工作的机制它让代码更简练有点懵没关系换一个方式说如果不用with要达到同等效果的的话要用什么呢try…except…finally这是另一个课题了并不是此处的重点读者朋友可以先忽略它不要打断学习unittest的思路 assertRaises是什么呢unittest 模块提供了用于测试函数是否在给定无效输入时引发特定异常的方法这个方法就是assertRaises我们在回去看代码s.split(2), 很明显我们前边已经尝试过执行参数为2的情况报了异常也就是2并不是split函数的合法参数我们传给2这个参数它理应报异常那么好with self.assertRaises(TypeError): 在干什么它想要的就是看看split()在得到了非法参数的时候是否报一个TypeError此处读者可能要整理一下思路仔细阅读此段内容
最后的unittest.main()有了它我们就有了一个简单的方式执行这个脚本unittest.main()提供了命令行界面运行脚本的方式 假设我们上边的脚本保存在文件testingunit.py里并将它保存在桌面然后我们启动命令行输入“python C:\Users\davieyang\Desktop\testingunit.py” 看看是个什么结果
C:\Users\用户名python C:\Users\davieyang\Desktop\testingunit.py
....
----------------------------------------------------------------------
Ran 4 tests in 0.001sOK我们能看到4个…它的意义我们共4个用例点表示测试通过那么如果点表示测试通过什么表示测试失败呢 是“F”如果测试遇到异常呢 是“E”,如果我们执行“python C:\Users\davieyang\Desktop\testingunit.py -v”又是什么结果
C:\Users\用户名python C:\Users\davieyang\Desktop\testingunit.py -v
test_isupper (__main__.TestStringMethods) ... ok
test_list (__main__.TestStringMethods) ... ok
test_split (__main__.TestStringMethods) ... ok
test_upper (__main__.TestStringMethods) ... ok----------------------------------------------------------------------
Ran 4 tests in 0.002sOK[subTest]
代码实例
# coding:utf-8
import unittestclass NumbersTest(unittest.TestCase):def test_even(self):使用subTest上下文管理器区分细小的变化取模运算返回除法的余数但是参数是0到5的整数没必要单独写方法for i in range(0, 6):with self.subTest(ii):self.assertEqual(i % 2, 0)if __name__ __main__:unittest.main()执行这段代码的结果会是
SubTest failure: Traceback (most recent call last):File C:\Python37\lib\unittest\case.py, line 59, in testPartExecutoryieldFile C:\Python37\lib\unittest\case.py, line 533, in subTestyieldFile D:\Programs\Python\Demo\unittest4\subtestDemo.py, line 14, in test_evenself.assertEqual(i % 2, 0)File C:\Program Files\JetBrains\PyCharm Community Edition 2018.3.1\helpers\pycharm\teamcity\diff_tools.py, line 39, in _patched_equalsraise native_errorFile C:\Program Files\JetBrains\PyCharm Community Edition 2018.3.1\helpers\pycharm\teamcity\diff_tools.py, line 32, in _patched_equalsold(self, first, second, msg)File C:\Python37\lib\unittest\case.py, line 839, in assertEqualassertion_func(first, second, msgmsg)File C:\Python37\lib\unittest\case.py, line 832, in _baseAssertEqualraise self.failureException(msg)
AssertionError: 1 ! 0SubTest failure: Traceback (most recent call last):File C:\Python37\lib\unittest\case.py, line 59, in testPartExecutoryieldFile C:\Python37\lib\unittest\case.py, line 533, in subTestyieldFile D:\Programs\Python\Demo\unittest4\subtestDemo.py, line 14, in test_evenself.assertEqual(i % 2, 0)File C:\Program Files\JetBrains\PyCharm Community Edition 2018.3.1\helpers\pycharm\teamcity\diff_tools.py, line 39, in _patched_equalsraise native_errorFile C:\Program Files\JetBrains\PyCharm Community Edition 2018.3.1\helpers\pycharm\teamcity\diff_tools.py, line 32, in _patched_equalsold(self, first, second, msg)File C:\Python37\lib\unittest\case.py, line 839, in assertEqualassertion_func(first, second, msgmsg)File C:\Python37\lib\unittest\case.py, line 832, in _baseAssertEqualraise self.failureException(msg)
AssertionError: 1 ! 0SubTest failure: Traceback (most recent call last):File C:\Python37\lib\unittest\case.py, line 59, in testPartExecutoryieldFile C:\Python37\lib\unittest\case.py, line 533, in subTestyieldFile D:\Programs\Python\Demo\unittest4\subtestDemo.py, line 14, in test_evenself.assertEqual(i % 2, 0)File C:\Program Files\JetBrains\PyCharm Community Edition 2018.3.1\helpers\pycharm\teamcity\diff_tools.py, line 39, in _patched_equalsraise native_errorFile C:\Program Files\JetBrains\PyCharm Community Edition 2018.3.1\helpers\pycharm\teamcity\diff_tools.py, line 32, in _patched_equalsold(self, first, second, msg)File C:\Python37\lib\unittest\case.py, line 839, in assertEqualassertion_func(first, second, msgmsg)File C:\Python37\lib\unittest\case.py, line 832, in _baseAssertEqualraise self.failureException(msg)
AssertionError: 1 ! 0One or more subtests failed
Failed subtests list: (i1), (i3), (i5)Ran 1 test in 0.020sFAILED (failures3)Process finished with exit code 1而如果我们不使用subTest(), 只是写个简单的循环去断言当程序执行到第一个断言失败时就会终止了后边可能还有断言能够成功的也就不会被执行了
# coding:utf-8
import unittestclass NumbersTest(unittest.TestCase):def test_even(self):for i in range(0, 6):# with self.subTest(ii):print(当前参数是%d % i)self.assertEqual(i % 2, 0)if __name__ __main__:unittest.main()执行结果会是
当前参数是0
当前参数是1Ran 1 test in 0.010sFAILED (failures1)0 ! 1Expected :1
Actual :0Click to see differenceTraceback (most recent call last):File C:\Program Files\JetBrains\PyCharm Community Edition 2018.3.1\helpers\pycharm\teamcity\diff_tools.py, line 32, in _patched_equalsold(self, first, second, msg)File C:\Python37\lib\unittest\case.py, line 839, in assertEqualassertion_func(first, second, msgmsg)File C:\Python37\lib\unittest\case.py, line 832, in _baseAssertEqualraise self.failureException(msg)
AssertionError: 1 ! 0During handling of the above exception, another exception occurred:Traceback (most recent call last):File C:\Python37\lib\unittest\case.py, line 59, in testPartExecutoryieldFile C:\Python37\lib\unittest\case.py, line 615, in runtestMethod()File D:\Programs\Python\Demo\unittest4\subtestDemo.py, line 15, in test_evenself.assertEqual(i % 2, 0)Process finished with exit code 1[Test Fixture]
import unittest
class TestStringMethods(unittest.TestCase): import unittest 导入unittest模块使得我们可以使用它class TestStringMethods(unittest.TestCase): 新创建的测试类继承了unittest.TestCase使得我们可以是使用TestCase里的特性
那么它有哪些主要特性呢 测试用例当我们的测试类继承了unittest.TestCase若以“def test_xxx(self):”这样的命名方式test开头在测试类中定义函数时它就会被unittest认为是一条测试方法然而就像我们做手动测试用例的时候总有一些原则在那么在写自动化测试用例时有哪些主要的原则呢 每一个测试用例必须是完全独立的从而能够单独执行也可以组团执行每一个测试用例必须有断言从而在测试失败的情况下断言异常和一条解释性的语句(AssertionError)将会抛出此时unittest将会将这条用例标识为失败其他的异常类型将会被认为是错误(error)在设计测试用例时要尽可能考虑后续维护的问题我们要尽可能的减少修改测试代码从而能够满足快速的迭代测试 setUp()这个函数也继承自unittest.TestCase它的作用是用来完成每一个测试方法执行前的准备工作如果setUp()方法执行的时候出现异常那么unittest框架认为测试出现了错误测试方法是不会被执行的 tearDown(): 同样继承自unittest.TestCase它的作用是每一个测试方法执行完后的清理工作如果setUp()执行成功那么测试方法执行成功还是失败tearDown()方法都会被执行 setUpClass(): 同样继承自unittest.TestCase它的作用是完成在所有测试方法执行前包括setUp()单元测试的前期准备工作必须用classmethod修饰整个测试类只执行一次 tearDownClass(): 同样继承自unittest.TestCase它的作用是完成在所有测试方法执行后包括tearDown()单元测试的清理工作必须用classmethod修饰整个测试类只执行一次 还有一种特例最简单的测试用例只需要通过覆盖runTest()方法来执行自定义的测试代码我们称之为静态方法测试方法名不能重复也意味着测试类中只能有一个runTest()方法很显然这样的方式会导致很多冗余代码 使用了1到5测试特性构建测试用例的我们称之为动态方法
实例代码
下边将用实例代码详细展示如上概念待测代码如下
class BubbleSort(object):def __init__(self, mylist):self.myList mylistself.length len(mylist)def ascending_order(self):for i in range(self.length-1):for j in range(self.length-1-i):if self.myList[j] self.myList[j 1]:self.myList[j], self.myList[j1] self.myList[j1], self.myList[j]return self.myListdef descending_order(self):for i in range(self.length-1):for j in range(self.length-1-i):if self.myList[j] self.myList[j 1]:self.myList[j], self.myList[j1] self.myList[j1], self.myList[j]return self.myList测试代码如下
import unittest
from Util.BubbleSort import BubbleSortclass TestBubbleSort(unittest.TestCase): classmethoddef setUpClass(cls):print(execute setUpClass\n)classmethoddef tearDownClass(cls):print(execute tearDownClass\n)def setUp(self):self.list1 [2, 10, 25, 30, 45, 100, 325]self.list3 [325, 10, 25, 45, 30, 100, 2]self.list4 [11, 3, 41, 101, 327, 26, 46]self.list2 [327, 101, 46, 41, 26, 11, 3]def tearDown(self):print(execute tearDown\n)def test_descending_order(self):bls BubbleSort(self.list4)self.list5 bls.descending_order()print(self.list5)self.assertEqual(self.list5, self.list2)def test_ascending_order(self):bls BubbleSort(self.list3)self.list6 bls.ascending_order()print(self.list6)self.assertEqual(self.list6, self.list1)if __name__ __main__:unittest.main()执行结果应该是
..
execute setUpClass
----------------------------------------------------------------------Ran 2 tests in 0.001s[2, 10, 25, 30, 45, 100, 325]
OK
execute tearDown[327, 101, 46, 41, 26, 11, 3]
execute tearDownexecute tearDownClassProcess finished with exit code 0[TestSuite]
代码示例
以前的执行方式
# encoding utf-8
import random
import unittestclass TestRandomFunction(unittest.TestCase):def setUp(self):self.str abcdef!#$%def tearDown(self):passdef test_randomchoice(self):var random.choice(self.str)self.assertTrue(var in self.str)print(var)def test_randomsample(self):with self.assertRaises(ValueError):random.sample(self.str, 100)for var in random.sample(self.str, 6):self.assertTrue(var in self.str)print(var)class TestRandomShuffleFunction(unittest.TestCase):def setUp(self):self.list [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13]def tearDown(self):passdef test_randomshuffle(self):random.shuffle(self.list)print(self.list)self.list.sort()self.assertEqual(self.list, [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13])if __name__ __main__:unittest.main()方式一
使用unittest.TestLoader它可以通过传给他的参数获取测试用例的测试方法然后再组合成TestSuite最后在将TestSuite传递给TestRunner 完成我们所期望的执行组合
# encoding utf-8
import random
import unittestclass TestRandomFunction(unittest.TestCase):def setUp(self):self.str abcdef!#$%def tearDown(self):passdef test_randomchoice(self):var random.choice(self.str)self.assertTrue(var in self.str)print(var)def test_randomsample(self):with self.assertRaises(ValueError):random.sample(self.str, 100)for var in random.sample(self.str, 6):self.assertTrue(var in self.str)print(var)class TestRandomShuffleFunction(unittest.TestCase):def setUp(self):self.list [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13]def tearDown(self):passdef test_randomshuffle(self):random.shuffle(self.list)print(self.list)self.list.sort()self.assertEqual(self.list, [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13])if __name__ __main__:# unittest.main()testcase1 unittest.TestLoader().loadTestsFromTestCase(TestRandomFunction)testcase2 unittest.TestLoader().loadTestsFromTestCase(TestRandomShuffleFunction)suite unittest.TestSuite([testcase1, testcase2])unittest.TextTestRunner(verbosity2).run(suite)方式二
另创建一个.py文件定义suite方法使用unittest.TestSuite().addTest(测试类(测试方法))
# encoding utf-8
import unittest
from unittest3.TestSuiteDemo2 import *def suite():suite unittest.TestSuite()suite.addTest(TestRandomFunction(test_randomchoice))suite.addTest(TestRandomShuffleFunction(test_randomshuffle))return suiteif __name__ __main__:runner unittest.TextTestRunner()runner.run(suite())方式三
另创建一个.py文件使用unittest.TestLoader().discover(“路径”“匹配文件名”)
# encoding utf-8
import unittestif __name__ __main__:suite unittest.TestLoader().discover(., patternTestSuiteDemo1.py)unittest.TextTestRunner(verbosity2).run(suite)random实例
import random
import string#随机获取0到10之间随机整数N0 N 10, 语法randint(a, b)a N b
random.randint(0,10)
#等同于
random.randrange(0,11) #随机获取0到10之间的偶数M, 语法:randrange(startstop[step])
random.randrange(0,11,2)#随机获取浮点数
random.random() #随机后去0到10之间的浮点数O语法uniform(a, b)[a N b for a b and b N a for b a]
random.uniform(0,10)#获取字符串随机字符
random.choice(string)#获取特定数量的随机字符返回list
random.sample(string,3)#多个字符中选取特定数量的字符组成新字符串
list1 [a,b,c,d,e,f,g,h,i,j, 1, 2, 3]
list2 [str(i) for i in list1]
str1 .join(random.sample(list2, 5))#获取随机字符串
random.choice([apple, pear, peach, orange, lemon])
random.choice(abcdefghijklmn)#洗牌
string1 a b c d e f g
string2 string1.split()
random.shuffle(string2)
print(string2)类说明
TestLoader类它用于加载测试用例然后返回一个测试用例集和LoadTestsFromTestCase类根据传给它的测试类获取其中以test开头的测试方法然后返回一个测试集合TestSuite类用于组装测试用例的实例然后传递给TestRunner进行测试执行TextTestRunner类用于执行测试用例Text表示是以文本形式输出测试结果它会返回一个TestResult实例对象用于存储测试用例执行过程中的详细信息可以使用Python的dir()进行查看
参数verbosity说明
verbosity 0时输出结果中不提示执行成功的用例数verbosity 1时输出结果中仅仅以.的形式表示执行成功的用例数成功几个就是几个点verbosity 2时可以输出每个用例执行的详细信息特别是在用例较多的情况下此设置会比较有用
[命令行执行]
命令行模式执行用例
unittest框架支持命令行执行测试模块、测试类甚至单独的测试方法
执行测试模块python -m unittest test_module1 test_module2 ......也可以采用路径的方式 python -m unittest tests/test_something.py如果想用一个高级的verbosity的方式执行加上参数-v即可例如 python -m unittest -v test_module执行测试类python -m unittest test_module1.Test_Class 执行测试方法python -m unittest test_module1.Test_Class.test_method 如果想获取这种命令组合的help则执行命令 python -m unittest -h将得到如下帮助信息
D:\Programs\Python\Demo\unittest1python -m unittest -h
usage: python.exe -m unittest [-h] [-v] [-q] [--locals] [-f] [-c] [-b][-k TESTNAMEPATTERNS][tests [tests ...]]positional arguments:tests a list of any number of test modules, classes and testmethods.optional arguments:-h, --help show this help message and exit-v, --verbose Verbose output-q, --quiet Quiet output--locals Show local variables in tracebacks-f, --failfast Stop on first fail or error-c, --catch Catch Ctrl-C and display results so far-b, --buffer Buffer stdout and stderr during tests-k TESTNAMEPATTERNS Only run tests which match the given substring
例如 -k foo 会去匹配
foo_tests.SomeTest.test_something, bar_tests.SomeTest.test_foo去执行但是不会匹配bar_tests.FooTest.test_somethingExamples:python.exe -m unittest test_module - run tests from test_modulepython.exe -m unittest module.TestClass - run tests from module.TestClasspython.exe -m unittest module.Class.test_method - run specified test methodpython.exe -m unittest path/to/test_file.py - run tests from test_file.pyusage: python.exe -m unittest discover [-h] [-v] [-q] [--locals] [-f] [-c][-b] [-k TESTNAMEPATTERNS] [-s START][-p PATTERN] [-t TOP]optional arguments:-h, --help show this help message and exit-v, --verbose Verbose output-q, --quiet Quiet output--locals Show local variables in tracebacks-f, --failfast Stop on first fail or error-c, --catch Catch Ctrl-C and display results so far-b, --buffer Buffer stdout and stderr during tests-k TESTNAMEPATTERNS Only run tests which match the given substring-s START, --start-directory STARTDirectory to start discovery (. default)-p PATTERN, --pattern PATTERNPattern to match tests (test*.py default)-t TOP, --top-level-directory TOPTop level directory of project (defaults to startdirectory)For test discovery all test modules must be importable from the top level
directory of the project.如果没有传参数那么将执行Test Discovery 例如输入命令python -m unittest它也等价于 python -m unittest discover并未给他任何模块、类或者方法那么他将做的事情便是Test Discovery
例如命令python -m unittest discover -s project_directory -p *_test.py本条命令中使用了参数 -s 和 -p -s表示从那个目录开始默认为 (.), -p则表示匹配哪样的文件名这条命令也等价于python -m unittest discover project_directory *_test.py
输入python -m unittest discover -h将的到如何帮助很清楚的写明了各参数说明
D:\Programs\Python\Demo\unittest1python -m unittest discover -h
usage: python.exe -m unittest discover [-h] [-v] [-q] [--locals] [-f] [-c][-b] [-k TESTNAMEPATTERNS] [-s START][-p PATTERN] [-t TOP]optional arguments:-h, --help show this help message and exit-v, --verbose Verbose output-q, --quiet Quiet output--locals Show local variables in tracebacks-f, --failfast Stop on first fail or error-c, --catch Catch Ctrl-C and display results so far-b, --buffer Buffer stdout and stderr during tests-k TESTNAMEPATTERNS Only run tests which match the given substring-s START, --start-directory STARTDirectory to start discovery (. default)-p PATTERN, --pattern PATTERNPattern to match tests (test*.py default)-t TOP, --top-level-directory TOPTop level directory of project (defaults to startdirectory)For test discovery all test modules must be importable from the top level
directory of the project.[TestLoader]
实际上TestLoader还有其他的方法
loadTestsFromModule(module, patternNone)loadTestsFromName(name, moduleNone) 和 loadTestsFromNames(names, moduleNone)getTestCaseNames(testCaseClass)返回一个存出测试方法名称的序列discover(start_dir, patterntest*.py, top_level_dirNone)这个方法我们已经用到好多次了defaultTestLoader他是一个TestLoader的实例如果我们不需要定制化的TestLoader直接使用这个即可还能必变重复创建TestLoader实例
总结
unittest单元测试框架介绍到这里基本上满足了日常的工作需要然而这并不是unittest的全部它还有很多的特性读者可以直接访问Python官网查看unittest的API文档对于学习来说官方文档是最好的书籍
[装饰器]
如果有一些测试方法不想执行如果有些测试方法在某些条件下不执行 该当如何如果有些方法未在unittest框架下编写又想使用unittest框架执行该当如何如果想自定义一个执行顺序该当如何
代码实例
如果有一些测试方法不想执行如果有些测试方法在某些条件下不执行 该当如何
# coding : utf-8
import unittest
import random
import sysclass TestSequenceFunctions(unittest.TestCase):a 1b 2def setUp(self):self.seq list(range(10))self.list [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13]unittest.skip(就跳过了不为什么)def test_shuffle(self):random.shuffle(self.seq)self.seq.sort()self.assertEqual(self.seq, list(range(10)))self.assertRaises(TypeError, random.shuffle, (1, 2, 3))unittest.skipIf(a ! 1, 如果a不等于1就跳过此测试方法)def test_choic(self):element random.choice(self.seq)self.assertTrue(element in self.seq)unittest.skipUnless(b 1, 除非b大于1否则跳过)def test_sample(self):with self.assertRaises(ValueError):random.sample(self.seq, 20)for element in random.sample(self.seq, 5):self.assertTrue(element in self.seq)unittest.expectedFailuredef test_randomshuffle(self):random.shuffle(self.list)print(self.list)self.assertEqual(self.list, [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13])if __name__ __main__:unittest.main(verbosity2)执行结果会是
Expected failure: Traceback (most recent call last):File C:\Program Files\JetBrains\PyCharm Community Edition 2018.3.1\helpers\pycharm\teamcity\diff_tools.py, line 32, in _patched_equalsold(self, first, second, msg)File C:\Python37\lib\unittest\case.py, line 839, in assertEqualassertion_func(first, second, msgmsg)File C:\Python37\lib\unittest\case.py, line 1045, in assertListEqualself.assertSequenceEqual(list1, list2, msg, seq_typelist)File C:\Python37\lib\unittest\case.py, line 1027, in assertSequenceEqualself.fail(msg)File C:\Python37\lib\unittest\case.py, line 680, in failraise self.failureException(msg)
AssertionError: Lists differ: [4, 1, 9, 2, 7, 8, 3, 11, 6, 5, 13] ! [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13]First differing element 0:
4
1- [4, 1, 9, 2, 7, 8, 3, 11, 6, 5, 13][1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13]During handling of the above exception, another exception occurred:Traceback (most recent call last):File C:\Python37\lib\unittest\case.py, line 59, in testPartExecutoryieldFile C:\Python37\lib\unittest\case.py, line 615, in runtestMethod()File D:\Programs\Python\Demo\unittest6\SkipDemo.py, line 38, in test_randomshuffleself.assertEqual(self.list, [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13])File C:\Program Files\JetBrains\PyCharm Community Edition 2018.3.1\helpers\pycharm\teamcity\diff_tools.py, line 38, in _patched_equalsraise error
teamcity.diff_tools.EqualsAssertionError: :: [4, 1, 9, 2, 7, 8, 3, 11, 6, 5, 13] ! [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13]Ran 4 tests in 0.010sOK (skipped1, expected failures1)Skipped: 就跳过了不为什么代码解析
unittest为我们提供了多种跳过测试用例的方法当我们的大量用例在不同场景下可能有些用例并不想执行例如回归测试、例如新部署的一套环境需要对主功能进行验证、例如有些用例需要具备条件才执行等等场景我们便需要这些跳过用例的方法当然我们可以将那些不想执行的用例注释掉但也可以采用如下装饰器给测试方法加上注解
unittest.skip(reason)# 无条件跳过reason是用来描述为什么跳过它unittest.skipIf(condition, reason)# 有条件跳过当condition满足的情况下便跳过此装饰器装饰的用例unittest.skipUnless(condition, reason)# 有条件跳过当condition满足的情况下便要执行此装饰器装饰的用例与上一个相反unittest.expectedFailure# 用于标记期望执行失败的测试方法如果该测试方法执行失败则被认为成功如果执行成功则被认为失败并且当测试模块被装饰器装饰为跳过时它的setUpModule()和tearDownModule()也就不会执行了同样的当测试类被装饰器装饰为跳过时它的setUpClass()和tearDownClass()也就不会执行了一样的当测试方法被装饰器装饰为跳过时它的setUp()和tearDown()也就不会执行了
如果想自定义一个执行顺序该当如何
在前边的文章中介绍了多种执行用例的方式首先unittest.main()这种方式启动单元测试去执行各测试方法的执行顺序是按所有方法名的字符串的ASCII码排序后的顺序执行的
如果想自定义顺序执行我们需要使用TestSuite(), 它的执行顺序是按照我们addTest()的次序进行执行的
# encoding utf-8
import unittest
from unittest3.TestSuiteDemo2 import *def suite():suite unittest.TestSuite()suite.addTest(TestRandomFunction(test_randomchoice))suite.addTest(TestRandomShuffleFunction(test_randomshuffle))return suiteif __name__ __main__:runner unittest.TextTestRunner()runner.run(suite())如果有些方法未在unittest框架下编写又想使用unittest框架执行该当如何
当我们的一些老代码并没建立在unittest的体系中但是如果想使用unittest去执行它又不想将所有老代码转换到unittest的时候unittest为我们提供了unittest.FunctionTestCase(testFunc, setUpNone, tearDownNone, descriptionNone) 假设我们有个测试方法如下 def test_randomchoice(self):var random.choice(self.str)self.assertTrue(var in self.str)print(var)它并没有建立在unittest框架中只是一个独立的函数那么我们可以创建等价的测试用例
testcase unittest.FunctionTestCase(test_randomchoice, setUpmakeSomethingDB, tearDowndeleteSomethingDB)然而并不建议使用这种方法如果大量的这种代码出现将使得测试代码比较混乱难以维护和重构
[断言]
简介
单元测试里很重要的一个部分就是断言unittest为我们提供了很多断言方法断言方法分为三类一种是用来断言被测试的方法的另一种是测试是否抛正确异常的第三种是用来断言日志是否包含应有信息的方法很多
第一种很好理解是用来判断我们的被测点是否达到预期用的。第二种用来判断在某种情况下是否会抛出特定的异常如果会抛出该特定异常则会判断为断言成功如果没抛出这种特定异常则会判断为断言失败。第三种是用来断言日志是否包含应有信息的
assertEqual(a, b, msgNone)断言 a b
assertNotEqual(a, b, msgNone)断言 a ! b
assertTrue(expr, msgNone)断言 bool(expr) is True
assertFalse(expr, msgNone)断言 bool(expr) is False
assertIs(a, b, msgNone)断言 a is b
assertIsNot(a, b, msgNone)断言 a is not b
assertIsNone(expr, msgNone)断言 expr is None
assertIsNotNone(expr, msgNone)断言 expr is not None
assertIn(a, b, msgNone)断言 a in b
assertNotIn(a, b, msgNone)断言 a not in b
assertIsInstance(obj, cls, msgNone)断言 obj is cls instance
assertNotIsInstance(obj, cls, msgNone)断言 obj is not cls instance
assertRaises(exc, fun, *args, **kwds)断言 fun(*args, **kwds) 是否抛出正确异常 否则抛出断言异常
assertRaisesRegex(exc, r, fun, *args, **kwds) 断言 fun(*args, **kwds) 是否抛出正确异常同时可以用正则r去匹配异常信息exc否则抛出断言异常
assertWarns(warn, fun, *args, **kwds)断言fun(*args, **kwds) raises warn
assertWarnsRegex(warn, r, fun, *args, **kwds)断言 fun(*args, **kwds) raises warn and the message matches regex r
assertLogs(logger, level) 断言log 断言log里是否出现期望的信息如果出现则通过如果没出现则断言失败抛出断言异常
assertAlmostEqual(a, b, msgNone, deltaNone) round(a-b, 7) 0 断言a-b约等于0小数点后默认保留7位
assertNotAlmostEqual(a, b, msgNone, deltaNone) round(a-b, 7) ! 0 断言不是约等于的情况
assertGreater(a, b, msgNone) a b 断言大于
assertGreaterEqual(a, b, msgNone) a b 断言大于等于
assertLess(a, b, msgNone, msgNone) a b 断言小于
assertLessEqual(a, b, msgNone) a b 断言小于等于
assertRegex(text, regex, msgNone) r.search(s)
assertNotRegex(text, regex, msgNone) not r.search(s)
assertCountEqual(a, b, msgNone) a and b have the same elements in the same number, regardless of their order
assertMultiLineEqual(a, b, msgNone) strings 断言多行字符串
assertSequenceEqual(a, b, msgNone, seq_typeNone) sequences 断言序列
assertListEqual(a, b, msgNone) lists 断言List
assertTupleEqual(a, b, msgNone) tuples 断言元组
assertSetEqual(a, b, msgNone) sets or frozensets 断言Set
assertDictEqual(a, b, msgNone) dicts 断言词典在早期的python版本中断言函数的写法有些已经被废弃了如下对应关系所示在我们使用编译器的时候经常会提示“Deprecated”这个单词意味着有新的方式取代了当前的实现方法
Method NameDeprecated aliasDeprecated aliasassertEqual()failUnlessEqualassertEqualsassertNotEqual()failIfEqualassertNotEqualsssertTrue()failUnlessassert_assertFalse()failIfassertRaises()failUnlessRaisesassertAlmostEqual()failUnlessAlmostEqualassertAlmostEqualsassertNotAlmostEqual()failIfAlmostEqualassertNotAlmostEqualsassertRegex()assertRegexpMatchesassertNotRegex()assertNotRegexpMatchesassertRaisesRegex()assertRaisesRegexp
代码实例
# encoding utf-8
import unittest
import random
import loggingmylogger logging.Logger(TestToBeTest)# 被测试类
class ToBeTest(object):classmethoddef sum(cls, a, b):return a bclassmethoddef div(cls, a, b):return a/bclassmethoddef return_none(cls):return None# 单元测试类
class TestToBeTest(unittest.TestCase):# assertEqual()方法实例def test_assertequal(self):try:a, b 100, 200sum 300# 断言ab等于sumself.assertEqual(a b, sum, 断言失败%s%s ! %s %(a, b, sum))except AssertionError as e:print(e)# 断言logwith self.assertLogs(assertlog, levelINFO) as cm:logging.getLogger(assertlog).info(first message)logging.getLogger(assertlog.bar).error(second message)self.assertEqual(cm.output, [INFO:assertlog:first message, ERROR:assertlog.bar:second message])# assertNotEqual()方法实例def test_assertnotequal(self):try:a, b 100, 200res -1000# 断言a-b不等于resself.assertNotEqual(a - b, res, 断言失败%s-%s ! %s %(a, b, res))except AssertionError as e:print(e)# assertTure()方法实例def test_asserttrue(self):list1 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]list2 [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]list3 list1[::-1]print(list3)try:# 断言表达式为真self.assertTrue(list3 list2, 表达式为假)except AssertionError as e:print(e)# assertFalse()方法实例def test_assertfalse(self):list1 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]list2 [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]list3 list1[::-1]try:# 断言表达式为假self.assertFalse(list3 list1, 表达式为真)except AssertionError as e:print(e)# assertIs()方法实例def test_assertis(self):list1 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]list2 list1try:# 断言list2和list1属于同一个对象self.assertIs(list1, list2, %s 与 %s 不属于同一对象 % (list1, list2))except AssertionError as e:print(e)# assertIsNot()方法实例def test_assertisnot(self):list1 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]list2 [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]try:# 断言list2和list1不属于同一个对象self.assertIsNot(list2, list1, %s 与 %s 属于同一对象 % (list1, list2))except AssertionError as e:print(e)# assertIsNone()方法实例def test_assertisnone(self):try:results ToBeTest.return_none()# 断言表达式结果是noneself.assertIsNone(results, is not none)except AssertionError as e:print(e)# assertIsNotNone()方法实例def test_assertisnotnone(self):try:results ToBeTest.sum(4, 5)# 断言表达式结果不是noneself.assertIsNotNone(results, is none)except AssertionError as e:print(e)# assertIn()方法实例def test_assertin(self):try:str1 this is unit test demostr2 demo# 断言str2包含在str1中self.assertIn(str2, str1, %s 不被包含在 %s中 %(str2, str1))except AssertionError as e:print(e)# assertNotIn()方法实例def test_assertnotin(self):try:str1 this is unit test demostr2 ABC# 断言str2不包含在str1中self.assertNotIn(str2, str1, %s 包含在 %s 中 % (str2, str1))except AssertionError as e:print(e)# assertIsInstance()方法实例def test_assertisinstance(self):try:o ToBeTestk object# 断言测试对象o是k的类型self.assertIsInstance(o, k, %s的类型不是%s % (o, k))except AssertionError as e:print(e)# assertNotIsInstance()方法实例def test_assertnotisinstance(self):try:o ToBeTestk int# 断言测试对象o不是k的类型self.assertNotIsInstance(o, k, %s 的类型是%s % (o, k))except AssertionError as e:print(e)# assertRaises()方法实例def test_assertraises(self):# 测试抛出指定的异常类型# assertRaises(exception)with self.assertRaises(TypeError) as exc:random.sample([1, 2, 3, 4, 5, 6], j)# 打印详细的异常信息print(exc.exception)# assertRaises(exception, callable, *args, **kwds)try:self.assertRaises(ZeroDivisionError, ToBeTest.div, 3, 0)except ZeroDivisionError as e:print(e)# assertRaisesRegexp()方法实例def test_assertraisesregex(self):# 测试抛出指定的异常类型并用正则表达式去匹配异常信息# assertRaisesRegex(exception, regexp)with self.assertRaisesRegex(ValueError, literal) as exc:int(abc)# 打印详细的异常信息print(exc.exception)# assertRaisesRegex(exception, regexp, callable, *args, **kwds)try:self.assertRaisesRegex(ValueError, invalid literal for.*\abc\$, int, abc)except AssertionError as e:print(e)# assertLogs()方法实例def test_assertlogs(self):with self.assertLogs(mylogger) as log:mylogger.error(打开浏览器)mylogger.info(关闭并退出浏览器)self.assertEqual(log.output, [ERROR:TestToBeTest:打开浏览器, INFO:TestToBeTest:关闭并退出浏览器])if __name__ __main__:unittest.main()