Python 基础 -- 测试代码

  • 编写函数或者类时,还可以为其编写测试;

  • 通过测试,可以确定代码面对各种输入都能够按照要求的那样工作;

  • 在程序新添加新代码时,可以对其进行测试,确保它们不会破坏程序既有的行为;

  • 使用 Python 模块 unittest 中的测试工具来测试代码;

  • 测试代码的最终目的就是核实一系列的输入都将得到预期的输出;

1. 测试函数

  • 要学习测试函数,得有测试代码;

  • 编写一个简单的函数,它接受名和姓,并返回整洁的姓名;

    name_function.py

    def get_formatted_name(first, last):
    	"""生成完整的名字"""
    	
    	full_name = first + " " + last
    	return full_name.title()
    
  • 为核实 get_formatted_name 像期望的那样工作,编写一个使用这个函数的程序;

  • 这个程序,输入用户的名和姓,并整洁的显示全名;

    names.py

    from name_function import get_formatted_name
    
    msg = "Enter 'q' at any time to quit!"
    print(msg)
    
    while True:
    	msg = "\nPlease give me a first name: "
    	first = input(msg)
    	if first == 'q':
    		break
    		
    	msg = "Please give me a last name: "	
    	last = input(msg)
    	if last == 'q':
    		break
    		
    	formatted_name = get_formatted_name(first, last)
    	msg = "\tNeatly formatted name: " + formatted_name + "."
    	print(msg)
    

    Enter 'q' at any time to quit!
    
    Please give me a first name: li
    Please give me a last name: si
    	Neatly formatted name: Li Si.
    
    Please give me a first name: liu  
    Please give me a last name: neng
    	Neatly formatted name: Liu Neng.
    
    Please give me a first name: zhao
    Please give me a last name: si
    	Neatly formatted name: Zhao Si.
    
    Please give me a first name: q
    
    
    ------------------
    (program exited with code: 0)
    Press return to continue
    
  • 从上述输出可知,合并得到的姓名正确无误;

  • 但是当修改后,每次对函数进行测试就太繁琐了;

  • 所幸 Python 提供了一种自动测试函数的高效方式;

1.1 单元测试和测试用例

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

1.2 可通过的测试

  • 要为函数编写测试用例;

  • 可先导入模块 unittest 以及要测试的函数;

  • 再创建一个继承 unittest.TestCase 的类;

  • 并编写一系列方法对函数行为的不同方面进行测试;

  • 下面是一个只包含一个方法的测试用例,它检查函数 get_formatted_name() 在给定名和姓时是否能正常工作;

    test_name_function.py

    import unittest
    from name_function import get_formatted_name
    
    class NameTestCase(unittest.TestCase):
    	"""测试 name_function.py"""
    	
    	def test_first_last_name(self):
    		formatted_name = get_formatted_name('li', 'si')
    		self.assertEqual(formatted_name, 'Li Si')
    
    unittest.main()
    
    • 第 1 行,导入了 Python 提供的标准库中的模块 unittest
    • 第 2 行,导入了要测试的函数 get_formatted_name
    • 第 4 行,创建了一个名为 NameTestCase 的类,用于包含一系列针对 get_formatted_name() 的单元测试;
      • 这个类的命名建议:
        • 看起来与测试函数相关;
        • 包含 Test 字样;
        • 这个类必须继承 unittest.TestCase
    • 第 7 行,定义一个用于测试 get_formatted_name() 的一个方法;
      • 这个方法命名为 test_first_last_name()
      • Python 在执行测试用例时,会自动执行所有以 test 开头的方法,所以测试用例里边的方法都需要以 test 开头命名;
    • 第 8 行,调用了要测试的函数,并存储了要测试的返回值;
    • 第 9 行,使用了 unittest 类最有用的功能之一:断言方法
      • 断言方法:核实得到的结果是否与期望的结果一致;
    • 第 11 行,让 Python 运行这个文件中的测试;
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK
    
    
    ------------------
    (program exited with code: 0)
    Press return to continue
    
    • 第 1 行,. 表示通过;
    • 第 3 行,表示消耗的时间;
    • 第 5 行,表示该测试用例中的所有单元测试都通过了;

1.3 不能通过的测试

  • 我们通过修改 get_formatted_name() 函数,来使得测试不通过,看看结果;

    name_function.py

    def get_formatted_name(first, middle, last):
    	"""生成完整的名字"""
    	
    	full_name = first + " " + middle + " "+ last
    	return full_name.title()
    
  • 这个版本包含了中间名,对其进行测试:

    E
    ======================================================================
    ERROR: test_first_last_name (__main__.NameTestCase)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "test_name_function.py", line 8, in test_first_last_name
        formatted_name = get_formatted_name('li', 'si')
    TypeError: get_formatted_name() missing 1 required positional argument: 'last'
    
    ----------------------------------------------------------------------
    Ran 1 test in 0.001s
    
    FAILED (errors=1)
    
    
    ------------------
    (program exited with code: 1)
    Press return to continue
    
    • 第 1 行,E 表示测试用例中有一个单元测试导致了错误;
    • 第 3 行,显示了错误的函数,当单元测试比较多时,知道那个测试未通过至关重要;
    • 第 5 行,看到一个标准的 traceback ,它指出函数调用 get_formatted_name('li', 'si') 有问题;
    • 第 8 行,指出问题是缺少一个必要的位置实参;
    • 第 11 行,表示运行了一个单元测试,消耗的时间;
    • 第 13 行,告诉你测试用例没通过;

1.4 测试未通过时怎么办

  • 检查条件如果没有出错,测试通过了意味着函数的行为是对的,而测试没通过意味着编写的新代码有错;

  • 测试未通过时,不要修改测试,而应该修复导致测试不能通过的代码;

  • 检查对函数所作的修改,找出导致函数行为不符合预期的修改;

  • 还有一种可能是,修改了函数以及测试用例后没有再次保存函数;

  • 针对上述代码,我们可以将中间名设置为可选项:

    name_function.py

    def get_formatted_name(first, last, middle=''):
    	"""生成完整的名字"""
    	if middle:
    		full_name = first + " " + middle + " " + last
    	else:
    		full_name = first + " " + last
    	return full_name.title()
    

    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK
    
    
    ------------------
    (program exited with code: 0)
    Press return to continue
    

1.5 添加新测试

  • 确定 get_formatted_name() 能够正确处理简单姓名之后,我们在编写一个测试,用于测试包含中间名的姓名;

  • 为此,我们在 NameTestCase 类中添加一个新的方法;

    import unittest
    from name_function import get_formatted_name
    
    class NameTestCase(unittest.TestCase):
    	"""测试 name_function.py"""
    	
    	def test_first_last_name(self):
    		formatted_name = get_formatted_name('li', 'si')
    		self.assertEqual(formatted_name, 'Li Si')
    	
    	def test_first_middle_last_name(self):
    		formatted_name = get_formatted_name('li', 'si', 'liu')
    		self.assertEqual(formatted_name, 'Li Liu Si')
    
    unittest.main()
    
    • 方法命名必须以 test 开头;
    • 方法名可以是很长的名字;
    • 方法名必须是描述性的;

    ..
    ----------------------------------------------------------------------
    Ran 2 tests in 0.001s
    
    OK
    
    
    ------------------
    (program exited with code: 0)
    Press return to continue
    

1.6 练习

1. 城市和国家

  • 编写一个函数,它接收两个形参:一个城市名和一个国家名;

  • 这个函数返回一个格式为 City, Country 的字符串,如 Santiago, Chile;

  • 将这个函数存储在一个名为 city_functions.py 的模块中;

  • 创建一个名为 test_cities.py 的程序,对刚编写的函数进行测试;

  • 编写一个名为 test_city_country() 的方法,核实使用类似于 santiagochile 这样的值来调用前述函数时,得到的字符串是正确的;

  • 运行 test_cities.py,确认测试 test_city_country() 通过了;

    city_country.py

    def city_country_soon(city, country):
    	msg = city.title() + ", " + country.title()
    	return msg
    

    test_cities.py

    import unittest
    from city_country import city_country_soon
    
    class CityTestCase(unittest.TestCase):
    	"""测试 city_country.py"""
    	
    	def test_city_country(self):
    		msg = city_country_soon('santiago', 'chile')
    		self.assertEqual(msg, 'Santiago, Chile')
    
    unittest.main()
    

    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.002s
    
    OK
    
    
    ------------------
    (program exited with code: 0)
    Press return to continue
    

2. 人口数量

  • 修改前面的函数,使其包含第三个必不可少的形参 population,并返回一个格式为 City, Country-population xxx 的字符串,如 Santiago, Chile-population 5000000;

  • 运行 test_cities.py,确认测试 test_city_country() 未通过;

  • 修改上述函数,将形参 population 设置为可选项;

  • 再次运行 test_cities.py 确认测试 test_city_country() 通过了;

  • 在编写一个名为 test_city_country_populaiton() 的测试,核实可以使用类似于 santiagochilepopulation=5000000 这样的值来调用这个函数;

  • 再次运行 test_cities.py 确认 test_city_country_populaiton() 通过了;

    city_country.py

    def city_country_soon(city, country, population=''):
    	
    	if population:
    		msg = city.title() + ", " + country.title() + "-population" + " " + str(population)
    	else:
    		msg = city.title() + ", " + country.title()
    
    	return msg
    

    test_cities.py

    import unittest
    from city_country import city_country_soon
    
    class CityTestCase(unittest.TestCase):
    	"""测试 city_country.py"""
    	
    	def test_city_country(self):
    		msg = city_country_soon('santiago', 'chile')
    		self.assertEqual(msg, 'Santiago, Chile')
    		
    	def test_city_country_populaiton(self):
    		msg = city_country_soon('santiago', 'chile', 5000000)
    		self.assertEqual(msg, 'Santiago, Chile-population 5000000')
    	
    unittest.main()
    

    ..
    ----------------------------------------------------------------------
    Ran 2 tests in 0.001s
    
    OK
    
    
    ------------------
    (program exited with code: 0)
    Press return to continue
    

2. 测试类

  • 对类进行测试也有很大的裨益;
  • 若针对类的测试通过了,就可以确信对类所做的改进没有意外的破坏其原有的行为;

2.1 断言方法

  • 之前在测试函数中了解过一种断言方法;

  • 所谓的断言方法就是判断你所认为应该满足的条件是否被满足;

  • 常用的断言方法;

    方法用途
    assertEqual(a, b)核实 a == b
    assertNotEqual(a, b)核实 a != b
    assertTrue(x)核实 x 为 True
    assertFalse(x)核实 x 为 False
    assertIn(item, list)核实 item 在 list 中
    assertNotIn(item, list)核实 item 不在 list 中

2.2 一个要测试的类

  • 类的测试与函数的测试相似,大部分工作都是测试类中方法的行为;

  • 编写一个帮住管理匿名调查的类;

    survey.py

    class AnonymousSurvey():
    	"""收集匿名调查问卷的答案"""
    	
    	def __init__(self, question):
    		"""存储一个问题,并为存储答案做准备"""
    		self.question = question
    		self.responses = []
    		
    	def show_question(self):
    		"""显示调查问卷"""
    		print(self.question)
    		
    	def store_response(self, new_response):
    		"""存储单份调查答案"""
    		self.responses.append(new_response)
    		
    	def show_results(self):
    		"""显示收集到的所有答卷"""
    		msg = "Survey results: "
    		print(msg)
    		
    		for response in self.responses:
    			msg = "- " + response
    			print(msg)
    
    • 创建这个类的实例,只需要提供一个问题即可;
    • 有了表示调查的实例后,就可以使用 show_question() 来显示其中的问题;
    • 使用 store_response() 来存储答案;
    • 使用 show_results() 来显示调查结果;
  • 为了证明 AnonymousSurvey 类可以正确的工作,来编写一个使用它的程序:

    language_survey.py

    from survey import AnonymousSurvey
    
    # 定义一个问题,并创建一个表示调查的 AnonymousSurvey 对象
    question = "What language did you first learn to speak?"
    my_survey = AnonymousSurvey(question)
    
    # 显示问题并存储答案
    my_survey.show_question()
    msg = "Enter 'q' at any time to quit.\n"
    print(msg)
    
    while True:
    	msg = "language: "
    	response = input(msg)
    	if response == 'q':
    		break
    	my_survey.store_response(response)
    
    # 显示调查结果
    msg = "\nThank you to every who participated in the survey!"
    print(msg)
    my_survey.show_results()
    

    What language did you first learn to speak?
    Enter 'q' at any time to quit.
    
    language: chinese
    language: C
    language: English
    language: Jav
    language: q
    
    Thank you to every who participated in the survey!
    Survey results: 
    - chinese
    - C
    - English
    - Jav
    
    
    ------------------
    (program exited with code: 0)
    Press return to continue
    

2.3 测试 AnonymousSurvey 类

  • 首先编写一个测试对 AnonymousSurvey 类的一个方面进行验证;

  • 如果用户面对调查问题时只提供了一个答案,这个答案也能被妥善存储;

  • 为此,我们将这个答案被存储之后,使用方法 assertIn() 来核实它包含在答案列表中;

    test_survey.py

    import unittest
    from survey import AnonymousSurvey
    
    class TestAnonymousSurvey(unittest.TestCase):
    	"""针对 AnonymousSurvey 类的测试"""
    	
    	def test_store_single_response(self):
    		"""测试单个案例也会被妥善的保存"""
    		question = "What language did you first learn to speak?"
    		my_survey = AnonymousSurvey(question)
    		my_survey.store_response('English')
    		
    		self.assertIn('English', my_survey.responses)
    		
    unittest.main()
    
    • 测试类与测试函数的操作基本相同,唯一不同就是需要创建类的实例,如第 10 行;
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK
    
    
    ------------------
    (program exited with code: 0)
    Press return to continue
    
  • 测试用户提供三个答案也能被妥善保存;

    import unittest
    from survey import AnonymousSurvey
    
    class TestAnonymousSurvey(unittest.TestCase):
    	"""针对 AnonymousSurvey 类的测试"""
    	
    	def test_store_single_response(self):
    		"""测试单个案例也会被妥善的保存"""
    		question = "What language did you first learn to speak?"
    		my_survey = AnonymousSurvey(question)
    		my_survey.store_response('English')
    		
    		self.assertIn('English', my_survey.responses)
    	
    	def  test_store_three_responses(self):
    		"""测试三个案例也会被妥善保存"""
    		question = "What language did you first learn to speak?"
    		my_survey = AnonymousSurvey(question)
    		responses = ['English', 'Spanish', 'Mandarin']
    		for response in responses:
    			my_survey.store_response(response)
    		
    		for response in responses:
    			self.assertIn(response, my_survey.responses)
    		
    unittest.main()
    

    ..
    ----------------------------------------------------------------------
    Ran 2 tests in 0.001s
    
    OK
    
    
    ------------------
    (program exited with code: 0)
    Press return to continue
    

2.4 setUp() 方法

  • 之前的测试函数效果很好,但是有重复的地方,略显得累赘;

  • 使用 setUp() 函数可以只将对象创建一次;

    import unittest
    from survey import AnonymousSurvey
    
    class TestAnonymousSurvey(unittest.TestCase):
    	"""针对 AnonymousSurvey 类的测试"""
    	
    	def setUp(self):
    		"""创建一个调查对象和一组答案,供使用的测试方法使用"""
    		question = "What language did you first learn to speak?"
    		self.my_survey = AnonymousSurvey(question)
    		self.responses = ['English', 'Spanish', 'Mandarin']
    
    	def test_store_single_response(self):
    		"""测试单个案例也会被妥善的保存"""
    		self.my_survey.store_response(self.responses[0])
    		self.assertIn(self.responses[0], self.my_survey.responses)
    	
    	def  test_store_three_responses(self):
    		"""测试三个案例也会被妥善保存"""
    		for response in self.responses:
    			self.my_survey.store_response(response)
    		
    		for response in self.responses:
    			self.assertIn(response, self.my_survey.responses)
    		
    unittest.main()
    
    • 第 7 行,setUp() 方法做了两件事:
      • 创建对象,第 10 行;
      • 创建一个列表答案,第 11 行;
      • 因为存储这两样变量的前缀都带有 self,所以可以在这个类的任意地方使用;

2.5 练习 – 雇员

  • 编写一个名为 Employee 的类,其方法 __init__() 接受名、姓和年薪,并将它们都存储在属性中;

  • 编写一个名为 give_raise() 的方法,它默认将年薪增加 5000 美元,但也能够接受其他的年薪增加量;

  • Employee 编写一个测试用例,其中包含两个测试方法:

  • test_give_default_raise()test_give_custom_raise()

  • 使用方法 setUp(),以免在每个测试方法中都创建新的雇员实例;

  • 运行这个测试用例,确认两个测试都通过了;

    class Employee():
    	"""这是一个雇员类"""
    	
    	def __init__(self, first, last, salary):
    		"""雇员的基本信息"""
    		self.first_name = first
    		self.first_last = last
    		self.salary = salary
    		
    	def give_raise(self, add=5000):
    		"""雇员的年薪增加"""
    		self.salary += add
    

    import unittest
    from employee_3 import Employee
    
    class TestEmployee(unittest.TestCase):
    	
    	def setUp(self):
    		"""创建一组雇员信息,提供用的测试方法"""
    		self.employee = Employee('li', 'si', 5000)
    
    	def test_give_default_raise(self):
    		"""测试默认加工资"""
    		self.employee.give_raise()
    		self.assertEqual(self.employee.salary, 10000)
    	
    	def test_give_custom_raise(self):
    		"""测试自定义的加工资"""
    		self.employee.give_raise(10000)
    		self.assertEqual(self.employee.salary, 15000)
    			
    unittest.main()
    

    ..
    ----------------------------------------------------------------------
    Ran 2 tests in 0.001s
    
    OK
    
    
    ------------------
    (program exited with code: 0)
    Press return to continue
    
  • 进行测试用例时,每完成一个单元测试,Python 都打印一个字符;
  • 测试通过时打印一个 .
  • 测试引发错误时打印一个 E
  • 测试导致断言失败时打印一个 F
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
#模块导入 import pygame,sys from pygame.locals import* #初始化pygame pygame.init() #设置窗口大小,单位是像素 screen = pygame.display.set_mode((500,400)) #设置背景颜色 screen.fill((0,0,0)) #设置窗口标题 pygame.display.set_caption("你好,我的朋友") # 绘制一条线 pygame.draw.rect(screen, (0,0,0), [0,100,70,40]) #加载图片 img = pygame.image.load("panda.jpg") #初始化图片位置 imgx = 0 imgy = 10 #加载和播放音频 sound = pygame.mixer.Sound('Sound_Of_The_Sea.ogg') sound.play() #加载背景音乐 pygame.mixer.music.load('TEST1.mp3') #播放背景音乐,第一个参数为播放的次数(-1表示无限循环),第二个参数是设置播放的起点(单位为秒) pygame.mixer.music.play(-1, 30.0) #导入文字格式 fontt=pygame.font.Font(None,50) #配置文字 tex=fontt.render("It is boring!!!",True,(0,0,128),(0,255,0)) #显示文字及坐标 texr=tex.get_rect() texr.center=(10,250) #初始化方向 dire = "right" #设置循环 while 1: #绘制文字 screen.blit(tex,texr) screen.fill((0,0,0)) screen.blit(img,(imgx,imgy)) if dire == "right": imgx+=5 if imgx == 380: dire = 'down' elif dire == 'down': imgy += 5 if imgy == 300: dire = 'left' elif dire == 'left': imgx -= 5 if imgx == 10: dire = 'up' elif dire == 'up': imgy -= 5 if imgy == 10: dire = 'right' #获取事件 for ss in pygame.event.get(): #判断事件 if ss.type == QUIT: #退出Pygame pygame.quit() #退出系统 sys.exit() #绘制屏幕内容 pygame.display.update() #设置帧率 pygame.time.delay(10)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值