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

news2024/11/30 0:27:00

原文

总结

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

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

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

相关文章

MySQL之优化服务器设置(三)

优化服务器设置 InnoDB表空间 InnoDB把数据保存在表空间内,本质上是一个由一个或多个磁盘文件组成的虚拟文件系统。InnoDB用表空间实现很多功能,并不只是存储表和索引。它还保存了回滚日志(旧版本行)、插入缓冲(Insert Buffer)、双写缓冲(Doublewrite …

大模型企业落地:制造业可以选择的应用场景

前言 在当今制造业快速发展的背景下,设备稳定运行对于企业的发展至关重要。然而,传统的设备维修模式已无法满足现代企业的需求。为此,引入智能化、数字化的设备维修解决方案成为必然趋势。本文将探讨如何利用大模型技术,构建企业…

《2023-2024中国数据资产发展研究报告》

中国电子信息产业发展研究院发布《2023-2024中国数据资产发展研究报告》(下称《报告》),紧跟国家战略部署,调研国内数据资产发展现状,掌握数据价值实现路径,助力释放数字经济新动能。 《报告》从数据资产相…

北京活动会议通常会邀约哪些媒体参会报道?

传媒如春雨,润物细无声,大家好,我是51媒体网胡老师。 北京作为我国的首都和文化中心,各类活动会议资源丰富,吸引了众多媒体的关注。以下是一些通常会被邀约参会报道的重要媒体类型: 国家级新闻机构&#x…

探索AI的魔力:这5款AI应用让你全面感受人工智能的魅力

“ AI 的出现就像燃气机和蒸汽机一样,极大地提高了生产力。” 01、—chatGPT ChatGPT 是由 OpenAI 公司开发的一种大型语言模型。 OpenAI 公司成立于 2015年,早期马斯克是该公司的创始人之一。 自 2018 年推出 GTP-1 以来,OpenAI 已经推出…

关于Linux ping 不通外网

网关为第三段为137那么子网ip第三段必须为137且IPaddr必须为137 将主机虚拟适配器连接到此网络必须勾上,不然vmnet适配器在windows将找不到 ping www.baidu.com不行的话试着勾上桥接模式应该是不行在勾上取消勾上桥接模式最后勾上nat模式

C语言小例程20/100

题目&#xff1a;一个数如果恰好等于它的因子之和&#xff0c;这个数就称为"完数"。例如61&#xff0b;2&#xff0b;3.编程找出1000以内的所有完数。 #include<stdio.h> #define N 1000 int main() {int i,j,k,n,sum;int a[256];for(i2;i<N;i){suma[0]1;k…

Transformer系列:图文详解Decoder解码器原理

从本节开始本系列将对Transformer的Decoder解码器进行深入分析。 内容摘要 Encoder-Decoder框架简介shifted right移位训练解码器的并行训练和串行预测解码器自注意力层和掩码解码器交互注意力层和掩码解码器输出和损失函数 Encoder-Decoder框架简介 在原论文中Transformer用…

视频转换器在线哪个好?让视频播放不受格式限制

在日常的视频观看中&#xff0c;我们可能会遇到视频格式与设备不兼容的问题&#xff0c;导致无法顺畅播放。这就像是缺少了播放的钥匙&#xff0c;让人无法享受视频内容。 面对视频格式不兼容的挑战&#xff0c;选择合适的转换工具至关重要。但不用担心&#xff0c;本文将分享…

框架学习之spring学习笔记(一)

一、框架前言 1-什么是spring框架&#xff0c;有哪些主要模块&#xff1f; Spring 框架是一个专门针对于 Java 应用程序开发&#xff0c;并提供了综合、广泛的基础性支持的轻量级框架。Spring框架使用目的是为了提高开发人员的开发效率以及系统的可维护性。 Spring 是以IoC和A…

html圆盘钟表纯js有解释【搬代码】

结果如图所示&#xff1a; 使用的idear中的html编写 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport…

SpringBoot+Vue实现Excel文档导入和导出

1.准备工作 1.1.前端程序 在前端首先加上批量导出的按钮&#xff0c;如下 <el-button size"small" type"warning" plain click"exportData"> 批量导出 </el-button> 在添加了点击事件之后&#xff0c;在methods中要与之对应的添加上…

Visual Studio Code 的安装教程和配置C语言环境插件推荐

目录 1.vscode简介2.下载安装vs code3.VSCode基础配置VSCode界面简介VSCode设置中文界面VSCode个性化设置VSCode常用设置基本编辑快捷键VSCode常用快捷键 4.下载安装MinGW5.设置vscode里的环境6.插件推荐7.vscode官方文档 1.vscode简介 VSCode是微软出的一款轻量级编辑器&…

第2章 Rust初体验8/8:末尾不带分号的表达式即代码块返回值:更简洁的语法:猜骰子冷热游戏

讲动人的故事,写懂人的代码 2.9 故事7: 玩家输入的数字若越界则继续猜 贾克强:“我们终于要一起写这个游戏的最后一个故事啦!游戏中,你需要猜两个骰子的点数之和,因此你猜的数字应该在2到12之间。我们可以在代码中加入一些判断逻辑,如果你猜的数字超出了这个范围,游戏会…

足底筋膜炎怎么治疗才能彻底除根

现代快节奏的生活中&#xff0c;足底筋膜炎作为一种常见的足部疾病&#xff0c;困扰着越来越多的人。长时间的站立、行走&#xff0c;以及不正确的运动姿势&#xff0c;都可能成为足底筋膜炎的诱因。足底筋膜炎带来的疼痛和不适&#xff0c;严重影响了人们的生活质量和日常工作…

用Rust手把手编写一个Proxy(代理), 开始动工

https://shop.kongfz.com/795263/ 代理端和代理服务端之间可用自有格式来实现多路复用以减少连接的建立断开的开销,目前暂未实现代理服务端。 类结构 proxy.rs 负责代理结构的存储,监听类型,监听地址,是否有父级地址,认证账号密码等。 flag.rs 监听类型的二进制结构,…

网络安全 - DDoS 攻击原理 + 实验

DDoS 攻击 什么是 DDoS 进攻 D D o S \color{cyan}{DDoS} DDoS&#xff08;Distributed Denial of Service&#xff0c;分布式拒绝服务&#xff09;攻击是一种通过多个计算机系统同时向目标系统发送大量请求&#xff0c;消耗其资源&#xff0c;使其无法正常服务的攻击方式。DD…

后端高频面试题分享-用Java判断一个列表是否是另一个列表的顺序子集

问题描述 编写一个函数&#xff0c;该函数接受两个列表作为参数&#xff0c;判断第一个列表是否是第二个列表的顺序子集&#xff0c;返回True或False。 要求 判断一个列表是否是另一个列表的顺序子集&#xff0c;即第一个列表的所有元素在第二个列表需要顺序出现。列表中的元…

Linux常用命令及或g++(或gcc)编辑器运用

一. 实验内容 1&#xff0e;打开VMware Workstation虚拟机进入Ubuntu系统&#xff0c;打开终端。 练习使用常用的Linux命令&#xff0c;主要包括如下命令&#xff1a; mkdir, rmdir, cd, pwd, ls, clear, cat, rm等。&#xff08;其中&#xff0c;cat、rm命令请在下面实验内容3…

Navicat和SQLynx产品功能比较二(SQL查询)

数据库管理工具最常用的功能就是SQL的查询&#xff0c;没有之一。本文针对Navicat和SQLynx做了SQL查询相关的性能测试&#xff0c;从测试结果来看&#xff0c;Navicat主要适合开发类的小型数据量需求&#xff0c;SQLynx可以适应大型数据量或小型数据量的需求&#xff0c;用户可…