《Python从入门到实践》第十一章 测试代码

使用pip安装pytest

第三方包指的是独立于Python核心的库

更新pip

python -m pip install --upgrade pip

安装pytest

python -m pip install --user pytest

如果在执行这个命令时遇到麻烦, 可以尝试:

python -m pip install pytest

以上安装及更新均在cmd中输入完成

单元测试和测试用例

一种最简单的测试是单元测试,用于合适函数的某个方面没有问题。

测试用例是一组单元测试,这些单元测试一道合适函数在各种情况下的行为都符合要求。良好的测试用例考虑到了函数可能收到的各种输入,包含针对所有这些情况的测试。

全覆盖测试用例包含一整套单元测试,涵盖了各种可能的函数使用方式。对于大型项目,要进行全覆盖测试可能很难。

可通过的测试

使用pytest进行测试,会让单元测试编写起来非常简单。我们将编写一个测试函数,它会调用要测试的函数,并做出有关返回值的断言。如果断言正确,表示测试通过;如果断言不正确,表示测试未通过。

name_function.py

def get_formatted_name(first, last):
    """生成规范的名字"""
    full_name = f"{first} {last}"
    return full_name.title()

test_name_function.py

from name_function import get_formatted_name


def test_first_last_name():
    """能够正确处理像Janis Joplin 这样的名字吗"""
    formatted_name = get_formatted_name('janis', 'joplin')
    assert formatted_name == 'Janis Joplin'

测试文件的名称很重要,必须以test_打头。当你让pytest运行测试时,它将查找以test_打头的文件,并运行其中的所有测试。

测试函数必须以test_打头,在测试过程中,pytest将找出并运行所有以test_打头的函数,此外,测试函数的名称应该比典型的函数名更长,更具描述性。

assert表示断言,断言就是声称满足特定的条件

运行测试

如果直接运行文件test_name_function.py,将不会有任何输出,因为我们没有调用这个测试函数。相反,应该让pytest替我们运行这个测试文件。

为此,打开一个终端窗口,pycharm为Alt+F12,在终端窗口执行命令pytest

tip:如果不知道如何在终端窗口中切换到正确的文件夹,请参阅1.5节。另外,如果出现一条消息,提示没有找到命令pytest,请执行命令python -m pytest

未通过的测试

测试未通过是什么样子呢,我们对get_formatted_name()进行修改

def get_formatted_name(first, middle, last):
    """生成规范的名字"""
    full_name = f"{first} {middle} {last}"
    return full_name.title()

运行原有的测试文件

from name_function import get_formatted_name


def test_first_last_name():
    """能够正确处理像Janis Joplin 这样的名字吗"""
    formatted_name = get_formatted_name('janis', 'joplin')
    assert formatted_name == 'Janis Joplin'

发现测试未通过,可以从报错信息中看出程序错误的地方。

在测试未通过时怎么办

在测试未通过时,如果检查的条件没错,那么测试通过意味着函数的行为是对的,而测试未通过意味着你编写的新代码有错。因此,在测试未通过时,不要修改测试。应为如果你这样做,即使能让测试通过,像测试那样调用函数的代码也将突然崩溃,相反,应该修复导致测试不能通过的代码:检查刚刚对函数所做的修改,找出这些修改是如何导致函数行为不符合预期的。

上面的程序我们就可以对其进行修改:

def get_formatted_name(first, last, middle=''):
    """生成规范的名字"""
    if middle:
        full_name = f"{first} {middle} {last}"
    else:
        full_name = f"{first} {last}"
    return full_name.title()

添加新测试

def test_first_middle_last_name():
    """能够正确处理像Wolfgang Amadeus Mozart"""
    formatted_name = get_formatted_name('wolfgang', 'mozart', 'amadeus')
    assert  formatted_name == 'Wolfgang Amadeus Mozart'

测试结果显示两个测试都通过了

练习:

练习11.1

city_functions

def get_city_country(city, country):
    city_country = f"{city} {country}"
    return city_country.title()

test_cities

from city_functions import get_city_country


def test_city_country():
    """测试函数能都得到Santiago Chile的结果"""
    city_country = get_city_country('santiago', 'chile')
    assert city_country == 'Santiago Chile'

练习11.2

def get_city_country(city, country, population=None):
    if population:
        city_country = f"{city} {country} population:{population}"
    else:
        city_country = f"{city} {country}"
    return city_country.title()
from city_functions import get_city_country


def test_city_country():
    """测试函数能否得到Santiago Chile的结果"""
    city_country = get_city_country('santiago', 'chile')
    assert city_country == 'Santiago Chile'


def test_city_country_population():
    """测试函数能否得到Santiago Chile Population:3000的值"""
    city_country_population = get_city_country('santiago', 'chile', 3000)

测试类

很多程序会用到类,因此证明类能够正确地工作十分必要

各种断言

上面我们只用了一种断言,声称一个字符串变量取预期的值,在编写测试时我们科做出任何可表示为条件语句的断言。

assert a ==b                                                断言两个值相等

assert element (not) in list                        断言元素在或不在列表中

assert not a                                                断言a的布尔求值为True

一个要测试的类

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)

为了证明AnonymousSurvey类能够正常工作,编写一个使用它的程序

from survey import AnonymousSurvey


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

# 显示问题并存储答案
language_survey.show_question()
print("Enter 'q' at any time to quit.\n")
while True:
    response = input("Language:")
    if response == 'q':
        break
    language_survey.store_response(response)

# 显示调查结果
language_survey.show_results()

测试AnonymousSurvey类

from survey import  AnonymousSurvey


def test_store_single_response():
    """测试单个答案会被妥善地存储"""
    question = "What language did you first learn to speak?"
    language_survey = AnonymousSurvey(question)
    language_survey.store_response('Chinese')
    assert 'Chinese' in language_survey.responses

下面来核实,当用户提供三个答案时,它们都将被妥善存储

def test_store_three_response():
    """测试三个答案会被妥善存储"""
    question = "What language did you first learn to speak?"
    language_survey = AnonymousSurvey(question)
    responses = ['English', 'Chinese', 'japanse']
    for response in responses:
        language_survey.store_response(response)

    for response in responses:
        assert response in language_survey.responses

使用夹具

在前面的test_survey.py中,我们每个 测试函数都创建了一个AnonymousSurvey实例。虽然这对于简单的示例来说不是问题,但是在包含数十乃至数百个测试中是个大问题。

在测试中,夹具可帮助我们搭建测试环境,这通常意味着创建供多个测试使用的资源,在pytest中,要创建夹具,可编写一个使用装饰器@pytest.fixture装饰的函数。装饰器是放在函数定义前面的指令。在运行函数前,Python将该指令应用于函数,以修改函数代码的行为。

import pytest
from survey import AnonymousSurvey


@pytest.fixture
def language_survey():
    """一个可供给所有测试函数使用的AnonymousSurvey实例"""
    question = "What language did you first learn to speak?"
    language_survey = AnonymousSurvey(question)
    return language_survey


def test_store_single_response(language_survey):
    """测试单个答案会被妥善地存储"""
    language_survey.store_response('Chinese')
    assert 'Chinese' in language_survey.responses


def test_store_three_response(language_survey):
    """测试三个答案会被妥善存储"""
    responses = ['English', 'Chinese', 'japanse']
    for response in responses:
        language_survey.store_response(response)

    for response in responses:
        assert response in language_survey.responses

当测试函数的一个形参与参与了装饰器@pytest.fixture的函数(夹具)同名时,将自动运行夹具,并将夹具返回的值传递给测试函数。

在想要使用夹具时,可编写一个函数来生成供多个测试函数使用的资源,再对这个函数应用装饰器@pytest.fixture,并让使用该资源的每个测试函数都接受一个与函数同门的形参。这样,测试将更简洁,编写和维护起来也将更加容易。

练习

练习11.3

import pytest
from employee import Employee


@pytest.fixture
def employee():
    employee = Employee('xiong', 'jiajin', 300000)
    return employee


def test_give_default_raise(employee):
    """测试能否正常增加默认5000的年薪"""
    employee.give_raise()
    assert employee.salary == 305000


def test_give_custom_raise(employee):
    employee.give_raise(20000)
    assert employee.salary == 320000

  • 19
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值