测试代码

测试代码

  介绍如何使用Python模块unittest 中的工具来测试代码。

 

1.  测试函数

  在Python中,测试函数是用于自动化测试,使用python模块中的unittest中的工具来进行测试。

  例如,创建一个函数max_function()接受两个数字,求其最大值,再创建一个函数number_function()提示用户输入两个数

  代码1:

1 def get_max_number(x,y):
2     """求两个数中的最大值"""
3     if x > y :
4         max_number = x
5     else:
6         max_number = y
7     return max_number

 

  代码2:

 1 from max_function import get_max_number
 2 
 3 print("Enter 'q' at any time to quit.")
 4 while True:
 5     x = input("Please enter x:")
 6     if x == 'q':
 7         break
 8     y = input("Please enter y:")
 9     if y == 'q':
10         break
11     max_number = get_max_number(x,y)
12     print("The max is :",max_number,".")

 

  运行结果:

1 Enter 'q' at any time to quit.
2 Please enter x:12
3 Please enter y:23
4 The max is : 23 .
5 Please enter x:66
6 Please enter y:99
7 The max is : 99 .
8 Please enter x:q

 

1.1  单元测试和测试用例

  Python标准库中的模块 unittest 提供了代码测试工具。单元测试用于核实函数的某个方面没有问题;测试用例是一组单元测试,这些单元测试一起核实函数在各种情形下的行为都符合要求。良好的测试用例考虑到了函数可能收到的各种输入,包含针对所有这些情形的测试。全覆盖式测试用例包含一整套单元测试,涵盖了各种可能的函数使用方式。对于大型项目,要实现全覆盖可能很难。通常,最初只要针对代码的重要行为编写测试即可,等项目被广泛使用时再考虑全覆盖。

 

1.2 可通过的测试

  创建测试用例的语法需要一段时间才能习惯,但测试用例创建后,再添加针对函数的单元测试就很简单了。要为函数编写测试用例,可先导入模块 unittest 以及要测试的函数,再创建一个继承 unittest.TestCase 的类,并编写一系列方法对函数行为的不同方面进行测试。

  例如,创建一个包含一个方法的测试用例,它检查函数get_max_number()在给定x、y值时,是否能正确求出最大值。

  代码:

 1 import  unittest
 2 
 3 from max_function import get_max_number
 4 
 5 class TestMaxCase(unittest.TestCase):
 6     """测试max_function.py"""
 7 
 8     def test_x_y(self):
 9         """能够处理x,y这样的数字"""
10         max_number = get_max_number(20,18)
11         self.assertEqual(max_number,20)
12 
13 unittest.main

  说明:

  第1行,导入一个模块unittest。

  第2行,从模块max_function中导入函数get_max_number()。

  第5行,创建了一个名为TestMaxCase的类,用于包含一系列针对 get_max_number() 的单元测试。

  第8行,定义一个方法test_x_y()。

  第10行,调用函数get_max_number()求出最大值,并赋值给变量max_number 。

  第11行,调用 unittest 的方法 assertEqual() ,并向它传递 max_number 和 20代码行self.assertEqual(max_number,20)的意思是说:“将 max_number的值同字数字20进行比较,如果它们相等,就万事大吉,如果它们不相等,跟我说一声!”

  第13行,调用unittest的main方法。

 

  运行结果:

1 Ran 1 test in 0.001s
2 
3 OK

   从第1行可知,测试了一个函数,花费了0.001s;第3行,说明测试OK,通过测试。

 

1.3 不能通过的测试

  例如,我们创建的一个求三个数的最大值的函数,但是只给其提供两个值。

  代码1:

 1 def get_max_number(x,y,z):
 2     """求三个数中的最大值"""
 3     if x > y  and  x > z:
 4         max_number = x
 5     elif y > z:
 6         max_number = y
 7     else:
 8         max_number = z
 9 
10     return max_number

 

  代码2:

 1 import  unittest
 2 
 3 from max_function1 import get_max_number
 4 
 5 class TestMaxCase(unittest.TestCase):
 6     """测试max_function.py"""
 7 
 8     def test_x_y(self):
 9         """能够处理x,y这样的数字"""
10         max_number = get_max_number(20,18)
11         self.assertEqual(max_number,20)
12 
13 unittest.main

 

  代码2的运行结果如下:

 1  Ran 1 test in 0.000s
 2 
 3 FAILED (errors=1)
 4 Launching unittests with arguments python -m unittest test_max_function1.TestMaxCase in F:\PyProject\s14\exercise\chapter_eleven
 5 
 6 Error
7 Traceback (most recent call last):
8   File "D:\Python\Python36\lib\unittest\case.py", line 59, in testPartExecutor
9     yield
10   File "D:\Python\Python36\lib\unittest\case.py", line 601, in run
11     testMethod()
12   File "F:\PyProject\s14\exercise\chapter_eleven\test_max_function1.py", line 12, in test_x_y
13     max_number = get_max_number(20,18)
14 TypeError: get_max_number() missing 1 required positional argument: 'z'

  从以上运行结果可知,由于取最大值的函数需要三个位置实参,但是只给其传了两个,少了一个,由于报错。

 

1.4 测试未通过时怎么办

  当测试不通过时,我们不要测试区修改用于测试的函数,而应该去修改被测试的函数,使其通过测试。

  例如,对1.3中测试未通过的函数进行完善。

  代码1:

 1 def get_max_number(x=0,y=0,z=0):
 2     """求三个数中的最大值"""
 3     if x > y  and  x > z:
 4         max_number = x
 5     elif y > z:
 6         max_number = y
 7     else:
 8         max_number = z
 9 
10     return max_number

  说明:

  第1行,为了在用户不提供任何参数而调用求最大值的函数时不报错,我们给每个形参都赋予一个默认值0。

  

  代码2:

 1 import  unittest
 2 
 3 from max_function2 import get_max_number
 4 
 5 class TestMaxCase(unittest.TestCase):
 6     """测试max_function.py"""
 7 
 8     def test_x_y(self):
 9         """能够处理x,y这样的数字"""
10         max_number = get_max_number(20,18)
11         self.assertEqual(max_number,20)
12 
13 unittest.main

 

  运行结果:

1 Ran 1 test in 0.000s
2 
3 OK

  从以上运行结果可知,测试已通过。

  

1.5 添加新测试

  例如,我们在以上求最大的模块中,增加一个求最小值的函数。

  代码1:

 1 def get_max_number(x=0,y=0,z=0):
 2     """求三个数中的最大值"""
 3     if x > y  and  x > z:
 4         max_number = x
 5     elif y > z:
 6         max_number = y
 7     else:
 8         max_number = z
 9 
10     return max_number
11 
12 def get_min_number(x=0,y=0,z=0):
13     """求三个数中的最小值"""
14     if x < y and x < z :
15         min_number = x
16     elif y < z :
17         min_number = y
18     else:
19         min_number = z
20 
21     return min_number

 

  代码2:

 1 import  unittest
 2 
 3 from max_min_function import get_max_number
 4 from max_min_function import get_min_number
 5 
 6 class TestMaxCase(unittest.TestCase):
 7     """测试max_function.py"""
 8 
 9     def test_max_number(self):
10         """能够处理x,y这样的数字"""
11         max_number = get_max_number(20,18)
12         self.assertEqual(max_number,20)
13 
14     def test_min_number(self):
15         """能否处理x、y、z这样的数字"""
16         min_number = get_min_number(1,13,18)
17         self.assertEqual(min_number,1)
18 
19 
20 unittest.main

 

  运行结果:

1 Ran 2 test in 0.001s
2 
3 OK

  从以上结果可知,两测试都已通过,耗时0.001s。

 

2. 测试类

  测试类就是针对类来编写测试,验证类是否能想我没期待的那样。如果针对类的测试通过了,我没就能确信对类所做的改进没有意外地破坏其原有的行为。

 

2.1 各种断言方法

  Python在 unittest.TestCase 类中提供了很多断言方法。断言方法检查我没认为应该满足的条件是否确实满足。如果该条件确实满足,我们对程序行为的假设就得到了确认,我们就可以确信其中没有错误。如果我们认为应该满足的条件实际上并不满足,Python将引发异常。

  常用的断言方法有:

  (1)assertEqual(a, b),核实 a == b。

  (2)assertNotEqual(a, b),核实 a != b。

  (3)assertTrue(x),核实 x 为 True。

  (4)assertFalse(x),核实 x 为 False。

  (5)assertIn( item , list ),核实 item 在 list 中。

  (6)assertNotIn( item , list ),核实 item 不在 list 中。

 

2.2  测试类

  类的测试与函数的测试相似,只不过类测试所做的大部分工作都是测试类中方法的行为,但存在一些不同之处。

  例如,我们创建一个管理匿名调查问卷的类AnonymousSurvey,并将其存于模块survey中,然后测试它。

  (1)创建类AnonymousSurvey

  代码1:

 1 class AnonymousSurvey():
 2     """收集匿名调查问卷的答案"""
 3 
 4     def __init__(self,question):
 5         """存储一个问题,并为存储答案做准备"""
 6         self.question = question
 7         self.responses = []
 8 
 9     def show_question(self):
10         """显示调查问卷"""
11         print(self.question)
12 
13     def store_response(self,new_response):
14         """存储单份调查问卷"""
15         self.responses.append(new_response)
16 
17     def show_results(self):
18         """显示收集到的所有答案"""
19         print("Survey results:")
20         for reponse in  self.responses:
21             print(('-' + reponse))

  说明:

  第4~7行,存储了一个指定的调查问题,并创建了一个空列表response,用于存储答案。

  第9~11行,创建一个函数show_question(),用于显示调查问卷。

  第13~15行,创建一个函数store_response(),用于存储单份调查问卷,即使用append()方法往答案列表中添加新答案。

  第17~21行,创建一个函数show_response(),用于显示收集到的所有答案,其中使用for循环遍历答案列表response。

 

  代码2:

 1 from survey import AnonymousSurvey
 2 
 3 # 定义一个问题,并创建一个表示调查的AnonymousSurvey对象
 4 question = "What language did you first learn to speak?"
 5 my_survey = AnonymousSurvey(question)
 6 
 7 # 显示问题并存储答案
 8 my_survey.show_question()
 9 print("Enter 'q' at time to quit.\n")
10 while True:
11     response = input("language:")
12     if response == 'q':
13         break
14     my_survey.store_response(response)
15 
16 # 显示调查结果
17 print("\nThank you to everyone who participated in the survey!")
18 my_survey.show_results()

  说明:

  第1行,从模块survey中导入一个类AnonymousSurvey。

  第4~5行,定义一个问题,并应用该问题创建一个表示调查的AnonymousSurvey对象。

  第8~14行,调用 show_question() 来显示问题,并提示用户输入答案。收到每个答案的同时将其存储起来。用户输入所有答案(输入 q 要求退出)

后。

  第17~18行,显示调查结果,即调用 show_results() 来打印调查结果。

 

  运行结果:

 1 What language did you first learn to speak?
 2 Enter 'q' at time to quit.
 3 
 4 language:English
 5 language:Spanish
 6 language:English
 7 language:Chinese
 8 language:Mandarin
 9 language:q
10 
11 Thank you to everyone who participated in the survey!
12 Survey results:
13 -English
14 -Spanish
15 -English
16 -Chinese
17 -Mandarin

 

  (2)测试 AnonymousSurvey 类

  • 测试单个答案

  代码1:

 1 import unittest
 2 from survey import AnonymousSurvey
 3 
 4 class TestAnonmyousSurvey(unittest.TestCase):
 5     """针对AnonymousSurvey类的测试"""
 6 
 7     def test_store_single_response(self):
 8         """测试单个答案会被妥善地存储"""
 9         question = "What language did you first learn to speak?"
10         my_survey = AnonymousSurvey(question)
11         my_survey.store_response('English')
12         self.assertIn('English', my_survey.responses)
13 
14     unittest.main

  说明:

  第1行,导入测试模块unittest。

  第2行,从模块survey中导入类AnonymousSurvey。

  第4行,继承模块unittest中的类TestCase创建一个测试类TestAnonmyousSurvey。

  第7~12行,创建一个方法test_store_single_response来测试单个答案会被妥善地存储。

  第14行,调用模块unittest中的main方法来执行测试。

 

  运行结果:

1 Ran 1 test in 0.000s
2 
3 OK

 

  

  • 测试多个答案

  代码2:

 1 import unittest
 2 from survey import AnonymousSurvey
 3 
 4 class TestAnonmyousSurvey(unittest.TestCase):
 5     """针对AnonymousSurvey类的测试"""
 6 
 7     def test_store_single_response(self):
 8         """测试单个答案会被妥善地存储"""
 9         question = "What language did you first learn to speak?"
10         my_survey = AnonymousSurvey(question)
11         my_survey.store_response('English')
12         self.assertIn('English', my_survey.responses)
13 
14     def test_store_three_responses(self):
15         """测试三个答案会被妥善地存储"""
16         question = "What language did you first learn to speak?"
17         my_survey = AnonymousSurvey(question)
18         responses = ['English', 'Spanish', 'Mandarin']
19         for response in responses:
20             my_survey.store_response(response)
21 
22         for response in responses:
23             self.assertIn(response, my_survey.responses)
24 
25     unittest.main

 

  运行结果:

1 Ran 2 tests in 0.000s2 
3 OK

 

2.3 方法 setUp()的使用

  在前面的test_survey.py中,我们在每个测试方法中都创建了一个 AnonymousSurvey 实例,并在每个方法中都创建了答案。 unittest.TestCase 类包含方法 setUp() ,让我们只需创建这些对象一次,并在每个测试方法中使用它们。如果你在 TestCase 类中包含了方法 setUp() ,Python将先运行它,再运行各个以test_打头的方法。这样,在我们编写的每个测试方法中都可使用在方法 setUp()中创建的对象了。

   例如,使用 setUp() 来创建一个调查对象和一组答案,供方法 test_store_single_response() 和test_store_three_responses() 使用。

  代码:

 1 import unittest
 2 from survey import AnonymousSurvey
 3 
 4 class TestAnonymousSurvey(unittest.TestCase):
 5     """针对AnonymousSurvey类的测试"""
 6     def setUp(self):
 7         """
 8         创建一个调查对象和一组答案,供使用的测试方法使用
 9         """
10         question = "What language did you first learn to speak?"
11         self.my_survey = AnonymousSurvey(question)
12         self.responses = ['English', 'Spanish', 'Mandarin']
13 
14     def test_store_single_response(self):
15         """测试单个答案会被妥善地存储"""
16         self.my_survey.store_response(self.responses[0])
17         self.assertIn(self.responses[0], self.my_survey.responses)
18 
19     def test_store_three_responses(self):
20         """测试三个答案会被妥善地存储"""
21         for response in self.responses:
22             self.my_survey.store_response(response)
23         for response in self.responses:
24             self.assertIn(response, self.my_survey.responses)
25 
26     unittest.main

  说明:

  第11行,创建一个调查对象。

  第12行,创建一个答案列表。

 

  运行结果:

1 Ran 1 test in 0.000s
2 
3 OK

  

  测试自己编写的类时,方法 setUp() 让测试方法编写起来更容易:可在 setUp() 方法中创建一系列实例并设置它们的属性,再在测试方法中直接使用这些实例。相比于在每个测试方法中都创建实例并设置其属性,这要容易得多。  

  运行测试用例时,每完成一个单元测试,Python都打印一个字符:测试通过时打印一个句点;测试引发错误时打印一个 E ;测试导致断言失败时打印一个 F 。这就是你运行测试用例时,在输出的第一行中看到的句点和字符数量各不相同的原因。如果测试用例包含很多单元测试,需要运行很长时间,就可通过观察这些结果来获悉有多少个测试通过了。

 

posted @ 2018-01-13 23:04  晴天云  阅读(3275)  评论(0编辑  收藏  举报