Python进阶(1) | 使用VScode写单元测试

Python进阶(1) | 单元测试

2024.01.28
VSCode: 1.85.1
Linux(ubuntu 22.04)

文章目录

  • Python进阶(1) | 单元测试
    • 1. 目的
    • 2. Python Profile
    • 3. 单元测试框架
      • 3.1 什么是单元测试
      • 3.2 选一个单元测试框架
      • 3.3 编写 Python 单元测试代码
      • 3.4 在 VSCode 里发现单元测试
      • 3.5 再写一个单元和测试: IoU 的计算
    • 4. 总结
    • 5. References

1. 目的

使用 Python 实现一些小工具、库的时候,增加单元测试来保证正确性。

重读 VSCode 的 Python 官方文档, 更新个人的 Python 开发效率。

2. Python Profile

VSCode 提供了定制 profile 的功能, 个人目前理解为类似于 vim/emacs 里的模式的升级版。以前我只是配置VSCode的全局配置和当前工程配置, 而 Profile 则是建立了不同的配置,每个打开的VSCode工程都可以在不同的 profile 之间切换。

举例: 分别设置 C++ Profile 和 Python profile, 在 Python profile 和 C++ profile 中使用不同的快捷键、不同的UI布局等。

关于 profile 的完整文档在 https://code.visualstudio.com/docs/editor/profiles

官方提供了 Python 的profile,可以根据这个预定义的 profile, 继承它,创建一个自己的 Python profile:
https://code.visualstudio.com/docs/editor/profiles#_python-profile-template

3. 单元测试框架

3.1 什么是单元测试

A unit is a specific piece of code to be tested, such as a function or a class. Unit tests are then other pieces of code that specifically exercise the code unit with a full range of different inputs, including boundary and edge cases. Both the unittest and pytest frameworks can be used to write unit tests.

所谓单元,指的是一段特定的要被测试的代码,比如说一个函数、一个类。
所谓测试,指的是被测试代码A之外的代码B, 也就是说B这部分代码存在的意义,就是测试A这部分代码。
测试代码通常需要包含各种不同的输入,包括边界情况。
单元测试仅仅关注输入 和 输出, 不关注代码实现的细节。

因此,所谓单元测试,首先需要划分出单元,然后针对每个单元(或者仅对于关注的单元),编写测试代码。

For each input, you then define the function’s expected return value (or values).

对于被测试的代码的每一种输入,你需要定义它的预期结果。

With all the arguments and expected return values in hand, you now write the tests themselves, which are pieces of code that call the function with a particular input, then compare the actual return value with the expected return value (this comparison is called an assertion):

然后调用被测试的代码A: 给它传入输入, 获得它的输出结果, 并且和你预设的结果进行比对,结果一样则成功,不一样则报告失败。

https://code.visualstudio.com/docs/python/testing

3.2 选一个单元测试框架

Python 最常用的单元测试框架: unittest 和 pytest.

unittest 是 Python 标准库的模块, 也就是 Python 安装后自带的。 pytest 则需要自行安装: pip install pytest.

3.3 编写 Python 单元测试代码

首先,是被测试的单元的代码, inc_dec.py:

def increment(x: int):
    return x + 1

def decrement(x: int):
    return x - 1

然后, 是编写测试代码. 先用 unittest 写一遍:test_unittest.py

import inc_dec
import unittest

class Test_TestIncrementDecrement(unittest.TestCase):
    def test_increment(self):
        self.assertEqual(inc_dec.increment(3), 4)
    
    # 这个测试用例一定会失败,是刻意做的
    def test_decrement(self):
        self.assertEqual(inc_dec.decrement(3), 4)

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

再用 pytest 写一遍, 写法更简单:

import inc_dec

def test_increment():
    assert inc_dec.increment(3) == 4

# 这个测试用例一定会失败,是刻意做的
def test_decrement():
    assert inc_dec.decrement(3) == 4

3.4 在 VSCode 里发现单元测试

首先在 VSCode 里点击左侧的 Testing 按钮, 创建测试相关的配置:
在这里插入图片描述

它对应到 .vscode/setting.json 里的内容:

{
    "python.testing.pytestArgs": [
        "."
    ],
    "python.testing.unittestEnabled": false,
    "python.testing.pytestEnabled": true
}

然后点击 Testing 视图中的测试用例中最上方的按钮, 会自动发现和执行所有的测试用例:
在这里插入图片描述

在 Testing 界面中点击到 “失败” (红色) 的case, 会看到失败的具体测试代码。我们发现是测试代码本身写错, 于是改掉, 然后重新在 Testing 界面中执行测试:
在这里插入图片描述

最终,我们看到 Testing 界面中的每一项都是绿色, 表示都成功了:
在这里插入图片描述

3.5 再写一个单元和测试: IoU 的计算

前面给出的 inc_dec.py 的代码太简单, 测试代码也不太符合预期解决的问题。

单元测试的预期目的,是发现单元中的bug。这次写一个经典的计算两个Box的IoU的函数,并且故意缺少处理非法box长度的情况。

bbox.py:

# define Box class
class Box(object):
    def __init__(self, x, y, w, h, score):
        self.x = x
        self.y = y
        self.w = w
        self.h = h
        self.score = score
    def __repr__(self):
        return 'Box(x=%f, y=%f, w=%f, h=%f, score=%f)' % (self.x, self.y, self.w, self.h, self.score)

# calculate IoU of two boxes
def box_iou(box1, box2):
    # get coordinates of intersecting rectangle
    x_left = max(box1.x, box2.x)
    y_top = max(box1.y, box2.y)
    x_right = min(box1.x + box1.w, box2.x + box2.w)
    y_bottom = min(box1.y + box1.h, box2.y + box2.h)
    # if x_right < x_left or y_bottom < y_top:
    #     return 0.0
    # intersection area
    intersection_area = (x_right - x_left) * (y_bottom - y_top)
    # union area
    box1_area = box1.w * box1.h
    box2_area = box2.w * box2.h
    union_area = box1_area + box2_area - intersection_area
    return intersection_area / union_area

test_bbox.py:

import bbox

def test_box_iou():
    box1 = bbox.Box(0, 0, 1, 1, 0.9)
    box2 = bbox.Box(0, 0, 1, 1, 0.9)
    assert bbox.box_iou(box1, box2) == 1.0

def test_box_iou2():
    box1 = bbox.Box(0, 0, 1, 1, 0.9)
    box2 = bbox.Box(1, 1, 2, 2, 0.9)
    assert bbox.box_iou(box1, box2) == 0

def test_box_iou3(): # 这个例子是边界case,很容易失败
    box1 = bbox.Box(0, 0, 0, 0, 0.9)
    box2 = bbox.Box(1, 1, 1, 1, 0.9)
    assert bbox.box_iou(box1, box2) == 0

上述代码在 test_box_iou3() 时失败了, 错误类型是出现了除0错误。显然,除非两个 box 大小都是0,否则不会出现除以0的情况。于是很偷懒的改了一下:

# calculate IoU of two boxes
def box_iou(box1, box2):
    # get coordinates of intersecting rectangle
    x_left = max(box1.x, box2.x)
    y_top = max(box1.y, box2.y)
    x_right = min(box1.x + box1.w, box2.x + box2.w)
    y_bottom = min(box1.y + box1.h, box2.y + box2.h)
    # if x_right < x_left or y_bottom < y_top:
    #     return 0.0
    # intersection area
    intersection_area = (x_right - x_left) * (y_bottom - y_top)
    # union area
    box1_area = box1.w * box1.h
    box2_area = box2.w * box2.h
    union_area = box1_area + box2_area - intersection_area
    if union_area == 0:
        return 0
    return intersection_area / union_area

再增加一个侧测试用例:当box本身的宽度或高度为负值时,预期结果我们设置为0. 测试代码是:

def test_box_iou4():
    box1 = bbox.Box(0, 0, -1, -1, 0.9)
    box2 = bbox.Box(0, 0, 2, 2, 0.9)
    iou = bbox.box_iou(box1, box2)
    assert iou == 0

IoU的实现代码,仍然是用很偷懒的修改:

# calculate IoU of two boxes
def box_iou(box1, box2):
    # get coordinates of intersecting rectangle
    x_left = max(box1.x, box2.x)
    y_top = max(box1.y, box2.y)
    x_right = min(box1.x + box1.w, box2.x + box2.w)
    y_bottom = min(box1.y + box1.h, box2.y + box2.h)
    # if x_right < x_left or y_bottom < y_top:
    #     return 0.0
    # intersection area
    intersection_area = (x_right - x_left) * (y_bottom - y_top)
    # union area
    box1_area = box1.w * box1.h
    box2_area = box2.w * box2.h
    union_area = box1_area + box2_area - intersection_area
    # if union_area == 0:
    #     return 0
    # if box1.w < 0 or box1.h < 0 or box2.w < 0 or box2.h < 0:
    #     return 0
    return intersection_area / union_area

此时的测试仍然不够完备。再补充一个:

def test_box_iou5():
    box1 = bbox.Box(0, 0, 0, 0, 0.9)
    box2 = bbox.Box(0, 0, 0, 0, 0.9)
    iou = bbox.box_iou(box1, box2)
    assert iou == 0

现在,把包含了补丁的 box_iou() 重构一番,得到:

# calculate IoU of two boxes
def box_iou(box1, box2):
    # get coordinates of intersecting rectangle
    x_left = max(box1.x, box2.x)
    y_top = max(box1.y, box2.y)
    x_right = min(box1.x + box1.w, box2.x + box2.w)
    y_bottom = min(box1.y + box1.h, box2.y + box2.h)
    if x_right <= x_left or y_bottom <= y_top:
        return 0.0
    # intersection area
    intersection_area = (x_right - x_left) * (y_bottom - y_top)
    # union area
    box1_area = box1.w * box1.h
    box2_area = box2.w * box2.h
    union_area = box1_area + box2_area - intersection_area
    return intersection_area / union_area

4. 总结

VSCode 的 Testing 视图,改善了运行单元测试的交互界面。传统的 C/C++ 中, gtest 框架通过传入 --gtest_filter=xxx 来过滤测试, 在 VSCode 面前仍然落后。

至于单元测试代码是否够好, 一个标准是覆盖率的高低, 就像 IoU 的例子, 第一次用 ChatGPT 生成代码时,虽然看似正确, 但其实 test_box_iou5() 这个测试用例(两个box的大小都是0,并且重合)是无法通过的。

因此, VSCode 的 Testing 界面仅仅是锦上添花, 单元测试的编写仍然需要考虑周全。

5. References

  • https://code.visualstudio.com/docs/editor/profiles#_python-profile-template
  • https://code.visualstudio.com/docs/python/testing

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

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

相关文章

问题:github上不了,但是其他网页可以正常打开

问题&#xff1a; github上不了&#xff0c;但是其他网页可以正常打开&#xff0c;试了关闭防火墙&#xff0c;dns刷新&#xff0c;都没用后&#xff0c;参考以下文章成功打开Github 1.Github无法访问解决方法 2.github访问不了&#xff1f;详细解决方法 解决办法&#xff1a…

用Python编写的简单双人对战五子棋游戏

本文是使用python创建的一个基于tkinter库的GUI界面&#xff0c;用于实现五子棋游戏。编辑器使用的是spyder&#xff0c;该工具。既方便做数据分析&#xff0c;又可以做小工具开发&#xff0c; 首先&#xff0c;导入tkinter库&#xff1a;import tkinter as tk&#xff0c;这…

leetcode刷题日志-146LRU缓存

思路&#xff1a;使用hashmap储存key&#xff0c;vaule&#xff0c;使用双向链表以快速查到尾结点&#xff08;待逐出的节点&#xff09;&#xff0c;链表的题一定要在纸上画一下&#xff0c;不然连着连着就不知道连在哪里去了 class LRUCache {public class ListNode {int ke…

Java基础常见面试题总结(下)

常见的Exception有哪些&#xff1f; 常见的RuntimeException&#xff1a; ClassCastException //类型转换异常IndexOutOfBoundsException //数组越界异常NullPointerException //空指针ArrayStoreException //数组存储异常NumberFormatException //数字格式化异常ArithmeticE…

【Mac】windows PC用户转用Mac 配置笔记

win转mac使用的一些配置笔记&#xff1b;感觉mac在UI上还是略胜一筹&#xff0c;再配合在win上的操作习惯就体验更好了&#xff0c;对日常办公需求的本人足以。 优化设置 主要 操作优化 AltTab&#xff1a; win 习惯查看全部活动的alt键&#xff0c;对比cmdtab多了可以预览&…

前端——JavaScript

目录 文章目录 前言 一. JavaScript基础 1.JavaScript基本结构 2. JavaScript 执行过程 3. JavaScript 引入方式 二. JavaScript 语法 1.数据类型 2.变量 2.1 var 关键字定义变量 2.2 let 关键字定义变量 2.3 var 与 let 的区别 3.字符串 3.1定义字符串 3.2 字…

Px4学习:进入控制台的方法

运行命令 ls /dev/tty* 会列出所有端口 然后连接飞控通过USB数据线连接到电脑&#xff0c;再运行一次&#xff0c;就可以找到 笔者的是ttyACM0&#xff0c;下面会用到 px4源码 1.13.3 进入控制台 进入PX4源码文件夹&#xff0c;用终端打开&#xff0c;运行命令 ./Tools/mav…

Qt|大小端数据转换

后面打算写Qt关于网络编程的博客&#xff0c;网络编程就绕不开字节流数据传输&#xff0c;字节流数据的传输一般是根据协议来定义对应的报文该如何组包&#xff0c;那这就必然牵扯到了大端字节序和小端字节序的问题了。不清楚的大小端的可以看一下相关资料&#xff1a;大小端模…

jenkins对接K8S

创建连接K8S的凭据 查看需要使用到的命名空间 [rootk8s ~]# kubectl get ns |grep arts-system arts-system Active 16d创建service accounts [rootk8s ~]# kubectl create sa jenkins-k8s -n arts-system serviceaccount/jenkins-k8s created [rootk8s ~]# kubectl…

log4j2 配置入门介绍

配置 将日志请求插入到应用程序代码中需要进行大量的计划和工作。 观察表明&#xff0c;大约4%的代码专门用于日志记录。因此&#xff0c;即使是中等规模的应用程序也会在其代码中嵌入数千条日志记录语句。 考虑到它们的数量&#xff0c;必须管理这些日志语句&#xff0c;而…

CTF CRYPTO 密码学-7

题目名称&#xff1a;敲击 题目描述&#xff1a; 让我们回到最开始的地方 0110011001101100011000010110011101111011011000110110010100110011011001010011010100110000001100100110001100101101001101000011100001100011001110010010110100110100011001000011010100110000…

python简单socket demo

socket说明 socket本质是编程接口(API)&#xff0c;对TCP/IP的封装&#xff0c;TCP/IP也要提供可供程序员做网络开发所用的接口&#xff0c;这就是Socket编程接口。除了常见的http请求之外&#xff0c;一些敏感的数据传输常用socket套接字层直接传输数据。一个简单的domo用于熟…

构造器模式

构造器模式 意图 将一个复杂对象的构建和表示分离&#xff0c;使得相同的构建能创建不同的表示。 解释 案例&#xff1a;想象一个角色扮演游戏的特征生成器。最简单的选择是让计算机为你创建角色。如果你想手动选择特征的细节像职业、性别、头发的颜色等。特征的产生是一个循…

【golang】16、dlv 调试工具、vscode+ssh 远程调试

文章目录 Goland Debug 模式崩溃 Goland Debug 模式崩溃 有时遇到如下现象&#xff1a; Golang Run 模式正常&#xff0c;Debug 无 BreakPoint 模式正常&#xff0c;但 Debug 加 BreakPoint 就会偶现 panic&#xff0c;panic 信息如下。 panic: runtime error: index out of …

敲黑板啦!CSGO游戏搬砖项目操作注意事项

CSGO游戏搬砖项目怎么赚钱的&#xff0c;利润在哪&#xff1f; 1.两个平台之间币种不一样&#xff0c;就存在一个汇率差&#xff0c;两平台装备价格也不一样&#xff0c;汇率差-价格差利润。 CSGO游戏搬砖项目具体有哪些操作步骤&#xff1f; 1、准备一台电脑&#xff0c;配置…

操作系统(7)----调度相关知识点(万字总结~)

目录 一.调度的三个层次 1.高级调度 2.低级调度 3.中级调度 二.进程的挂起状态 三.进程调度的时机 四.进程调度方式 1.非剥夺调度方式 2.剥夺调度方式 五.进程的切换与过程 六.调度器/调度程序 1.调度程序 2.闲逛进程 七.评价调度算法的各个指标 1.CPU利用率 2…

yarn安装第三方插件包,提示报错,yarn的镜像源已经过期了,因为yarn和npm用的是淘宝的镜像源,淘宝的镜像源已经过期了,要设置最新的淘宝镜像源。

淘宝最新镜像源切换_淘宝镜像-CSDN博客 查看yarn用的什么镜像源 yarn config get registry 查看具体的信息 yarn config list 设置淘宝的最新镜像源&#xff0c;yarn和npm都要设置最新的淘宝镜像源&#xff0c;不然还是报错 npm config set registry https://registry.npmm…

Mysql-存储引擎-InnoDB

数据文件 下面这条SQL语句执行的时候指定了ENGINE InnoDB存储引擎为InnoDB: CREATE TABLE tb_album (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 编号,title varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 相册名称,image varc…

leetcode26. 删除有序数组中的重复项

题目 题目 给你一个 非严格递增排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums 的唯一元素的数量为 k &…

python笔记10

1、继承 继承是面向对象编程中的一个重要概念&#xff0c;它允许一个类&#xff08;子类&#xff09;继承另一个类&#xff08;父类&#xff09;的属性和方法。通过继承&#xff0c;子类可以重用父类的代码&#xff0c;并且有机会添加新的属性和方法&#xff0c;或者重写父类的…