• Python--测试代码


    目录

    1、使用pip安装pytest

    1.1 更新pip

    1.2 安装putest

    2、测试函数

    2.1 单元测试和测试用例

    2.2 可通过的测试

    2.3 运行测试

    2.4 未通过的测试

    2.5 解决测试未通过

    2.6 添加新测试

    3、测试类

    3.1 各种断言

    3.2 一个测试的类

    3.3 测试AnonymousSurvey类

    3.4 使用夹具


    在编写函数或类时,还可为其编写测试。通过测试,可确定代码⾯对 各种输⼊都能够按要求⼯作。

    1、使用pip安装pytest

    第三⽅包(third-party package)指的是独⽴于 Python 核 ⼼的库。

    然⽽,很多包并未被纳⼊标准库,因此得以独⽴于 Python 语⾔本⾝的更新 计划。为很多重要的功能是使⽤第三⽅包实 现的。

    1.1 更新pip

    Python 提供了⼀款名为 pip 的⼯具,可⽤来安装第三⽅包。因为 pip 帮我们

    安装来⾃外部的包,所以更新频繁,以消除潜在的安全问题。有鉴于此,

    我们先来更新 pip。

    打开⼀个终端窗⼝,执⾏如下命令:

    1. $ python -m pip install --upgrade pip
    2. ❶ Requirement already satisfied: pip in /.../python3.11/site-packages
    3. (22.0.4)
    4. --snip--
    5. ❷ Successfully installed pip-22.1.2

    可使⽤下⾯的命令更新系统中安装的任何包:

    $ python -m pip install --upgrade package_name

    1.2 安装putest

    将 pip 升级到最新版本后,就可以安装 pytest 了:

    1. $ python -m pip install --user pytest
    2. Collecting pytest
    3. --snip--
    4. Successfully installed attrs-21.4.0 iniconfig-1.1.1 ...pytest-7.x.x

    这⾥使⽤的核⼼命令也是 pip install,但指定的标志不是 --upgrade,⽽是 --user。这个标志让 Python 只为当前⽤户装指定的 包。

    可使⽤下⾯的命令安装众多的第三⽅包:

    $ python -m pip install --user package_name 

    注意:如果在执⾏这个命令时遇到⿇烦,可尝试在不指定标志 --user 的情况下再次执⾏它。

    2、测试函数

    要学习测试,必须有要测试的代码。下⾯是⼀个简单的函数,它接受名和 姓并返回格式规范的姓名:

    1. def get_formatted_name(first, last):
    2. """⽣成格式规范的姓名"""
    3. full_name = f"{first} {last}"
    4. return full_name.title()

    我们编写⼀个使⽤这个 函数的程序。程序 names.py 让⽤户输⼊名和姓,并显⽰格式规范的姓名:

    1. from name_function import get_formatted_name
    2. print("Enter 'q' at any time to quit.")
    3. while True:
    4. first = input("\nPlease give me a first name: ")
    5. if first == 'q':
    6. break
    7. last = input("Please give me a last name: ")
    8. if last == 'q':
    9. break
    10. formatted_name = get_formatted_name(first, last)
    11. print(f"\tNeatly formatted name: {formatted_name}.")

    这个程序从 name_function.py 中导get_formatted_name()。⽤户可 输⼊⼀系列名和姓,并看到格式规范的姓名:

    1. Enter 'q' at any time to quit.
    2. Please give me a first name: janis
    3. Please give me a last name: joplin
    4. Neatly formatted name: Janis Joplin.Please give me a first name: bob
    5. Please give me a last name: dylan
    6. Neatly formatted name: Bob Dylan.
    7. Please give me a first name: q

    从上述输出可知,合并得到的姓名正确⽆误。

    现在假设要修改 get_formatted_name(),使其还能够处理中间名。为此,可在每 次修get_formatted_name() 后都进⾏测试:运⾏程序 names.py,并 输⼊像 Janis Joplin 这样的姓名。不过这太烦琐了。所幸 pytest 提供了⼀ 种⾃动测试函数输出的⾼效⽅式。

    2.1 单元测试和测试用例

    测试⽤例(test case)是⼀组单元测试, 这些单元测试⼀道核实函数在各种情况下的⾏为都符合要求。良好的测试 ⽤例考虑到了函数可能收到的各种输⼊,包含针对所有这些情况的测试。

    全覆盖(full coverage)测试⽤例包含⼀整套单元测试,涵盖了各种可能的 函数使⽤⽅式。

    2.2 可通过的测试

    使⽤ pytest 进⾏测试,会让单元测试编写起来⾮常简单。我们将编写⼀ 个测试函数,它会调⽤要测试的函数,并做出有关返回值的断⾔。如果断 ⾔正确,表⽰测试通过;如果断⾔不正确,表⽰测试未通过。

    这个针对 get_formatted_name() 函数的测试如下:

    1. from name_function import get_formatted_name
    2. def test_first_last_name():
    3. """能够正确地处理像 Janis Joplin 这样的姓名吗?"""
    4. ❷ formatted_name = get_formatted_name('janis', 'joplin')
    5. assert formatted_name == 'Janis Joplin'

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

    在这个测试⽂件中,⾸先导⼊要测试的get_formatted_name() 函数。 然后,定义⼀个测试函数 test_first_last_name()(⻅❶)。

    这个函 数名⽐以前使⽤的都⻓,原因有⼆。

    第⼀,测试函数必须以 test_ 打头。在测试过程中,pytest 将找出并运⾏所有以 test_ 打头的函数。

    第⼆, 测试函数的名称应该⽐典型的函数名更⻓,更具描述性。你⾃⼰不会调⽤ 测试函数,⽽是由 pytest 替你查找并运⾏它们。因此,测试函数的名称 应⾜够⻓,让你在测试报告中看到它们时,能清楚地知道它们测试的是哪 些⾏为

    接下来,调⽤要测试的函数(⻅❷)。像运⾏ names.py 时⼀样,这⾥在调 ⽤ get_formatted_name() 函数时向它传递了实参 'janis' 和 'joplin'。将这个函数的返回值赋给变量 formatted_name。

    最后,做出⼀个断⾔(⻅❸)。断⾔(assertion)就是声称满⾜特定的条 件:这⾥声称 formatted_name 的值为 'Janis Joplin'。

    2.3 运行测试

    打开⼀个终端窗⼝,并切换到这个测试⽂件所在的⽂件夹。如果你

    使⽤的是 VS Code,可打开测试⽂件所在的⽂件夹,并使⽤该编辑器内嵌

    的终端。在终端窗⼝中执⾏命令 pytest,你将看到如下输出:

    1. $ pytest
    2. ========================= test session starts
    3. =========================
    4. ❶ platform darwin -- Python 3.x.x, pytest-7.x.x, pluggy-1.x.x
    5. ❷ rootdir: /.../python_work/chapter_11
    6. ❸ collected 1 item
    7. test_name_function.py .
    8. [100%]
    9. ========================== 1 passed in 0.00s
    10. ==========================

    ⼀些有关运⾏测试的系统的 信息(⻅❶)。该测试是从哪个⽬录运⾏的(⻅❷)pytest 找到了⼀个测试(⻅❸),并 指出了运⾏的是哪个测试⽂件(⻅❹)

    注意:如果出现⼀条消息,提⽰没有找到命令 pytest,请执⾏命 令 python -m pytest

    2.4 未通过的测试

    修改

    get_formatted_name(),使其能够处理中间名,但同时故意让这个函

    数⽆法正确地处理像 Janis Joplin 这样只有名和姓的姓名。

    下⾯是 get_formatted_name() 函数的新版本,它要求通过⼀个实参指

    定中间名:

    1. def get_formatted_name(first, middle, last):
    2. """⽣成格式规范的姓名"""
    3. full_name = f"{first} {middle} {last}"
    4. return full_name.title()

    对其进⾏测试时,我

    们发现它不再能正确地处理只有名和姓的姓名了。

    这次运⾏ pytest 时,输出如下:

    1. $ pytest
    2. ========================= test session starts
    3. =========================
    4. --snip--
    5. test_name_function.py F
    6. [100%]
    7. ============================== FAILURES
    8. ===============================
    9. ❸ ________________________ test_first_last_name
    10. _________________________
    11. def test_first_last_name():
    12. """能够正确地处理像 Janis Joplin 这样的姓名吗?"""
    13. > formatted_name = get_formatted_name('janis', 'joplin')
    14. ❺ E TypeError: get_formatted_name() missing 1 required positional
    15. argument: 'last'
    16. test_name_function.py:5: TypeError
    17. ======================= short test summary info
    18. =======================
    19. FAILED test_name_function.py::test_first_last_name - TypeError:
    20. get_formatted_name() missing 1 required positional argument:
    21. 'last'
    22. ========================== 1 failed in 0.04s
    23. ==========================

    输出中有⼀个字⺟ F(⻅❶),表明有⼀个测试未通过。然后是 FAILURES 部分(⻅❷),这是关注的焦点,因为在运⾏测试时,通常应 该关注未通过的测试。接下来,指出未通过的测试函数是 test_first_last_name()(⻅❸)。右尖括号(⻅❹)指出了导致测 试未能通过的代码⾏。下⼀⾏中的 E(⻅❺)指出了导致测试未通过的具体 错误:缺少必不可少的位置实参 'last',导致 TypeError。在末尾的简 短⼩结中,再次列出了最重要的信息。

    2.5 解决测试未通过

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

    在这个⽰例中,新增的中间名参数是必不可少 的。就这⾥⽽⾔, 最佳的选择是让中间名变为可选的。

    1. def get_formatted_name(first, last, middle=''):
    2. """⽣成格式规范的姓名"""
    3. if middle:
    4. full_name = f"{first} {middle} {last}"
    5. else:
    6. full_name = f"{first} {last}"
    7. return full_name.title()

    2.6 添加新测试

    确定 get_formatted_name() ⼜能正确地处理简单的姓名后,我们再编 写⼀个测试,⽤于测试包含中间名的姓名。为此,在⽂件 test_name_function.py 中添加⼀个测试函数:

    1. from name_function import get_formatted_name
    2. def test_first_last_name():
    3. --snip--
    4. def test_first_last_middle_name():
    5. """能够正确地处理像 Wolfgang Amadeus Mozart 这样的姓名吗?"""
    6. ❶ formatted_name = get_formatted_name(
    7. 'wolfgang', 'mozart', 'amadeus')
    8. ❷ assert formatted_name == 'Wolfgang Amadeus Mozart'

    为测试 get_formatted_name() 函数,我们先使⽤名、姓和中间名调⽤ 它(⻅❶),再断⾔返回的姓名与预期的姓名(名、中间名和姓)⼀致 (⻅❷)。再次运⾏ pytest,两个测试都通过了:

    1. $ pytest
    2. ========================= test session starts
    3. =========================
    4. --snip--
    5. collected 2 items
    6. test_name_function.py ..
    7. [100%]
    8. ========================== 2 passed in 0.01s
    9. ==========================

    3、测试类

    如果针 对类的测试通过了,你就能确信对类所做的改进没有意外地破坏其原有的 ⾏为。

    3.1 各种断言

    到⽬前为⽌,我们只介绍了⼀种断⾔:声称⼀个字符串变量取预期的值。 在编写测试时,可做出任何可表⽰为条件语句的断⾔。如果该条件确实成 ⽴,你对程序⾏为的假设就得到了确认,可以确信其中没有错误。测试中常⽤的断⾔语句

    断⾔

    ⽤途

    assert a == b

    断⾔两个值相等

    assert a != b

    断⾔两个值不等

    assert a

    断⾔ a 的布尔求值为 True

    assert not a

    断⾔ a 的布尔求值为 False

    assert element in list

    断⾔元素在列表中

    assert element not in list

    断⾔元素不在列表中

    这⾥列出的只是九⽜⼀⽑,测试能包含任意可⽤条件语句表⽰的断⾔。

    3.2 一个测试的类

    类的测试与函数的测试相似,所做的⼤部分⼯作是测试类中⽅法的⾏为。

    这是⼀

    个帮助管理匿名调查的类:

    1. # survey.py
    2. class AnonymousSurvey:
    3. """收集匿名调查问卷的答案"""
    4. def __init__(self, question):
    5. """存储⼀个问题,并为存储答案做准备"""
    6. self.question = question
    7. self.responses = []
    8. def show_question(self):
    9. """显⽰调查问卷"""
    10. print(self.question)
    11. def store_response(self, new_response):
    12. """存储单份调查答卷"""
    13. self.responses.append(new_response)
    14. def show_results(self):
    15. """显⽰收集到的所有答卷"""
    16. print("Survey results:")
    17. for response in self.responses:
    18. print(f"- {response}")

    这个类⾸先存储⼀个调查问题(⻅❶),并创建了⼀个空列表,⽤于存储 答案。这个类包含打印调查问题的⽅法(⻅❷),在答案列表中添加新答 案的⽅法(⻅❸),以及将存储在列表中的答案打印出来的⽅法(⻅❹)。 要创建这个类的实例,只需提供⼀个问题即可。编写⼀个使⽤它的程 序:

    1. # language_survey.py
    2. from survey import AnonymousSurvey
    3. # 定义⼀个问题,并创建⼀个表⽰调查的 AnonymousSurvey 对象
    4. question = "What language did you first learn to speak?"
    5. language_survey = AnonymousSurvey(question)
    6. # 显⽰问题并存储答案
    7. language_survey.show_question()
    8. print("Enter 'q' at any time to quit.\n")
    9. while True:
    10. response = input("Language: ")
    11. if response == 'q':
    12. break
    13. language_survey.store_response(response)
    14. # 显⽰调查结果
    15. print("\nThank you to everyone who participated in the survey!")
    16. language_survey.show_results()

    3.3 测试AnonymousSurvey类

    下⾯来编写⼀个测试,对 AnonymousSurvey 类的⾏为的⼀个⽅⾯进⾏验 证。

    1. # test_survey.py
    2. from survey import AnonymousSurvey
    3. def test_store_single_response():
    4. """测试单个答案会被妥善地存储"""
    5. question = "What language did you first learn to speak?"
    6. ❷ language_survey = AnonymousSurvey(question)
    7. language_survey.store_response('English')
    8. assert 'English' in language_survey.responses

    ⾸先,导⼊要测试的 AnonymousSurvey 类。第⼀个测试函数验证:调查

    问题的单个答案被存储后,它会包含在调查结果列表中。对于这个测试函

    数,⼀个不错的描述性名称是 test_store_single_response()(⻅

    ❶)。如果这个测试未通过,我们就能通过测试⼩结中的函数名得知,在 存储单个调查答案⽅⾯存在问题。 要测试类的⾏为,需要创建其实例。在❷处,使⽤问题"What language did you first learn to speak?" 创建⼀个名language_survey 的实例,然后使⽤ store_response() ⽅法存储单个答案 English。接下来,通过断⾔ English 在列表

    language_survey.responses 中,核实这个答案被妥善地存储了(⻅ ❸)。

    1. $ pytest test_survey.py
    2. ========================= test session starts =========================
    3. --snip--
    4. test_survey.py . [100%]
    5. ========================== 1 passed in 0.01s ==========================

    。下⾯来核实,当 ⽤户提供三个答案时,它们都将被妥善地存储。为此,再添加⼀个测试函 数:

    1. from survey import AnonymousSurvey
    2. def test_store_single_response():
    3. --snip--
    4. def test_store_three_responses():
    5. """测试三个答案会被妥善地存储"""
    6. question = "What language did you first learn to speak?"
    7. language_survey = AnonymousSurvey(question)
    8. ❶responses = ['English', 'Spanish', 'Mandarin']
    9. for response in responses:
    10. language_survey.store_response(response)
    11. for response in responses:
    12. assert response in language_survey.responses

    我们将这个新函数命名为 test_store_three_responses(),并像 test_store_single_response() ⼀样,在其中创建⼀个调查对象。 先定义⼀个包含三个不同答案的列表(⻅❶),再对其中的每个答案都调⽤ store_response()。存储这些答案后,使⽤⼀个循环来断⾔每个答案 都包含在 language_survey.responses 中(⻅❷)。 再次运⾏这个测试⽂件,两个测试(针对单个答案的测试和针对三个答案 的测试)都通过了:

    1. $ pytest test_survey.py
    2. ========================= test session starts =========================
    3. --snip--
    4. test_survey.py .. [100%]
    5. ========================== 2 passed in 0.01s ==========================

    前述做法的效果很好,但这些测试有重复的地⽅。下⾯使⽤ pytest 的另 ⼀项功能来提⾼效率。

    3.4 使用夹具

    在测试中,夹具(fixture)可帮助我们搭建测试环境。这通常意味着创建供 多个测试使⽤的资源。在 pytest 中,要创建夹具,可编写⼀个使⽤装饰 器 @pytest.fixture 装饰的函数。装饰器(decorator)是放在函数定义 前⾯的指令。在运⾏函数前,Python 将该指令应⽤于函数,以修改函数代 码的⾏为。下⾯使⽤夹具创建⼀个 AnonymousSurvey 实例,让 test_survey.py 中的两 个测试函数都可使⽤它:

    1. import pytest
    2. from survey import AnonymousSurvey
    3. ❶ @pytest.fixture
    4. def language_survey():
    5. """⼀个可供所有测试函数使⽤的 AnonymousSurvey 实例"""question = "What language did you first learn to speak?"
    6. language_survey = AnonymousSurvey(question)
    7. return language_survey
    8. def test_store_single_response(language_survey):
    9. """测试单个答案会被妥善地存储"""
    10. ❹ language_survey.store_response('English')
    11. assert 'English' in language_survey.responses
    12. def test_store_three_responses(language_survey):
    13. """测试三个答案会被妥善地存储"""
    14. responses = ['English', 'Spanish', 'Mandarin']
    15. for response in responses:
    16. ❻ language_survey.store_response(response)
    17. for response in responses:
    18. assert response in language_survey.responses

    现在需要导⼊ pytest,因为我们使⽤了其中定义的⼀个装饰器。我们将装 饰器@pytest.fixture(⻅❶)应⽤于新函数language_survey() (⻅❷)。这个函数创建并返回⼀个AnonymousSurvey 对象。 请注意,两个测试函数的定义都变了(⻅❸和❺):都有⼀个名为 anguage_survey 的形参。当测试函数的⼀个形参与应⽤了装饰器

    @pytest.fixture 的函数(夹具)同名时,将⾃动运⾏夹具,并将夹具 返回的值传递给测试函数。在这个⽰例中,language_survey() 函数向 test_store_single_response() 和

    test_store_three_responses() 提供了⼀个 language_survey 实 例。 两个测试函数都没有新增代码,⽽且都删除了两⾏代码(⻅❹和❻):定义 问题的代码⾏,以及创建 AnonymousSurvey 对象的代码⾏。

  • 相关阅读:
    服务链路追踪 —— SpringCloud Sleuth
    s19.基于 Kubernetes v1.25.0(kubeadm) 和 Docker 部署高可用集群(一)
    LeetCode刷题系列 -- 25. K 个一组翻转链表
    VS2022 安装.NET 3.5/.NET 4/.NET 4.5/.NET 4.5.1目标包的方法
    【数据结构与算法】单链表的增删查改(代码+图解)
    解放生产力orm并发更新下应该这么处理求求你别再用UpdateById了
    NIO的浅了解
    Java语言基础第二天
    docker上安装的Jenkins但没有vi
    计算机网络必考题
  • 原文地址:https://blog.csdn.net/qq_62254095/article/details/132927898