Python UnitTest 官方文档 敬上,在没有Debug环境的情况下,UnitTest简直就是无敌的存在了,报错清晰,言简意赅,便于迅速定位错误。
单元测试的三种测试样例
完整的单元测试样例包含功能测试、边界测试、负面测试三种类型。
完整的单元测试才能孕育出完整的代码。
普通功能测试 :我们首先要保证写出的代码能够完成面试官要求的基本功能。比如面试题要求完成的功能是把字符串转换成整数,我们就可以考虑输入字符串”123”来测试自己写的代码。这里要把零、正数和负数 都考虑进去。在考虑功能测试的时候,我们要尽量突破常规思维的限制。面试的时候我们经常受到惯性思维的限制,从而看不到更多的功能需求。比如面试题17“打印从1到最大的n位数”,很多人觉得这道题很简单。最大的3位数是999、最大的4位数是9999,这些数字很容易就能算出来。但是最大的n位数都能用int型表示吗?超出int的范围我们可以考虑long long类型,超出long long能够表示的范围呢?面试官是不是要求考虑任意大的数字?如果面试官确认题目要求的是任意大的数字,那么这道题目就是一个大数问题 ,此时我们需要特殊的数据结构 来表示数字,比如用字符串或者数组来表示大的数字,以确保不会溢出。
边界值的测试 :很多时候我们的代码中都会有循环或者递归。如果我们的代码基于循环,那么结束循环的边界条件是否正确? 如果基于递归,那么递归终止的边界值是否正确? 这些都是边界测试时要考虑的用例。还是以字符串转换成整数的问题为例,我们写出的代码应该确保能够正确转换最大的正整数和最小的负整数。
错误输入(负面)测试 :我们写出的函数除了要顺利地完成要求的功能,当输入不符合要求的时候还能做出合理的错误处理。 在设计把字符串转换成整数的函数的时候,我们就要考虑当输入的字符串不是一个数字时,比如“1a2b3c",该怎么告诉函数的调用者这个输入是非法的。
代码的鲁棒性
所谓鲁棒性(Robust),所谓的鲁棒性是指程序能够判断输入是否合乎规范要求,并对不符合要求的输入予以合理的处理。
常用特殊输入
int32:正数、负数、0、-2**31、(2**31)-1
list:[]
ListNode:None、头节点、尾节点 、空链表、单节点链表
Tree:功能测试(普通的二叉树 ;二叉树的所有节点都没有左子树或者右子树 :只有一个节点的二叉树 )。 特殊输入测试(二叉树的根节点为None ,也即是一个空树 ;所有节点的值都相同 的二叉树)。
str:功能测试(输入的字符串中有一个或者多个字符;只有一个字符的字符串;所有字符都唯一的字符串;所有字符都相同的字符串)。 特殊输入测试(输入的字符串的内容为空或者 nullpt
r指针)。
matrx:功能测试(多行多列的矩阵;一行或者一列的矩阵;只有一个数字的矩阵 )。 特殊输入测试(指向矩阵数组的指针为nullptr)。
Python UnitTest Module
unittest的设计灵感最初来源于 Junit
以及其他语言中具有共同特征的单元框架。它支持自动化测试,在测试中使用setup(初始化)和shutdown(关闭销毁)操作,组织测试用例为套件(批量运行),以及把测试和报告独立开来。
通过以下几个示例,你就能理解如何使用unittest了。
示例一 :理解 assertEqual
、test_isupper
、test_split
的区别,以及简单启动测试的方法 unittest.main()
,如何测试自定义函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import unittestdef add (x, y ): """自定义函数 """ return x + y class TestStringMethods (unittest.TestCase ): def test_upper (self ): self.assertEqual(add(1 , 1 ), 2 ) def test_isupper (self ): self.assertTrue('FOO' .isupper()) self.assertFalse('Foo' .isupper()) def test_split (self ): s = 'hello world' self.assertEqual(s.split(), ['hello' , 'world' ]) with self.assertRaises(TypeError): s.split(2 ) if __name__ == '__main__' : unittest.main()
输出
示例二 :理解自主控制测试用例的方法 runner.run(suite)
以及setUp
方法、tearDown
方法的含义,以及如何在一个测试样例中使用多组数据,如何测试类对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 import unittestclass compute (): def add (self, x, y ): return x + y def multipy (self, x, y ): return x * y def divided (self, x, y ): return x // yclass TestCompute (unittest.TestCase ): @classmethod def setUpClass (cls ): """类对象方法 这是所有case的前置条件 """ cls.test_class = compute() def setUp (self ): """示例对象方法 这是每条case的前置条件 """ pass def test_add (self ): inputs = [(1 , 5 ), (2 , 4 ), (3 , 3 )] answer = [6 , 6 , 6 ] for i in range (len (inputs)): self.assertEqual(self.test_class.add(inputs[i][0 ], inputs[i][1 ]), answer[i]) def test_multipy (self ): self.assertEqual(self.test_class.multipy(6 , 6 ), 36 ) def test_divided (self ): self.assertEqual(self.test_class.divided(72 , 2 ), 36 ) @classmethod def tearDownClass (cls ): """这是所有case的后置条件 """ del cls.test_class def tearDown (self ): """这是每条case的后置条件 """ pass if __name__ == '__main__' : suite = unittest.TestSuite() suite.addTest(TestCompute("test_add" )) suite.addTest(TestCompute("test_multipy" )) runner = unittest.TextTestRunner() runner.run(suite)
输出
即使,TestCompute
有三个用例,但是由于自行构造用例集的过程中只添加了两个所以,也没有像 unittest.main()
那样,全部跑完三个样例。
示例三 :一个LeetCode答案及其测试样例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import unittestclass Solution : def replaceSpace (self, s: str ) -> str: if not len (s): return '' padding = '%20' count = 0 for i in s: if i == ' ' : count += 1 p1 = len (s) - 1 s += ' ' * count * 2 p2 = len (s) - 1 s = list (s) while p1 != p2 or (s[p1] == ' ' and s[p2] == ' ' ): if s[p1] != " " : s[p2] = s[p1] p1 -= 1 p2 -= 1 else : p1 -= 1 for i in range (2 , -1 , -1 ): s[p2] = padding[i] p2 -= 1 return "" .join(s)class TestSulotion (unittest.TestCase ): """声明Python UnitTest Class用于测试 """ def setUp (self ): self.test_class = Solution() def test_replaceSpace (self ): s = ["cad eaeb" , "a b" , "a b " , " a b" , " " , "We are happy." , "" ] answer = ["cad%20eaeb" , "a%20%20%20b" , "a%20b%20%20" , "%20%20a%20b" , "%20%20%20%20%20" , "We%20are%20happy." , "" ] for i in range (len (s)): self.assertEqual(self.test_class.replaceSpace(s[i]), answer[i]) def tearDown (self ): del self.test_classif __name__ == "__main__" : unittest.main()
输出
---------------------------------------------------------------------- Ran 1 test in 0.001s OK PS C:\U sers\T ommy\. leetcode>