资讯专栏INFORMATION COLUMN

Python学习之路10-测试代码

huangjinnan / 3154人阅读

摘要:也就是说,你可以将上述代码中的看做单元测试,而将看做测试用例。在测试类中的每一个测试方法都必须以开头,否则将不会被认定是一个单元测试。

《Python编程:从入门到实践》笔记。
本章主要学习如何使用Python标准库中的unittest模块对代码进行简单的测试。
1. 前言

作为初学者,并非必须为你尝试的所有项目编写测试;但参与工作量较大的项目时,你应对自己编写的函数和类的重要行为进行测试。这样你就能够更加确定自己所做的工作不会破坏项目的其他部分,你就能够随心所欲地改进即有代码。如果不小心破坏了原来的功能,你马上就会知道,从而能够轻松地修复问题。相比于等到Bug出现后再去改,在测试未通过时采取措施要容易得多。而且,如果你想要分享你的项目,有测试的代码更容易让人接受。

2. 测试函数 2.1 一个能通过的测试

以下是一个将用户输入的姓与名拼接的函数:

# name_function.py
def get_formatted_name(first, last):
    """返回一个整洁的完整姓名"""
    full_name = first + " " + last
    return full_name.title()


if __name__ == "__main__":
    print("Enter "q" at any time to quit.")
    while True:
        first = input("
Please give me a first name: ")
        if first == "q":
            break
        last = input("Please give me a last name: ")
        if last == "q":
            break

        formatted_name = get_formatted_name(first, last)
        print("
Neatly formatted name: " + formatted_name + ".")

当然你也可以将if语句下面的代码多带带放在一个文件中,并在该文件开头带入get_formatted_name()函数。

if __name__ == "__main__"的补充:

在Python中,模块就是对象,所有模块都有一个内置属性__name__,当该模块被导入时,该模块的__name__属性会被置为模块名,当直接运行该模块,或者说直接运行该文件时,该属性就会使用默认值"__main__",可以用一句经典的话总结这个用法:

Make a script both importable and executable.

if语句下面的代码相当于对上面的函数的测试,不过这样的测试每次都需要我们自己输入数据,并自己根据结果判断代码是否工作正常,如果代码稍微多一点,稍微复杂一点,这样的测试方法将会很繁琐,所以,我们使用unittest模块了测试代码。

# 代码test_name_function.py:
import unittest
from name_function import get_formatted_name

class NamesTestCase(unittest.TestCase):
    """测试name_function.py"""

    def test_first_last_name(self):
        """能够正确地处理像Janis Joplin这样的名字吗?"""
        formatted_name = get_formatted_name("janis", "joplin")
        self.assertEqual(formatted_name, "Janis Joplin")

unittest.main()

# 结果:
.     # 这里有个实心句点
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

这里先明确两个概念:

单元测试:用于核实函数在某个方面没有问题

测试用例:一组单元测试,这些单元测试一起核实函数在各种情况下的行为都符合要求。

也就是说,你可以将上述代码中的test_first_last_name看做单元测试,而将NamesTestCase看做测试用例。

一般测试文件多带带放在一个文件夹中,也可以将测试都放在一个文件中。

为函数编写测试用例,可先导入unittest模块和要测试的函数,再创建一个继承unittest.TestCase的类,并编写一系列方法对函数行为的不同方面进行测试。在测试用,我们使用断言self.assertEqual()(并不是只有这一个断言函数)来判断结果与期望是否相同。在测试类中的每一个测试方法都必须以test_开头,否则将不会被认定是一个单元测试。最后我们通过unittest.main()来运行这个文件中的所有测试。当测试通过时,结果中会先输出一个实心句点,输出几个句点表示通过了几个单元测试,然后输出单元测试数目,最后输出OK

2.2 一个不能通过的测试

外国人的名字还有中间名,以上代码并未考虑这个情况。我们通过将上述代码改成含有中间名的版本来演示测试不通过的情况:

# 代码:
def get_formatted_name(first, middle, last):
    """返回一个整洁的完整姓名"""
    full_name = first + " " + middle + " " + last
    return full_name.title()

# 其余代码均不变

# 运行上面测试代码后的结果:
E
======================================================================
ERROR: test_first_last_name (__main__.NamesTestCase)
能够正确地处理像Janis Joplin这样的名字吗?
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_name_function.py", line 10, in test_first_last_name
    formatted_name = get_formatted_name("janis", "joplin")
TypeError: get_formatted_name() missing 1 required positional argument: "last"

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

第一行输出了一个字母Etraceback指出缺少了参数。如果你检查的条件没错,测试通过了意味着函数的行为是对的,而测试未通过意味着你编写的新代码有错。因此,测试未通过时,不是去修改测试代码,而失去修改你编写的代码。

2.3 添加新测试

以下我们将上述的get_formatted_name()函数修改为能自动处理中间名的函数,并在测试文件中添加一个单元测试:

# 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()

# test_name_function.py
import unittest
from chapter11 import get_formatted_name

class NamesTestCase(unittest.TestCase):
    """测试name_function.py"""

    def test_first_last_name(self):
        """能够正确地处理像Janis Joplin这样的名字吗?"""
        formatted_name = get_formatted_name("janis", "joplin")
        self.assertEqual(formatted_name, "Janis Joplin")

    def test_first_last_middle_name(self):
        """能够正确地处理像Wolfgang Amadeus Mozart这样的姓名吗?"""
        formatted_name = get_formatted_name("wolfgang", "mozart", "amadeus")
        self.assertEqual(formatted_name, "Wolfgang Amadeus Mozart")

unittest.main()

# 结果:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
3. 测试类

前面讲的都是对函数的测试,这里我们开始对类的测试。在测试之前,先介绍几种常用的断言方法:

方法 用途
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中

下面创建一个匿名调查类:

# 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):
        """显示收集到的所有答卷"""
        print("Survey results:")
        for response in self.responses:
            print("- " + response)

以下是对该类的测试代码:

# test_survey.py
import unittest
from chapter11 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()

# 结果:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

这里的setUp()方法相当于普通函数的__init__()方法,用于初始化这个测试类,减少重复代码,比如,如果不用setUp()方法,那么question变量在每个测试函数中都要声明一次,十分麻烦低效。你过测试类中包含了setUp()方法,Python将先运行它,再运行各个以test_开头的方法。

至此,Python的基础部分大致结束,后面将是项目部分,以后可能还会对基础部分进行补充。


迎大家关注我的微信公众号"代码港" & 个人网站 www.vpointer.net ~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/41791.html

相关文章

  • Python学习之路10-测试代码

    摘要:也就是说,你可以将上述代码中的看做单元测试,而将看做测试用例。在测试类中的每一个测试方法都必须以开头,否则将不会被认定是一个单元测试。 《Python编程:从入门到实践》笔记。本章主要学习如何使用Python标准库中的unittest模块对代码进行简单的测试。 1. 前言 作为初学者,并非必须为你尝试的所有项目编写测试;但参与工作量较大的项目时,你应对自己编写的函数和类的重要行为进行测...

    Developer 评论0 收藏0
  • Python学习之路4-if语句

    摘要:本章主要讲述条件语句等结构。是一条包罗万象的语句,只要不满足前面的条件,其中的代码就会执行,这可能会引入无效甚至恶意的数据。使用语句处理列表语句常和循环结构配合使用。 《Python编程:从入门到实践》笔记。本章主要讲述条件语句if, if-else, if-elif, if-elif-else等结构。 1. 条件测试 包括了相等,不等,大于,小于,大于等于,小于等于,存在于,与或非等...

    JouyPub 评论0 收藏0
  • Python 进阶之路 (九) 再立Flag, 社区最全的itertools深度解析(上)

    摘要:例如,以下对两个的相应元素求和这个例子很好的解释了如何构建中所谓的迭代器代数的函数的含义。为简单起见,假设输入的长度可被整除。接受两个参数一个可迭代的正整数最终会在中个元素的所有组合的元组上产生一个迭代器。 前言 大家好,今天想和大家分享一下我的itertools学习体验及心得,itertools是一个Python的自带库,内含多种非常实用的方法,我简单学习了一下,发现可以大大提升工作...

    tuantuan 评论0 收藏0
  • 《正规军的Python进阶之路Python技能树测评》

    摘要:每个模块都有对应的分支内容,并且分支内容都分为参考资料练习题交流讨论三个内容,我最喜欢的是练习题,之前都是非正规军的学习,没有系统训练过,现在有技能树测评终于可以把之前散乱的知识点总结在一起了。祝大家都能在技能树测评判断自己在哪个级别的。 通过《Python技能树测评》判断自己在哪个级别: ...

    jayzou 评论0 收藏0
  • Python学习之路9-文件和异常

    摘要:本章主要是学习的文件操作,主要是从文件中读取数据以及将数据存储到文件中,还有错误处理,异常类,模块等。函数返回一个文件对象,用于接收该对象。异常中使用被称为异常的特殊对象来管理程序执行期间发生的错误。 《Python编程:从入门到实践》笔记。本章主要是学习Python的文件操作,主要是从文件中读取数据以及将数据存储到文件中,还有错误处理,异常类,json模块等。 1. 从文件中读数据 ...

    chenatu 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<