Python入门实例——测试代码


测试函数

Python 模块 unittest 主要用来做测试代码使用,这样能够在自己编写代码后使用 Python 脚本编写简单测试代码完成自动测试。例如下面这样的简单函数:

def get_formatted_name(first, last):
    '''Generate a neattly formatted full name.'''
    full_name = first + ' ' + last
    return full_name.title()

函数 get_formatted_name() 将名和姓合并成姓名,在名和姓之间加上一个空格,并将它们的首字母都大写,再返回结果。

接下来,为了核实该函数能够像期望的那样正常工作,编写如下测试例程来实现测试该函数的功能:

from name_func import get_formatted_name

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

执行结果如下:

在这里插入图片描述
上面这种方式只是最简单的通过自己输入参数并将输出打印出来观察是否正确,但是这太繁琐了。所幸 Python 提供了一种自动测试函数输出的高效方式 —— unittest 模块方式。

单元测试和测试用例

还是用于测试上面的函数,创建测试用例进行单元测试,测试代码如下:

import unittest
from name_func import get_formatted_name

class NamesTestCase(unittest.TestCase):
    '''测试 name_func.py '''
    def test_first_last_name(self):
        '''能够正确处理姓名'''
        formatted_name = get_formatted_name('janis', 'jopin')
        self.assertEqual(formatted_name, 'Janis Jopin')
        
unittest.main()

测试代码中,首先导入了模块 unittest 和测试函数对象 get_formatted_name() 。创建一个名为 NamesTestCase 的类,用于包含一系列针对测试对象的单元测试(可以随意给这个类命名)。这个类必须继承 unittest.TestCase类 ,这样 python 才知道如何运行编写好的测试。

运行最后一行的 unittest.main() 可以使得 Python 运行这个文件中的测试。运行结果如下:

在这里插入图片描述

执行结果中第一行的 . (句点)表明有一个测试通过了;接下来的一行指出了 Python 运行了一个测试,消耗的时间不到 0.003 秒,最后的 OK 表明该测试用例中的所有单元测试都通过了。

在开发时不可避免会对原来写好的函数进行修改,但是在源代码修改后,针对之前版本的测试代码也需要相应的修改了。下面对原来的函数进行如下修改:

# 添加中间姓名
def get_formatted_name(first, middle, last):
    '''Generate a neattly formatted full name.'''
    full_name = first + ' ' + middle + ' ' + last
    return full_name.title()

再次执行刚刚编写的测试用例代码,显示结果如下:

在这里插入图片描述可以看到显示的信息很多。

第一行输出的字母 E 指出测试用例中有一个单元测试导致了错误,接下来输出表示 NamesTestCase 中的 test_first_last_name() 导致了错误。当测试用例包含众多单元测试时,知道是哪个测试未通过至关重要。输出显示后面会弹出一个标准的 traceback ,指出了函数测试错误的问题,这里是因为缺少了一个必不可少的位置实参。

针对这个问题,我们其实可以将原函数修改成两个版本的兼容版本,具体如下:

def get_formatted_name(first, last, middle = ''):
    '''Generate a neattly formatted full name.'''
    if middle:
        full_name = first + ' ' + middle + ' ' + last
    else:
        full_name = first + ' ' + last    
    return full_name.title()

在这个版本中,函数既可以接收三个参数的调用也同时可以接收旧版本的两参数调用,并且我们可以在原来的测试代码中添加新的测试用例,修改如下:

import unittest
from name_func import get_formatted_name

class NamesTestCase(unittest.TestCase):
    '''测试 name_func.py '''
    def test_first_last_name(self):
        '''测试两参数正确处理姓名函数'''
        formatted_name = get_formatted_name('janis', 'jopin')
        self.assertEqual(formatted_name, 'Janis Jopin')
    
    def test_first_middle_last_name(self):
        '''测试三参数正确处理姓名函数'''
        formatted_name = get_formatted_name('janis', 'jopin', 'middle')
        self.assertEqual(formatted_name, 'Janis Middle Jopin')
        
unittest.main()

执行这个新的测试用例,结果如下:

在这里插入图片描述

从结果可以看出,测试用例通过了。

测试类

前面已经说明针对单个函数的测试如何编写,但是 Python 作为面向对象的语言,更多的则是以类存在,所以在 Python 测试中其实更多的也是针对类的测试。在编写开发新的类后,如何能够证明你开发的新类能够正常工作或者能够使得代码开发正常。

接下来就是针对类的测试。

各种断言方法

Python 在 unittest.TestCase类 中提供了很多断言方法。前面能够看出,断言方法能够检查应该满足的条件是否确实满足。如果该条件确实满足,那么对程序行为的假设就得到了确认,也就可以确信这中间没用错误。如果实际上条件并没有得到满足,Python 将引发异常。

下面是6个常用到的断言方法。使用这些方法可以核实返回的值等于或不等于预期的值、返回的值为 True 或 False 、 返回的值在列表中或不在列表中。并且只能在继承 unittest.TestCase 的类中使用这些方法。

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

编写待测试的类

下面编写一个类进行测试,具体代码如下:

class AnonymousSurvey():
    '''收集匿名调查问卷的答案'''
    def __init__(self, question):
        '''存储一个问题,并为存储答案做准备'''
        self.question = question
        self.responses = []
        
    def show_question(self):
        '''显示调查问卷'''
        print(self.question)
        
    def shore_response(self, new_response):
        '''存储单份调查问卷'''
        self.responses.append(new_response)
        
    def show_results(self):
        '''显示收集到的所有答案'''
        print("Survey results:")
        for response in self.responses:
            print("- " + response)

在个类首先需要存储一个指定的调查问题,并创建一个空列表,用于存储答案。这个类包含打印调查问题的方法、在答案列表中添加新答案的方法以及将存储在列表中的答案都打印出来的方法。要创建这个类的实例,只需要提供一个问题即可,有了类对象实例后,就可以使用 show_question() 来显示其中的问题,使用 shore_response() 来存储答案,并使用 show_results() 来显示调查结果。

具体使用方式如以下代码所示:

from survey import AnonymousSurvey

# 定义一个问题,并创建一个表示调查的 AnonymousSurvey 对象
question = "What language did you first learn to speak?"
my_survey = AnonymousSurvey(question)

# 显示问题并存储答案
my_survey.show_question()
print("Enter 'q' at any time to quit.\n")
while True:
    response = input("Language: ")
    if response == 'q':
        break
    my_survey.store_response(response)
    
# 显示调查结果
print("\nThank you to everyone who participated in the survey!")
my_survey.show_results()

执行结果如下:

在这里插入图片描述

测试类

接下来就针对这个类的行为各方面编写一个测试代码,通过 assertIn() 来核实结果,具体代码:

import unittest
from survey import AnonymousSurvey

class TestAnonmyousSurvey(unittest.TestCase):
    '''针对AnonymousSurvey类的测试'''
    def test_store_single_response(self):
        '''测试单个答案会被妥善存储'''
        question = "What language did you first learn to speak?"
        my_survey = AnonymousSurvey(question)
        my_survey.shore_response("English")
        my_survey.shore_response("Chinese")
        
        self.assertIn("English", my_survey.responses)
        
unittest.main()

执行测试代码结果如下:

在这里插入图片描述

添加测试用例,修改如下:

import unittest
from survey import AnonymousSurvey

class TestAnonmyousSurvey(unittest.TestCase):
    '''针对AnonymousSurvey类的测试'''
    def test_store_single_response(self):
        '''测试单个答案会被妥善存储'''
        question = "What language did you first learn to speak?"
        my_survey = AnonymousSurvey(question)
        my_survey.shore_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', 'Chinese', 'Mandarin']
        for response in responses:
            my_survey.shore_response(response)
            
        for response in responses:
            self.assertIn(response, my_survey.responses)
        
unittest.main()

执行结果如下:

在这里插入图片描述

方法 setUp()

前面测试类的多个测试用例的代码中,每个测试用例都创建了一个实例对象及相应的操作。其实在 unittest.TestCase() 类中包含方法 setUp() ,这样我们只需要执行这些操作一次就可以在后面测试方法中使用它们。修改上面的代码如下:

import unittest
from survey import AnonymousSurvey

class TestAnonmyousSurvey(unittest.TestCase):
    '''针对AnonymousSurvey类的测试'''
    
    def setUp(self):
        '''创建一个调查对象和一组答案,供测试用例使用'''
        question = "What language did you first learn to speak?"
        self.my_survey = AnonymousSurvey(question)
        self.responses = ['English', 'Chinese', 'Mandarin']
    
    def test_store_single_response(self):
        '''测试单个答案会被妥善存储'''
        self.my_survey.shore_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.shore_response(response)
        for response in self.responses:
            self.assertIn(response, self.my_survey.responses)
        
unittest.main()

执行结果如下:

在这里插入图片描述

可以看出两项测试也都通过了,并且测试代码编写的更精简了。

总结

本篇文章中,使用模块 unittest 中的工具为函数和类编写测试,编写继承 unittest.TestCase类 以及编写测试方法,使用 assert 断言的方式来核实函数和类的行为是否符合预期以判断测试是否通过。并且使用方法 setUp() 根据类高效创建实例并设置属性,以便在类的所有测试方法中可以使用它们,从而精简测试类代码的编写。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章