使用 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 testdef tearDown(self):... # This is run after each testdef 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.000sOK

无聊警告

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

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 unittestfrom the_code_to_test import addclass 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.000sOK

发生神魔事了?

我们刚刚写了一个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_integersself.assertEqual(result, 3)
AssertionError: -1 != 3----------------------------------------------------------------------
Ran 1 test in 0.000sFAILED (failures=1)

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

进行多项测试

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

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

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

import unittestfrom the_code_to_test import addclass 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.000sOK   

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

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

import unittestfrom the_code_to_test import addclass 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.000sOK

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

import unittestfrom the_code_to_test import addclass 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_floatsself.assertEqual(result, 0.3)
AssertionError: 0.30000000000000004 != 0.3----------------------------------------------------------------------
Ran 3 tests in 0.000sFAILED (failures=1)

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

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

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

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

阅读报告

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

import unittestfrom the_code_to_test import addclass 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_typesadd(1, "2")File "the_code_to_test.py", line 2, in addreturn 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_floatsself.assertEqual(result, 0.3)
AssertionError: 0.30000000000000004 != 0.3----------------------------------------------------------------------
Ran 4 tests in 0.001sFAILED (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 TypeErrorwith self.assertRaises(TypeError):add(1, "2")

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

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

Setup and tear down 初始化和清理

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

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

import unittestfrom the_code_to_test import addclass TestAddFunction(unittest.TestCase):def setUp(self):# Anything you attach to self here is available# in other testsprint('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.000sOK

使用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 还是其他什么,创建测试。
给他们测试的函数,并告诉他们为该函数编写一个测试。你经常需要修复一些东西,但这比手写要快得多,至少对于简单的问题来说是这样。大多数测试并没有那么复杂。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

拦截器 之 用户登录判断

spring boot 拦截器的实现需要有两步: 自定义一个拦截器 package com.example.demo.common;import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import org.springfra…

Golang——gRPC gateway网关

前言 etcd3 API全面升级为gRPC后,同时要提供REST API服务,维护两个版本的服务显然不大合理,所以gRPC-gateway诞生了。通过protobuf的自定义option实现了一个网关。服务端同时开启gRPC和HTTP服务,HTTP服务接收客户端请求后转换为gr…

SAP HANA1709~2023版本Fiori激活简介

SAP Fiori 是一个设计系统,使您能够创建具有消费者级别用户体验的业务应用,通过在任何设备上运行,可以在Ipad或者是手机端都可以随时随地的使用SAP,现在越来越多的公司都在使用Fiori系统,公司高层可以更直观的在移动端设备中查看各种数据。 本文主要说明HANA版本怎么激活F…

直流放大器

一,概念及存在问题 集成电路主要由半导体材料构成,其内部适合用二极管,三极管等类型的元器件制作,而不适用电容,电感和变压器,因此集成放大电路内部多个放大电路之间通常采用直接耦合。直接耦合电路除了可…

牛客热题:兑换零钱(一)

📟作者主页:慢热的陕西人 🌴专栏链接:力扣刷题日记 📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言 文章目录 牛客热题:兑换零钱(一)题目链接方法一&am…

基于WPF技术的换热站智能监控系统03--实现左侧加载动画

1、左侧布局规划 左侧分5行,每行的高度通过height属性来指定,1.2*表示占1.2倍的宽度 2、创建用户控件 在WPF中想要进行个性化处理,主要可以通过三个方面来实现:控件模板(控件模板、数据模板、数据容器模板&#xff09…

【Webpack】使用 Webpack 构建 Vue3+TS 项目

构建项目目录 tsc --init npm init -yshim.d.ts 文件是一个类型声明文件,用于告诉 TypeScript 编译器如何处理 Vue 的单文件组件(SFC)和其他自定义模块。为 Vue 的单文件组件和其他非 TypeScript 模块提供类型信息,以便在 TypeScr…

Web期末复习指南(2w字总结)

前言:本文旨在梳理Web技术常包含的内容,阅读完整篇文章后会对整体有个系统的认知,从前端的HTML到后端的MySql,对于大概试题中可能会涉及的地方都有所分析,通篇提供了许多代码案例,供读者更好的理解。对于一…

FPGA - 全局时钟资源

全局时钟资源是指FPGA内部为实现系统时钟到达FPGA内部各 CLB、IOB,以及BSRAM(Block Select RAM,选择性BRAM)等基本逻辑单元的延时和抖动最小化,采用全铜层工艺设计和实现的专用缓冲与驱动结构。 由于全局时钟资源的布线…

OpenGL3.3_C++_Windows(5)

变换 && 3D空间的2D图形 /\/\/\/\/\//\/\/\/\/\/\/\/\//\/\///\/\/\/\//\/\/\/\//\//\/\/\/\/\\/GLM库从0.9.9版本起,默认会将矩阵类型初始化为一个零矩阵(所有元素均为0),而不是单位矩阵(对角元素为1&#…

从踢足球到数字孪生

前言 贵州“村超”的火热现象是一个多方面因素共同作用的结果,它不仅是一场体育赛事,更是一个文化现象,反映了时代的精神和人民的情感诉求,同时也推动了乡村振兴和地区发展。足球的魅力是多方面的,它不仅仅是一项运动,更是一种全球性的文化现象。 简单规则下的无限变化:…

Java注解Annotation机制说明和基础使用(为什么Annotation直接促进了框架的繁荣发展?)

一、注解解决的问题【可忽略】 软件开发过程中,如何配置一直是一个重要的问题,对于一个框架,如果你不为它提供初始结构,它就无法理解你要做什么,自然无法工作。 1.问题:紧密贴合的代码和配置 在很久之前…

分布式微服务: springboot底层机制实现

springboot底层机制实现 搭建SpringBoot底层机制开发环境ConfigurationBean会发生什么,并分析机制提出问题: SpringBoot 是怎么启动Tomcat, 并可以支持访问Controller源码分析: SpringApplication.run()SpringBoot的debug流程 实现SpringBoot底层机制[Tomcat启动分析 Spring容…

【数据分析】推断统计学及Python实现

各位大佬好 ,这里是阿川的博客,祝您变得更强 个人主页:在线OJ的阿川 大佬的支持和鼓励,将是我成长路上最大的动力 阿川水平有限,如有错误,欢迎大佬指正 Python 初阶 Python–语言基础与由来介绍 Python–…

【SpringBoot】SpringBoot:实现文件上传和下载功能

文章目录 引言项目初始化添加依赖 配置文件存储位置实现文件上传功能创建文件上传控制器创建上传页面 实现文件下载功能创建文件下载控制器 安全性和最佳实践文件大小限制文件类型验证文件名和路径验证文件下载时的安全性 测试与部署示例:编写单元测试 部署结论 引言…

墨香戏韵,重塑经典

创意名称 墨香戏韵,重塑经典|基于AIGC对戏剧创新 创意概述 京剧作为中国传统戏曲之一,源远流长,承载了丰富的文化内涵和艺术特色。水墨画则是中国传统绘画的瑰宝,以其独特的墨色表达和极简的形式赢得了广泛的赞誉。我们的项目将…

docker-compose部署FastDFS分布式文件系统

文章目录 一、技术选型二、fastDFS组成部分三、docker-compose文件四、客户端nginx配置五、存储器spring Boot集成参考文献 一、技术选型 还有一个更好的google FS(但是他不开源,我也没找到社区版一类的可以不要钱使用的)。 最后考虑到我们存…

【字符串函数2】

5. strncpy 函数的使用和模拟实现 选择性拷贝 char * strncpy ( char * destination, const char * source, size_t num ); 1.拷贝num个字符从源字符串到目标空间。 2.如果源字符串的⻓度⼩于num,则拷⻉完源字符串之后,在⽬标的后边 追加0 &#…

语法04 C++ 标准输入语句

标准输入 使用格式:cin >> 输入的意思就是把一个值放到变量里面去,也就是变量的赋值,这个值是由我们自己输入的。 (注意:输入变量前要先定义,输入完之后要按Enter键。) 输入多个变量,与输出类似,…

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(){/…