惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园_首页
T
The Blog of Author Tim Ferriss
罗磊的独立博客
The Register - Security
The Register - Security
IT之家
IT之家
D
Docker
爱范儿
爱范儿
Apple Machine Learning Research
Apple Machine Learning Research
Hugging Face - Blog
Hugging Face - Blog
博客园 - Franky
L
LangChain Blog
J
Java Code Geeks
博客园 - 三生石上(FineUI控件)
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
K
Kaspersky official blog
Latest news
Latest news
博客园 - 聂微东
酷 壳 – CoolShell
酷 壳 – CoolShell
Cyberwarzone
Cyberwarzone
人人都是产品经理
人人都是产品经理
C
CERT Recently Published Vulnerability Notes
The Hacker News
The Hacker News
T
Tailwind CSS Blog
S
SegmentFault 最新的问题
MongoDB | Blog
MongoDB | Blog
Scott Helme
Scott Helme
L
LINUX DO - 热门话题
Spread Privacy
Spread Privacy
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
D
DataBreaches.Net
Project Zero
Project Zero
H
Hackread – Cybersecurity News, Data Breaches, AI and More
G
Google Developers Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
C
Check Point Blog
S
Schneier on Security
NISL@THU
NISL@THU
T
Tor Project blog
P
Proofpoint News Feed
N
News and Events Feed by Topic
Last Week in AI
Last Week in AI
N
Netflix TechBlog - Medium
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
U
Unit 42
C
Cybersecurity and Infrastructure Security Agency CISA
T
The Exploit Database - CXSecurity.com
L
LINUX DO - 最新话题
Help Net Security
Help Net Security
Blog — PlanetScale
Blog — PlanetScale

郑文峰的博客

使用dify对接飞书多维表格 使用n8n对接飞书多维表格 服务启动时出现 OOM 一次服务升级时pg表DDL执行超时失败 Go语言高效IO缓冲技术详解 Go语言延迟初始化(Lazy Initialization)最佳实践 Go语言字符串拼接性能对比与优化指南 Go语言结构体内存对齐完全指南 Go语言空结构体:零内存消耗的高效编程 Go语言堆栈分配与逃逸分析深度解析 Go语言原子操作完全指南 Go语言内存预分配完全指南 Go语言不可变数据共享:无锁并发编程实践 Go语言零拷贝技术完全指南 Go语言遍历性能深度解析:从原理到优化实践 Go语言Interface Boxing原理与性能优化指南 Go协程池深度解析:原理、实现与最佳实践 使用etcd分布式锁导致的协程泄露与死锁问题 基于pre-commit的Python代码规范落地实践 初识 MCP Server pulsar阻塞导致logstash无法接入日志 django-prometheus使用及源码分析 kube-proxy源码分析 kubernetes service如何通过iptables转发 tcp缓存引起的日志丢失 django-apschedule定时任务异常停止 理解calico容器网络通信方案原理 理解flannel的三种容器网络方案原理 理解Linux IPIP隧道 理解VXLAN网络 理解Linux TunTap设备 快速了解iptables kafka中listener和advertised.listeners的作用 django rest_framework 分页 django后端服务、logstash和flink接入VictoriaMetrics指标监控 python中import原理 docker容器单机网络 手动实现docker容器bridge网络模型 mysql之MVCC原理 mysql之日志 使用java开发logstash的filter插件 使用python实现单例模式的三种方式 redis之缓存 redis之分片集群 redis之哨兵机制 redis之主从库同步 redis之持久化 redis之五种基本数据类型 go中如何处理error pod中将代码与运行环境分离 ddt源码分析 python装饰器的使用方法 读书笔记:如何阅读一本书 使用kubeadm安装k8s 优化gin表单的错误提示信息 gin中validator模块的源码分析 go简单使用grpc python简单使用grpc k8s之PV、PVC和StorageClass k8s之StatefulSet k8s之DaemonSet k8s之Job和CronJob k8s之ConfigMap和Secret k8s之Service k8s之Pod k8s之Deployment 容器的本质 docker容器 python迭代器与生成器 python元编程 python垃圾回收机制 python上下文管理器 django rest_framework使用jwt django rest_framework异常处理 django rest_framework 自定义文档 django压缩文件下载 django rest_framework使用pytest单元测试 django restframework choice 自定义输出数据 django Filtering 使用 django viewset 和 Router 配合使用时报的错 django model的序列化 django中使用AbStractUser django.core.exceptions.ImproperlyConfigured Application labels aren't unique, duplicates users django 中 media配置 django 外键引用自身和on_delete参数 django 警告 while time zone support is active Flask使用flask_socketio实现websocket flask结合mongo tornado 文件上传 tornado 使用jwt完成用户异步认证 tornado 用户密码 bcrypt加密 tornado 结合wtforms使用表单操作 tornado finish和write区别 tornado 使用peewee-async 完成异步orm数据库操作 pyspark streaming简介 和 消费 kafka示例 使用hue创建ozzie的pyspark action workflow count的性能优化 django rest_framework Authentication django celery 结合使用 网站
使用ddt实现unittest的参数化测试
zhengwenfeng · 2022-10-12 · via 郑文峰的博客

# 0. 前言

本文介绍如何使用ddt库来完成unitest的参数化设置。

ddt的github地址 (opens new window)

ddt的官方文档 (opens new window)

# 1. 为什么需要参数化

我们在写单测中,需要考虑到各种场景,通过输入各种场景的值执行目的的方法,来判断输出是否是我们所期待的值。

如下代码代码所示,针对large_than_two方法进行了三种场景的校验写了三个单测,但其中逻辑代码是一致的,而只需要使用不同的参数值进行输入,导致有许多的重复代码进行复制粘贴。

from unittest.case import TestCase


def large_than_two(value) -> bool:
    return value > 2


class TestDemoCase(TestCase):

    def test_larger_than_two_with_three(self):
        self.assertTrue(large_than_two(3))

    def test_larger_than_two_with_eight(self):
        self.assertTrue(large_than_two(8))

    def test_larger_than_two_with_five(self):
        self.assertFalse(large_than_two(5))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 2. 使用ddt实现参数化

首先需要通过pip来安装该库

# 2.1 基本使用

我们在TestCase上添加ddt装饰器,然后在单测方法上添加data装饰器,并添加了3种场景的输入参数,如下代码所示:

from unittest.case import TestCase

from ddt import ddt, data


def large_than_two(value) -> bool:
    return value > 2


@ddt
class TestDemoCase(TestCase):

    @data(3, 8, 5)
    def test_larger_than_two(self, value):
        self.assertTrue(large_than_two(value))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

执行上面的单测,输入出如下:

============================= test session starts =============================
collecting ... collected 3 items

test_demo.py::TestDemoCase::test_larger_than_two_with_three_1_3 PASSED   [ 33%]
test_demo.py::TestDemoCase::test_larger_than_two_with_three_2_8 PASSED   [ 66%]
test_demo.py::TestDemoCase::test_larger_than_two_with_three_3_5 PASSED   [100%]

============================== 3 passed in 0.02s ==============================

1
2
3
4
5
6
7
8

会发现,虽然我们写了一个单测用例,但是却执行了3个用例,这是因为ddt会将data的3个参数分别注入到test_larger_than_two方法中value中并执行单测。

在输出的单测信息中,会输出单测方法+第多少个单测+参数值来表示当前用例的执行。

通过这种方式可以减少我们的重复代码。

# 2.2 多个值使用参数化

当我们需要在一个单测用例中注入多个值时,可以在data中传入多个元组进行参数化,但执行单例时,会将元组注入到value中,我们将其解开则能拿到多个值。代码如下图所示:

def greater(v1, v2) -> bool:
    return v1 > v2


@ddt
class TestDemoCase(TestCase):

    @data(
        (3, 2),
        (4, 1),
        (8, 6))
    def test_greater(self, value):
        v1, v2 = value
        self.assertTrue(greater(v1, v2))

1
2
3
4
5
6
7
8
9
10
11
12
13
14

执行之后输出如下所示:

============================= test session starts =============================
collecting ... collected 3 items

test_demo.py::TestDemoCase::test_greater_1__3__2_ PASSED                 [ 33%]
test_demo.py::TestDemoCase::test_greater_2__4__1_ PASSED                 [ 66%]
test_demo.py::TestDemoCase::test_greater_3__8__6_ PASSED                 [100%]

============================== 3 passed in 0.02s ==============================

1
2
3
4
5
6
7
8

# 2.3 参数化扁平使用

元组中的数据可以由ddt解开后注入到单测方法中的参数中。只需要在单测方法钱添加unpack装饰器即可。代码如下所示:

from unittest.case import TestCase

from ddt import ddt, data, unpack

@ddt
class TestDemoCase(TestCase):

    @unpack
    @data(
        (3, 2),
        (4, 1),
        (8, 6))
    def test_greater(self, v1, v2):
        self.assertTrue(greater(v1, v2))

1
2
3
4
5
6
7
8
9
10
11
12
13
14

执行后结果如下:

============================= test session starts =============================
collecting ... collected 3 items

test_demo.py::TestDemoCase::test_greater_1__3__2_ PASSED                 [ 33%]
test_demo.py::TestDemoCase::test_greater_2__4__1_ PASSED                 [ 66%]
test_demo.py::TestDemoCase::test_greater_3__8__6_ PASSED                 [100%]

============================== 3 passed in 0.02s ==============================

1
2
3
4
5
6
7
8

# 2.4 命名参数

我们还可以给传入的参数进行命名而不是元组的形式,传入的参数名称与单测方法中参数的变量名对应,则不需要对应顺序传入,可读性更强了。代码如下:

@ddt
class TestDemoCase(TestCase):

    @unpack
    @data(
        {"first": 3, "second": 2},
        {"first": 4, "second": 1},
        {"first": 8, "second": 6}
    )
    def test_greater(self, second, first):
        self.assertTrue(greater(first, second))

1
2
3
4
5
6
7
8
9
10
11

执行后输出:

============================= test session starts =============================
collecting ... collected 3 items

test_demo.py::TestDemoCase::test_greater_1 PASSED                        [ 33%]
test_demo.py::TestDemoCase::test_greater_2 PASSED                        [ 66%]
test_demo.py::TestDemoCase::test_greater_3 PASSED                        [100%]

============================== 3 passed in 0.01s ==============================

1
2
3
4
5
6
7
8

# 2.5 从json文件中读取参数进行单测

在某些业务中,输入的参数过于复杂,并且场景繁多,如果将参数数据全部放在单测代码中,则会显得繁重,而且代码不易读,ddt提供了从json文件中读取参数来作为单测的输入数据。

创建data.json文件,其中包含了参数数据。

[
  {
    "first": 3,
    "second": 2
  },
  {
    "first": 4,
    "second": 1
  },
  {
    "first": 8,
    "second": 6
  }
]

1
2
3
4
5
6
7
8
9
10
11
12
13
14

然后使用file_data作为单测方法的装饰器,并传入上面数据文件的路径。

from ddt import ddt, file_data

@ddt
class TestDemoCase(TestCase):

    @file_data("data.json")
    def test_greater(self, second, first):
        self.assertTrue(greater(first, second))

1
2
3
4
5
6
7
8

执行成功并输出:

============================= test session starts =============================
collecting ... collected 3 items

test_demo.py::TestDemoCase::test_greater_1 PASSED                        [ 33%]
test_demo.py::TestDemoCase::test_greater_2 PASSED                        [ 66%]
test_demo.py::TestDemoCase::test_greater_3 PASSED                        [100%]

============================== 3 passed in 0.02s ==============================

1
2
3
4
5
6
7
8

# 3. 总结

本文是介绍ddt的基本并常用的用法,如果想要深入使用可以参考官方文档。

其实ddt有个缺点是不能针对某一个单测方法进行单独的执行,必须要运行整个Unittest class才行,这样在调试的过程中非常不方便。

如果你看到本文其实我比较推荐你使用pytest来替代unittest使用,pytest中也有参数化的使用,并且可以单独的去运行每一个单测。

我是因为在做一个django项目,其中使用的是django test来写单测的,而django test是基于Unittest来实现的,所以只能使用ddt来实现参数化。