使用 Python 进行测试(1)测试基础

原文

总结

我们将从unittest开始,尽管它并不那么好用,但它是Python标准库中的测试工具。
使用unittest编写测试看起来像这样:

import unittest

# 需要测试的代码
def add(a, b):
    return a + b

# The tests
class TestAddFunction(unittest.TestCase):

    def setUp(self):
        ... # This is run before each test

    def tearDown(self):
        ... # This is run after each test

    def test_add_integers(self):
        result = add(1, 2)
        self.assertEqual(result, 3)

    def test_add_floats(self):
        result = add(0.1, 0.2)
        self.assertAlmostEqual(result, 0.3)

    def test_add_mixed_types(self):
        with self.assertRaises(TypeError):
            add(1, "2")


if __name__ == "__main__":
    unittest.main()

当你运行测试时,你会得到一个关于测试是否通过的报告:

python the_tests.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

无聊警告

测试时一个难以教授的主题,因为你需要使用各种工具来减少测试的痛苦,但你不能直接就用工具。如果直接使用工具,你就无法理解为什么要那样编写测试。
为了后面能理解整个测试过程,我们需要先从一些无聊的概念开始。

Unittest

你可能听过单元测试(unit test)这个概念;另一方面,unittest也是Python的标准库,但这个库不仅可以进行单元测试。
为了避免混淆,本文的unittest表示Python的标准库。

我坦白:我不使用unittest 编写测试,我们有更好的测试工具。但它毕竟是Python的标准库,可以作为我们的起点。

你的第一次测试

测试的环境,import会给初学者带来一些麻烦。因此,我们在本例中使用一个目录编写代码:

basic_project
├── the_code_to_test.py
└── the_tests.py

现在我们在the_code_to_test.py编写未来可能价值千金的代码:

def add(a, b):
    return a + b

add as a service

接下来,我们将编写测试,保障我们代码的质量,在the_tests.py中:

import unittest

from the_code_to_test import add

class TestAddFunction(unittest.TestCase):
    def test_add_integers(self):
        result = add(1, 2)
        self.assertEqual(result, 3)


if __name__ == "__main__":
    unittest.main()

现在我们可以运行测试了,在basic_project目录下执行python the_tests.py
运行结果为:

python the_tests.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

发生神魔事了?

我们刚刚写了一个test_add_integers的测试,该测试运行add(1,2)并检查是否为3

使用unittest执行该操作需要几个步骤:

#导入unittest
import unittest

# 导入需要测试的代码
from the_code_to_test import add

#继承TestCase.
#这对unittest很重要
class TestAddFunction(unittest.TestCase):

    #测试方法需要以"test"开头,以便unittest认出
    def test_add_integers(self):

        # 编写断言(assert),即我们期望的事情
        result = add(1, 2)
        self.assertEqual(result, 3)


if __name__ == "__main__":
    unittest.main()

实际上,我们只关心其中的两句:

result = add(1, 2)
self.assertEqual(result, 3)

测试是不是很有意思呢?也许比洗碗更有意思!
测试也是必须的!
考虑我们的add函数中发生了一个错误:

def add(a, b):
    return a - b # woops

现在让我们看看测试报告:

python the_tests.py
F
======================================================================
FAIL: test_add_integers (__main__.TestAddFunction)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "the_tests.py", line 9, in test_add_integers
    self.assertEqual(result, 3)
AssertionError: -1 != 3

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)

测试报告其中一项测试未通过。我们稍后将看到如何解释这些结果。

进行多项测试

您不太可能只有一个测试,因此让我们添加更多测试。
首先,我们将add函数恢复为合理的代码:

def add(a, b):
    return a + b

然后,让我们再添加一个断言来检查负数:

import unittest

from the_code_to_test import add

class TestAddFunction(unittest.TestCase):
    def test_add_integers(self):
        result = add(1, 2)
        self.assertEqual(result, 3)

        result = add(1, -2)
        self.assertEqual(result, -1)


if __name__ == "__main__":
    unittest.main()

我们的测试现在涵盖了更多用例。如果我们运行它,它仍然会报告1个测试。

python the_tests.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK   

一个测试(方法)确实可以包含多个断言,但它在报告中将作为一个整体成功或失败。

现在让我们添加一个新方法来测试添加字符串,因为 + 运算符也适用于字符串:

import unittest

from the_code_to_test import add

class TestAddFunction(unittest.TestCase):

    def test_add_integers(self):
        result = add(1, 2)
        self.assertEqual(result, 3)

        result = add(1, -2)
        self.assertEqual(result, -1)

    def test_add_strings(self):
        result = add("1", "2")
        self.assertEqual(result, "12")



if __name__ == "__main__":
    unittest.main()

运行它,我们可以看到2个测试:

python the_tests.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

让我们通过添加第 3 个测试来使其失败,以便您可以查看报告会发生什么。我们将尝试添加浮点数,这不能安全地与相等进行比较:

import unittest

from the_code_to_test import add


class TestAddFunction(unittest.TestCase):

    def test_add_integers(self):
        result = add(1, 2)
        self.assertEqual(result, 3)

        result = add(1, -2)
        self.assertEqual(result, -1)

    def test_add_strings(self):
        result = add("1", "2")
        self.assertEqual(result, "12")

    def test_add_floats(self):
        result = add(0.1, 0.2)
        self.assertEqual(result, 0.3)

if __name__ == "__main__":
    unittest.main()

现在运行测试可以得到:

python the_tests.py
F..
======================================================================
FAIL: test_add_floats (__main__.TestAddFunction)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "the_tests.py", line 21, in test_add_floats
    self.assertEqual(result, 0.3)
AssertionError: 0.30000000000000004 != 0.3

----------------------------------------------------------------------
Ran 3 tests in 0.000s

FAILED (failures=1)

我们现在检测并执行了 3 个测试。其中一个失败了,所以我们得到的不是顶部的 3 个点,而是“F…”。

但是,这种失败不是由于代码中的错误,而是因为我们错误地编写了测试。这是测试中令人讨厌的事情之一,你有更多的机会犯错误,在你更有经验之前,这将是令人沮丧的。

坚持下去,这是一种“熟能生巧”的情况。您需要有经验才能高效编写代码。并使用 ChatGPT,它很棒。

另外,我不会撒谎,我仍然经常与测试作斗争,只是比以前少得多。

阅读报告

测试失败时,您需要的第一个条件反射是阅读报告,因此让我们解释一下它包含的内容。
我将再添加一个失败的测试:

import unittest

from the_code_to_test import add


class TestAddFunction(unittest.TestCase):

    def test_add_integers(self):
        result = add(1, 2)
        self.assertEqual(result, 3)

        result = add(1, -2)
        self.assertEqual(result, -1)

    def test_add_strings(self):
        result = add("1", "2")
        self.assertEqual(result, "12")

    def test_add_floats(self):
        result = add(0.1, 0.2)
        self.assertEqual(result, 0.3)

    def test_add_mixed_types(self):
        add(1, "2")


if __name__ == "__main__":
    unittest.main()

现在报告将向我们显示 4 个测试,其中 2 个失败:

python the_tests.py
F.E.
======================================================================
ERROR: test_add_mixed_types (__main__.TestAddFunction)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "the_tests.py", line 24, in test_add_mixed_types
    add(1, "2")
  File "the_code_to_test.py", line 2, in add
    return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'

======================================================================
FAIL: test_add_floats (__main__.TestAddFunction)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "the_tests.py", line 21, in test_add_floats
    self.assertEqual(result, 0.3)
AssertionError: 0.30000000000000004 != 0.3

----------------------------------------------------------------------
Ran 4 tests in 0.001s

FAILED (failures=1, errors=1)

让我们给所有这些贴上标签:
test report

红色标记的部分是错误(Error)。该信息出现了3 次。一次在顶部摘要中的字母“E”,一次在运行过程中为每个错误(我们这里只有一个)带有标签“ERROR”,在最终统计信息的末尾有一个errors=1。
错误是由于代码崩溃而未成功的测试。

橙色标记的是失败(failure)。该信息也重复了 3 次。一次在顶部的摘要中,带有字母“F”,一次在运行期间每次出现时带有“FAIL”标签(我们这里只有一个),一次在最终统计信息的末尾。
失败是没有成功的测试,因为返回了一个断言 False ,这意味着你对代码如何工作的期望被证明是错误的。

顶部的绿点是已通过的测试,因此报告中不再提及它们。

在标签“ERROR”和“FAIL”旁边,您可以看到未成功的测试名称(紫色)。这样,您就可以找到问题发生的位置。

最后,对于每个问题,您都有一个常规的 Python 堆栈跟踪,即以“traceback”开头的蓝色块。在 ERROR 中,堆栈跟踪将显示代码的哪一部分爆炸了。在 FAIL 块中,堆栈跟踪将显示返回 False 的断言。

各种断言

assertEqual 不是我们唯一可以做出的断言,还有很多: assertIs , assertIn , assertWarns …

我们将利用它来修复我们的测试。

我们在test_add_floats的“FAIL” 是因为我们做了一个 0.1 + 0.2 == 0.3 会返回 True 的假设。这与其说是我们代码中的错误,不如说是我们的测试不正确。
由于编程语言精度限制,0.1+0.2 的结果会离0.3有些偏差:

>>> 0.1 + 0.2
0.30000000000000004

我们不能在这个测试中使用相等,但幸运的是,unittest 模块附带 assertAlmostEqual 了它,可以让你检查这两个数字是否非常接近:

def test_add_floats(self):
    result = add(0.1, 0.2)
    self.assertAlmostEqual(result, 0.3)

对于 test_add_mixed_types ,错误是有确定的。我们希望代码在用户传递混合类型时抛出 TypeError。因此,让我们重写测试,以考虑此处的异常实际上是成功的:

def test_add_mixed_types(self):
    # The test now passes if the function raises a TypeError
    with self.assertRaises(TypeError):
        add(1, "2")

我们的测试套件越来越快乐:

python the_tests.py
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

Setup and tear down 初始化和清理

有些测试组需要您在每次测试之前准备一些东西,例如数据库、文件或只是预先计算的东西。以及其他需要在每次测试后删除、清理或关闭某些内容的内容。

这可以通过在测试类上声明 setUptearDown 方法来完成:

import unittest

from the_code_to_test import add


class TestAddFunction(unittest.TestCase):

    def setUp(self):
        # Anything you attach to self here is available
        # in other tests
        print('This is run before each test')

    def tearDown(self):
        print('This is run after each test')

    def test_add_integers(self):
        result = add(1, 2)
        self.assertEqual(result, 3)

        result = add(1, -2)
        self.assertEqual(result, -1)

    def test_add_strings(self):
        result = add("1", "2")
        self.assertEqual(result, "12")

    def test_add_floats(self):
        result = add(0.1, 0.2)
        self.assertAlmostEqual(result, 0.3)

    def test_add_mixed_types(self):
        with self.assertRaises(TypeError):
            add(1, "2")


if __name__ == "__main__":
    unittest.main()

您可以看到这些方法被自动调用:

python the_tests.py
This is run before each test
This is run after each test
.This is run before each test
This is run after each test
.This is run before each test
This is run after each test
.This is run before each test
This is run after each test
.
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

使用test runner

我们有:

if __name__ == "__main__":
    unittest.main()

事实上,这不是强制性的。我们可以删除它,并改用测试运行程序(test runner )。
测试运行程序是根据某些规则(例如文件名、它们在目录树中的位置等)检测、收集和运行所有测试的程序。

Unittest 现在自带测试运行程序,因此我们也可以运行所有以这种方式调用它的测试:

python -m unittest the_tests.py

我们也会得到测试报告。

测试运行程序在目录上工作,而不仅仅是一个文件,因此一旦项目增长,您可能更喜欢使用一个目录。Python 中还有其他测试运行程序,例如 nose、pytest、tox、nox 等。它们甚至可以附带很多工具,正如我们将在 pytest 中看到的那样。

事实上,编写测试是一件苦差事,因此我们应该尽可能轻松地编写它们,否则这样做的动力会迅速下降。

Pytest 使编写测试变得不那么痛苦,并且具有许多功能,一旦您认真对待测试,您很快就会学会欣赏这些功能。

由于所有这些原因,我们不会在 unittest 上花费更多时间,在本系列的下一部分中,我们将继续使用 pytest。

此外,测试是生成式 AI 大放异彩的领域之一,不要犹豫,使用你最喜欢的LLM,无论是copilot、chatgpt、claude 还是其他什么,创建测试。
给他们测试的函数,并告诉他们为该函数编写一个测试。你经常需要修复一些东西,但这比手写要快得多,至少对于简单的问题来说是这样。大多数测试并没有那么复杂。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/712368.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网络安全攻防基础入门笔记--操作系统名词解释文件下载反弹shell防火墙绕过

渗透测试常用专业术语 POC,EXP,Payload,Shellcode POC 全程Proof of Concept,中文"概念验证",常指一段漏洞证明的代码 EXP 全程Exploit ,中文"利用",指利用系统漏洞进行攻击的动作 Payload 中文"有效载荷",指成功Exploit之后,真正在目标系…

车道偏离预警系统技术规范(简化版)

车道偏离预警系统技术规范(简化版) 1 系统概述2 预警区域3 功能条件4 显示需求5 指标需求 1 系统概述 车道偏离预警系统工作在中高速驾驶的情况下,当驾驶员因注意力不集中导致车辆偏离本车道时,系统通过光学和声学信号对驾驶员进行…

MySQL 使用 MyFlash 快速恢复误删除、误修改数据

一、MyFlash MyFlash 是由美团点评公司技术工程部开发并维护的一个开源工具,主要用于MySQL数据库的DML操作的回滚。这个工具通过解析binlog日志,帮助用户高效、方便地进行数据恢复。MyFlash的优势在于它提供了更多的过滤选项,使得回滚操作变…

UC Berkeley简介以及和Stanford的区别与联系

UC Berkeley Source: Google Map 中文版 UC Berkeley,全称University of California, Berkeley,是一所位于美国加利福尼亚州伯克利市的世界知名公立研究型大学。以下是关于UC Berkeley的详细介绍: 学术声誉和排名 学术声誉: U…

欧洲杯德语词汇与表达,柯桥零基础德语培训

欧洲杯 - die Europameisterschaft 足球 - der Fuball 比赛 - das Spiel / die Partie 球员 - der Spieler 教练 - der Trainer 裁判 - der Schiedsrichter 球迷 - die Fans 进球 - das Tor 守门员 - der Torwart / der Torhter 前锋 - der Strmer 中场 - der Mittelf…

2024最值得入手骨传导耳机指南,精选五款分享!

作为前几年在蓝牙耳机市场杀出的一匹黑马,黑科技加持的骨传导耳机受到广大运动爱好者的喜爱。利用骨传导技术,通过头骨、颌骨把声音传到听觉神经引起听觉,同时又不阻碍外接声音的通过,保证了佩戴的舒适性也带来安全使用的最佳体验…

判断两张图片是否相似

判断两张图片是否相似 要判断两张图片是否相似,你可以使用多种方法,其中包括结构相似性指数(SSIM)和 perception hash 等。以下是使用 SSIM 和 perception hash 进行判断的示例代码。 安装必要的包 确保你已经安装了 scikit-im…

帕金森病的食疗建议

帕金森病(PD)是一种慢性、进展性的神经退行性疾病,主要影响中老年人。虽然目前尚无法根治,但及早规范治疗可显著改善症状,提高患者的生活质量。饮食调理作为帕金森病综合治疗的重要组成部分,对于维持患者较…

unity数独游戏

using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI;public class MainMenuPanel : MonoBehaviour {public Button btnPlay; // 开始按钮public Slider sldDifficulty; // 难度滑动条private void Awake(){/…

RabbitMQ实践——利用一致性Hash交换器做带权重的调度

在《RabbitMQ实践——利用一致性Hash交换器做负载均衡》一文中,我们介绍了如何开启一致性hash交换器,并实现了消息的负载均衡,以达到横向扩展消费者数量的能力。 但是现实中,可能存在这样的场景:一些队列所在的机器配置…

【vue】终端 常用代码 和其他注意

🥑这里目录 一、【安装】1. 搜版本2.卸载3.安装 带版本4. 纯安装(自动最新) 二、【官网】官网源码及用法讲解1.【npm】2.【printjs】打印 一、【安装】 以下全拿 qrcode.vue 举例 1. 搜版本 例子:搜 qrcode.vue的版本代码&…

电子行业实施MES管理系统的时机是什么

随着信息技术的飞速发展,MES生产管理系统逐渐成为电子企业实现自动化生产和信息化管理的必备工具。那么,何时是电子企业实施MES管理系统的最佳时机呢? 1.生产过程中出现了问题,需要优化和改进。 2.企业需要提高产品交付和响应速…

短视频矩阵系统/源码搭建---拆解热门视频功能开发上线

短视频矩阵系统/源码搭建 一、短视频矩阵系统源码开发需要用到以下技术: 1.前端技术:HTML、CSS、JavaScript、Vue.js等前端框架。 2.后端技术:Java、Python、PHP等后端语言及相关框架,如Spring Boot、Django、Laravel等。 3.移…

算法day26

第一题 429. N 叉树的层序遍历 本题的要求我们可以通过队列来辅助完成层序遍历; 如下图的n叉树: 步骤一: 我们定义一个队列,先进行根节点入队列操作; 步骤二: 我们进行当前队列每一个元素的出队列操作&…

(科学:某天是星期几)泽勒一致性是由克里斯汀·泽勒开发的用于计算某天是星期几的算法。

(科学:某天是星期几)泽勒一致性是由克里斯汀泽勒开发的用于计算某天是星期几的算法。这个公式是: 其中: h是一个星期中的某一天(0 为星期六;1 为星期天;2 为星期一;3 为星期二;4 为 星期三;5 为星期四;6为星期五)。 q 是某月的第几天。 m 是月份(3 为三月,4 为四月,…

[数据集][目标检测]减速带检测数据集VOC+YOLO格式5400张1类别

数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):5400 标注数量(xml文件个数):5400 标注数量(txt文件个数):5400 标注…

去掉eslint

1、在vue.config.js文件里加上下面的代码,然后重启就可以了! 2、vue.config.js文件代码: const { defineConfig } require(vue/cli-service) module.exports defineConfig({transpileDependencies: true,lintOnSave: false })

Python基础教程(二十):SMTP发送邮件

💝💝💝首先,欢迎各位来到我的博客,很高兴能够在这里和您见面!希望您在这里不仅可以有所收获,同时也能感受到一份轻松欢乐的氛围,祝你生活愉快! 💝&#x1f49…

MySQL -- 事务

MySQL事务是数据库操作的一个重要概念,事务是指一组操作要么全部完成,要么全部不完成,是数据库的一个逻辑工作单元。事务的主要目的是确保数据库的一致性和可靠性。 事务是一组SQL语句的执行,要么全部成功,要么全部失…

【踩坑】解决运行一段时间GPU计算后忽然变得很慢

转载请注明出处:小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你,欢迎[点赞、收藏、关注]哦~ 目录 发现问题 问题分析 修复思路 思路一 思路二 思路二对应代码 这个问题真的找了我好久,但说起来其实也简单,就是GPU温…