Python中测试代码的介绍


前言

本文的主要内容是Python中测试代码的介绍,包括测试的目的、测试函数以及测试类等内容,文中附有代码以及相应的运行结果辅助理解。


一、测试的目的

测试能够给我们信心,如果你的代码通过了测试,那么即便有再多的人使用你编写的程序,它也能正确地运行。通过测试,可确定代码面对各种输入时都能够按预想的那样工作。
测试是很多初学者都不熟悉的主题,作为初学者,并非必须为你尝试的所有项目编写测试,但参与工作量较大的项目时,应对自己编写的函数和类的重要行为进行测试,这样就能够更加确定自己所做的工作不会破坏项目的其他部分,如果不小心破坏了原来的功能,你马上就会知道,从而能够轻松地修复问题。相比于等到不满意的用户报告bug后再采取措施,在测试未通过时采取措施要容易得多。


二、测试函数

Python标准库中的模块unittest提供了代码测试工具,单元测试用于核实函数的某个方面没有问题,测试用例是一组单元测试,这些单元测试一起核实函数在各种情形下的行为都符合要求。良好的测试用例考虑到了函数可能收到的各种输入,包含针对所有这些情形的测试,全覆盖式测试用例包含一整套单元测试,涵盖了各种可能的函数使用方式。

1.断言方法

Python在unittest.TestCase类中提供了很多断言方法,断言方法的引入是为了检查你认为应该满足的条件是否确实满足,如果该条件确实满足,你对程序行为的假设就得到了确认,就可以确信在该测试中没有错误,如果你认为应该满足的条件实际上并不满足,Python将引发异常。
下面是常见的几个断言方法。
在这里插入图片描述

使用这些方法可核实返回的值等于或不等于预期的值、返回的值为True或False、返回的值在列表中或不在列表中,只能在继承unittest.TestCase的类中使用这些方法。

2.可通过的测试

写测试代码时,首先要导入模块unittest,如果你待测试的函数在另一个模块,也要将该函数导入;接着创建一个测试类,该类名可以自己起,但是这个类必须继承unittest.TestCase类,因为这样Python才知道如何运行你编写的测试;再接着在该类下写一个方法,用来测试你所写函数的正确性,这个方法需要以test_开头,因为在该模块运行后以test_开头的方法才会自动运行,在这个方法中,调用要测试的函数,并存储了要测试的返回值;然后使用unittest类的功能,即一个断言方法,来核实得到的结果是否与期望的结果一致;最后代码行unittest.main()让Python运行这个文件中的测试。
下面来写一个简单的测试函数例子。

import unittest  # 导入模块

def get_full_name(first_name, last_name):  # 待测试的函数,也可以将其单独写在一个模块,然后导入
    """输出全名"""
    full_name = first_name + ' ' + last_name
    return full_name.title()

class NameTestCase(unittest.TestCase):   # 该类名可以自己起,这个类必须继承unittest.TestCase类,这样Python才知道如何运行你编写的测试
    """测试函数"""
    def test_first_last_name(self): # 运行该模块时,所有以test_打头的方法将自动运行
        """是否能够正确地处理只有名和姓的姓名"""
        full_name = get_full_name('kevin', 'durant')
        self.assertEqual(full_name, 'Kevin Durant')  # 使用断言方法判断函数的输出与我们预想的输出是否一致

unittest.main()  # 让Python运行这个文件中的测试

运行结果如下图所示。
在这里插入图片描述
在上面的输出结果中,第1行的句点表明有一个测试通过了(有几个句点就通过了几个测试,本例中只有一个测试);接下来的一行指出Python运行了一个测试,消耗的时间不到0.001秒;最后的OK表明该测试用例中的所有单元测试都通过了。

3.不可通过的测试

简单修改一下上面的例子,使其为不可通过的测试。
下面是不可通过的测试的例子。

import unittest  # 导入模块

def get_full_name(first_name, middle_name, last_name):  # 待测试的函数,也可以将其单独写在一个模块,然后导入
    """输出全名"""
    full_name = first_name + ' ' + middle_name + ' ' + last_name
    return full_name.title()

class NameTestCase(unittest.TestCase):  # 该类名可以自己起,这个类必须继承unittest.TestCase类,这样Python才知道如何运行你编写的测试
    """测试函数"""
    def test_first_last_name(self):  # 运行该模块时,所有以test_打头的方法将自动运行
        """是否能够正确地处理只有名和姓的姓名"""
        full_name = get_full_name('kevin', 'durant')
        self.assertEqual(full_name, 'Kevin Durant')  # 使用断言方法判断函数的输出与我们预想的输出是否一致

unittest.main()  # 让Python运行这个文件中的测试

运行结果如下图所示。
在这里插入图片描述
在上面的输出结果中,第1行的 E 表明有一个单元测试导致了错误;接下来的一行指出了出错误发生的方法;Traceback这里指明了缺少一个必不可少的位置实参;最后显示了运行测试的时间,还看到了一条消息,它指出整个测试用例都未通过,因为运行该测试用例时发生了一个错误。

4.添加多个测试

在测试不可通过的时候,就要查看自己的函数写得是否正确,有必要的话还要多加一个或几个测试。
再来修改一下这个例子,让其既能通过只有名和姓的测试,也能通过除了名和姓还包含中间名的测试。
下面是包含多个测试的例子。

import unittest  # 导入模块

def get_full_name(first_name, last_name, middle_name=''):  # 待测试的函数,也可以将其单独写在一个模块,然后导入
    """输出全名"""
    if middle_name:
        full_name = first_name + ' ' + middle_name + ' ' + last_name
    else:
        full_name = first_name + ' ' + last_name
    return full_name.title()

class NameTestCase(unittest.TestCase):  # 该类名可以自己起,这个类必须继承unittest.TestCase类,这样Python才知道如何运行你编写的测试
    """测试函数"""
    def test_first_last_name(self):  # 运行该模块时,所有以test_打头的方法将自动运行
        """是否能够正确地处理只有名和姓的姓名"""
        full_name = get_full_name('kevin', 'durant')
        self.assertEqual(full_name, 'Kevin Durant')  # 使用断言方法判断函数的输出与我们预想的输出是否一致

    def test_first_middle_last_name(self):  # 运行该模块时,所有以test_打头的方法将自动运行
        """是否能够正确地处理有名、姓和中间名的姓名"""
        full_name = get_full_name('larry', 'jr', 'nance')
        self.assertEqual(full_name, 'Larry Nance Jr')

unittest.main()  # 让Python运行这个文件中的测试

运行结果如下图所示。
在这里插入图片描述
在上面的输出结果中,第1行的两个句点表明有两个测试通过了;接下来的一行指出Python运行了两个测试,消耗的时间不到0.001秒;最后的OK表明该测试用例中的所有单元测试都通过了。


三、测试类

前面是关于函数的测试,下面来进行类的测试,很多程序中都会用到类,因此能够证明类正确地工作相当有用,如果对类的测试通过了,就能确信对类所做的改进没有意外地破坏其原有的行为。

1.简单的测试类

下面是一个简单测试类的例子。

import unittest

class FavoriteProgramLanguage():  # 该类可以单独写一个模块然后导入
    """最喜欢的编程语言"""
    def __init__(self, question):
        """存储一个问题,并创建一个列表用来存放答案"""
        self.question = question
        self.answers = []  # 创建一个列表来存放答案

    def show_question(self):
        """显示问题"""
        print(self.question)

    def save_answer(self, answer):
        """将回答存放进列表"""
        self.answers.append(answer)  # 追加到列表中

class TestFavoriteProgramLanguage(unittest.TestCase):
    """测试类FavoriteProgramLanguage"""
    def test_one_answer(self):
        """测试单个回答是否会被正确地存储"""
        question = "What program language did you like best?"
        my_answer = FavoriteProgramLanguage(question)
        my_answer.save_answer('Python')
        self.assertEqual(question, my_answer.question)   # 查看问题是否相同
        self.assertIn('Python', my_answer.answers)  # 查看'English'是否在列表中

unittest.main()  # 让Python运行这个文件中的测试

运行结果如下图所示。
在这里插入图片描述
在上面这个例子中,类的功能是存储问题和该问题的回答,这个简单的测试类是检验单个答案是否成功的存储在了列表中,由测试结果可知,单个答案存储成功。

2.含多个答案的测试类

上面的例子只测试了一个答案的正确性,下面在该例子的基础上修改,使其可以测试多个答案。
下面是含多个答案测试类的例子。

import unittest

class FavoriteProgramLanguage():
    """最喜欢的编程语言"""
    def __init__(self, question):
        """存储一个问题,并创建一个列表用来存放答案"""
        self.question = question
        self.answers = []

    def show_question(self):
        """显示问题"""
        print(self.question)

    def save_answer(self, answer):
        """将回答存放进列表"""
        self.answers.append(answer)  # 追加到列表中

class TestFavoriteProgramLanguage(unittest.TestCase):
    """测试类FavoriteProgramLanguage"""
    def test_one_answer(self):
        """测试单个回答是否会被正确地存储"""
        question = "What program language did you like best?"
        my_answer = FavoriteProgramLanguage(question)
        my_answer.save_answer('Python')
        self.assertEqual(question, my_answer.question)  # 查看问题是否相同
        self.assertIn('Python', my_answer.answers)  # 查看'English'是否在列表中
      
    def test_three_answer(self):
        """测试多个回答是否会被正确地存储"""
        question = "What program language did you like best?"
        my_answer = FavoriteProgramLanguage(question)
        languages = ['Python', 'Java', 'C']  # 待测试的列表
        for language in languages:
            my_answer.save_answer(language)  # 将列表中的元素依次追加到列表
        self.assertEqual(question, my_answer.question)   # 查看问题是否相同
        for language in languages:
            self.assertIn(language, my_answer.answers)  # 查看多个回答是否都在列表中

unittest.main()  # 让Python运行这个文件中的测试

运行结果如下图所示。
在这里插入图片描述
由运行结果可知,两个测试都通过了。

3.setUp()方法

在上面的例子中,我们在两个测试方法中都创建了一个类的实例,并在每个方法中都创建了答案,这样就显得很繁琐,好在unittest.TestCase类中包含方法setUp(),这让我们只需创建这些对象一次,并在每个测试方法中使用它们。
如果在TestCase类中包含了方法setUp(),Python将先运行它,再运行各个以test_打头的方法。
在上面例子的基础上加入setUp()方法,改进后的代码如下。

import unittest

class FavoriteProgramLanguage():
    """最喜欢的编程语言"""
    def __init__(self, question):
        """存储一个问题,并创建一个列表用来存放答案"""
        self.question = question
        self.answers = []

    def show_question(self):
        """显示问题"""
        print(self.question)

    def save_answer(self, answer):
        """将回答存放进列表"""
        self.answers.append(answer)  # 追加到列表中

class TestFavoriteProgramLanguage(unittest.TestCase):
    """测试类FavoriteProgramLanguage"""

    def setUp(self):
        """创建一个调查对象和一组答案,供使用的测试方法使用"""
        question = "What program language did you like best?"
        self.my_answer = FavoriteProgramLanguage(question)
        self.languages = ['Python', 'Java', 'C']
        self.assertEqual(question, self.my_answer.question)  # 查看问题是否相同

    def test_one_answer(self):
        """测试单个回答是否会被正确地存储"""
        self.my_answer.save_answer(self.languages[0])  # 将第一个元素追加到列表
        self.assertIn(self.languages[0], self.my_answer.answers)  # 查看第一个元素是否在列表中

    def test_three_answer(self):
        """测试多个回答是否会被正确地存储"""
        for language in self.languages:
            self.my_answer.save_answer(language)  # 将列表中的元素依次追加到列表
        for language in self.languages:
            self.assertIn(language, self.my_answer.answers)  # 查看多个回答是否在列表中

unittest.main()  # 让Python运行这个文件中的测试

运行结果如下图所示。
在这里插入图片描述
试想一下,如果在测试类中包含多个方法,那么setUp()方法的作用将会愈加明显。


总结

以上就是Python中测试代码介绍的所有内容了,这部分的内容相对比较简单,在创建的类中继承unittest.TestCase类,并能够熟练正确的运用几个常用的断言方法即可写出测试代码,测试类就是在测试函数基础上的引申。
本文参考书目:Python 编程 :从入门到实践 / (美) 埃里克•马瑟斯(Eric Matthes)著;袁国忠译

猜你喜欢

转载自blog.csdn.net/weixin_42570192/article/details/124281318