第31章 测试驱动开发中的设计模式与重构解析(Python 版)

写在前面


这本书是我们老板推荐过的,我在《价值心法》的推荐书单里也看到了它。用了一段时间 Cursor 软件后,我突然思考,对于测试开发工程师来说,什么才更有价值呢?如何让 AI 工具更好地辅助自己写代码,或许优质的单元测试是一个切入点。 就我个人而言,这本书确实很有帮助。第一次读的时候,很多细节我都不太懂,但将书中内容应用到工作中后,我受益匪浅。比如面对一些让人抓狂的代码设计时,书里的方法能让我逐步深入理解代码的逻辑与设计。 作为一名测试开发工程师,我想把学习这本书的经验分享给大家,希望能给大家带来帮助。因为现在工作中大多使用 Python 代码,所以我把书中JAVA案例都用 Python 代码进行了改写 。

在测试驱动开发(TDD)的流程里,设计模式与重构是提升软件质量和可维护性的关键环节。前面我们探讨了多种设计模式,现在深入介绍重构相关的模式,包括调和差异、隔离改变、迁移数据等,同样以 Python 示例代码辅助理解。

重构相关模式

调和差异(Reconcile Differences)

概念与应用场景

调和差异模式用于合并两段看似相似的代码。通过逐渐消除它们之间的差异,仅在完全相同的情况下合并,可减少代码冗余,提升代码的一致性和可维护性。

示例代码

假设我们有两个相似的函数,分别计算矩形和正方形的面积,通过调和差异的思路来重构。

# 原始两个相似函数
def calculate_rectangle_area(length, width):return length * widthdef calculate_square_area(side):return side * side# 调和差异后的函数
def calculate_area(dimension1, dimension2=None):if dimension2 is None:return dimension1 * dimension1return dimension1 * dimension2

隔离改变(Isolate Change)

概念与用途

隔离改变模式旨在将复杂方法或对象中需要改变的部分隔离开来,就像外科手术一样,专注于特定区域的改动,降低对系统其他部分的影响。当改动完成且确认影响不大时,可撤销隔离。

示例代码

假设我们有一个银行利率计算类,其中 findRate 方法较为复杂,现在要对其部分逻辑进行修改,使用隔离改变的方式。

class Bank:def __init__(self):self.rate_config = {"default_rate": 0.05}# 原始复杂方法def findRate(self, amount, term):# 复杂逻辑,假设根据金额和期限计算利率base_rate = self.rate_config["default_rate"]if amount > 10000:base_rate += 0.01if term > 5:base_rate += 0.005return base_rate# 隔离改变部分逻辑def _calculate_extra_rate(self, amount, term):extra_rate = 0if amount > 10000:extra_rate += 0.01if term > 5:extra_rate += 0.005return extra_ratedef findRate_isolated(self, amount, term):base_rate = self.rate_config["default_rate"]extra_rate = self._calculate_extra_rate(amount, term)return base_rate + extra_rate

迁移数据(Migrate Data)

概念与实现方式

迁移数据模式用于处理数据表示方式的变更,可通过临时复制数据等步骤,实现从一种数据格式到另一种数据格式的转换,确保数据的正确迁移和系统的正常运行。

示例代码

假设我们有一个测试用例类 TestSuite,最初使用单个测试实例,现在要迁移为测试列表。

# 原始 TestSuite 类
class TestSuite:def __init__(self):self.test = Nonedef add(self, test):self.test = testdef run(self, result):self.test.run(result)# 数据迁移过程
class NewTestSuite:def __init__(self):self.tests = []def add(self, test):self.tests.append(test)def run(self, result):for test in self.tests:test.run(result)# 测试用例类
class TestCase:def __init__(self, name):self.name = namedef run(self, result):print(f"Running test: {self.name}")

提取方法(Extract Method)

概念与操作步骤

提取方法模式将复杂方法中的一部分代码抽取出来,形成独立的方法,以提高代码的可读性和可维护性。具体步骤包括开辟代码段、确保变量处理正确、复制代码到新方法、在原方法中调用新方法等。

示例代码

假设我们有一个计算订单总金额的复杂方法,包含折扣计算等逻辑,进行提取方法的重构。

# 原始复杂方法
def calculate_order_total(products, discount_rate):subtotal = 0for product in products:subtotal += product.pricediscount_amount = subtotal * discount_ratereturn subtotal - discount_amount# 提取方法后的代码
def calculate_subtotal(products):subtotal = 0for product in products:subtotal += product.pricereturn subtotaldef calculate_discount(subtotal, discount_rate):return subtotal * discount_ratedef calculate_order_total_extracted(products, discount_rate):subtotal = calculate_subtotal(products)discount_amount = calculate_discount(subtotal, discount_rate)return subtotal - discount_amount# 产品类
class Product:def __init__(self, price):self.price = price

内联方法(Inline Method)

概念与使用场景

内联方法模式用于简化过于零乱和松散的控制流,通过用方法本身替换方法的调用,减少方法调用的层级,使代码逻辑更加直观。

示例代码

假设我们有两个简单方法,add_numbers 调用 calculate_sum,现在进行内联方法重构。

# 原始两个方法
def calculate_sum(a, b):return a + bdef add_numbers(x, y):return calculate_sum(x, y)# 内联方法后的代码
def add_numbers_inlined(x, y):return x + y

提取接口(Extract Interface)

概念与实施步骤

提取接口模式在 Java 语言中用于引入操作的第二个实现,通过声明接口、让已有类实现接口等步骤,实现代码的多态性和扩展性。

示例代码

假设我们有 RectangleCircle 类,现在提取一个 Shape 接口。

# 提取的接口
class Shape:def area(self):pass# 实现接口的类
class Rectangle(Shape):def __init__(self, width, height):self.width = widthself.height = heightdef area(self):return self.width * self.heightclass Circle(Shape):def __init__(self, radius):self.radius = radiusdef area(self):return 3.14 * self.radius * self.radius

转移方法(Move Method)

概念与转移步骤

转移方法模式将某个方法移动到更合适的类中,以优化代码结构。步骤包括复制方法、粘贴到目标类、处理对象引用和变量传递、调用新方法替代原方法等。

示例代码

假设我们有 Product 类和 Order 类,Product 类中有一个计算产品总价的方法,现在将其转移到 Order 类中。

# 原始 Product 类
class Product:def __init__(self, price, quantity):self.price = priceself.quantity = quantitydef calculate_total(self):return self.price * self.quantity# 原始 Order 类
class Order:def __init__(self, products):self.products = productsdef total_price(self):total = 0for product in self.products:total += product.calculate_total()return total# 转移方法后的代码
class NewProduct:def __init__(self, price, quantity):self.price = priceself.quantity = quantityclass NewOrder:def __init__(self, products):self.products = productsdef calculate_product_total(self, product):return product.price * product.quantitydef total_price(self):total = 0for product in self.products:total += self.calculate_product_total(product)return total

方法对象(Method Object)

概念与构建方式

方法对象模式用于表示需要多个参数和本地变量的复杂方法,通过创建一个对象,将方法的参数和逻辑封装其中,提供更灵活的处理方式。

示例代码

假设我们有一个复杂的计算现金流净现值的方法,现在使用方法对象模式重构。

class CashFlowCalculator:def __init__(self, cash_flows, discount_rate):self.cash_flows = cash_flowsself.discount_rate = discount_ratedef run(self):npv = 0for i, cash_flow in enumerate(self.cash_flows):npv += cash_flow / ((1 + self.discount_rate) ** i)return npv

添加参数(Add Parameter)

概念与添加步骤

添加参数模式用于给方法添加参数,若方法属于接口,需先给接口添加参数,然后给方法添加参数,并根据编译错误修改其他相关代码。

示例代码

假设我们有一个计算圆面积的方法,现在要添加一个参数表示是否使用高精度计算。

# 原始方法
def calculate_circle_area(radius):return 3.14 * radius * radius# 添加参数后的方法
def calculate_circle_area_with_param(radius, high_precision=False):if high_precision:import mathreturn math.pi * radius * radiusreturn 3.14 * radius * radius

将方法中的参数变成构造方法中的参数

概念与转换步骤

此模式将方法中的参数转移到构造方法中,以简化方法调用和参数传递。步骤包括给构造方法添加参数、添加实例变量、设置变量、转换引用、删除原方法和调用中的参数、移除多余引用、重命名变量等。

示例代码

假设我们有一个 Person 类,其 set_age 方法有一个参数,现在将该参数转移到构造方法中。

# 原始类
class Person:def __init__(self):self.age = Nonedef set_age(self, age):self.age = age# 转换后的类
class NewPerson:def __init__(self, age):self.age = age

总结

测试驱动开发中的设计模式和重构技巧相辅相成。设计模式提供了通用的解决方案来构建良好的软件架构,而重构则允许我们在不改变系统外部行为的前提下,优化代码结构、提高可读性和可维护性。

从命令模式的请求封装到内联方法的控制流简化,从隔离改变的局部优化到迁移数据的数据格式转换,这些模式和技巧为开发者在面对各种开发场景时提供了丰富的工具和思路。在实际项目中,应根据具体需求和代码状况,灵活运用这些设计模式和重构方法,不断迭代和完善软件系统,以达到高效、可靠的开发目标。

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

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

相关文章

docker中运行的MySQL怎么修改密码

1,进入MySQL容器 docker exec -it 容器名 bash 我运行了 docker ps命令查看。正在运行的容器名称。可以看到MySQL的我起名为db docker exec -it db bash 这样就成功的进入到容器中了。 2,登录MySQL中 mysql -u 用户名 -p 回车 密码 mysql -u root -p roo…

春节期间,景区和酒店如何合理用工?

春节期间,景区和酒店如何合理用工? 春节期间,旅游市场将迎来高峰期。景区与酒店,作为旅游产业链中的两大核心环节,承载着无数游客的欢乐与期待。然而,也隐藏着用工管理的巨大挑战。如何合理安排人力资源&a…

初始化mysql报错cannot open shared object file: No such file or directory

报错展示 我在初始化msyql的时候报错:mysqld: error while loading shared libraries: libaio.so.1: cannot open shared object file: No such file or directory 解读: libaio包的作用是为了支持同步I/O。对于数据库之类的系统特别重要,因此…

C语言------数组从入门到精通

1.一维数组 目标:通过思维导图了解学习一维数组的核心知识点: 1.1定义 使用 类型名 数组名[数组长度]; 定义数组。 // 示例: int arr[5]; 1.2一维数组初始化 数组的初始化可以分为静态初始化和动态初始化两种方式。 它们的主要区别在于初始化的时机和内存分配的方…

Docker/K8S

文章目录 项目地址一、Docker1.1 创建一个Node服务image1.2 volume1.3 网络1.4 docker compose 二、K8S2.1 集群组成2.2 Pod1. 如何使用Pod(1) 运行一个pod(2) 运行多个pod 2.3 pod的生命周期2.4 pod中的容器1. 容器的生命周期2. 生命周期的回调3. 容器重启策略4. 自定义容器启…

【开源免费】基于SpringBoot+Vue.JS公交线路查询系统(JAVA毕业设计)

本文项目编号 T 164 ,文末自助获取源码 \color{red}{T164,文末自助获取源码} T164,文末自助获取源码 目录 一、系统介绍二、数据库设计三、配套教程3.1 启动教程3.2 讲解视频3.3 二次开发教程 四、功能截图五、文案资料5.1 选题背景5.2 国内…

< OS 有关 > Android 手机 SSH 客户端 app: connectBot

connectBot 开源且功能齐全的SSH客户端,界面简洁,支持证书密钥。 下载量超 500万 方便在 Android 手机上,连接 SSH 服务器,去运行命令。 Fail2ban 12小时内抓获的 IP ~ ~ ~ ~ rootjpn:~# sudo fail2ban-client status sshd Status for the jail: sshd …

中国股市“慢牛”行情的实现路径与展望

在现代经济体系中,股市不仅是企业融资的重要平台,也是投资者财富增值的关键渠道。一个健康、稳定、持续增长的股市,对于推动经济高质量发展、提升国家金融竞争力具有深远意义。近年来,“慢牛”行情成为众多投资者和市场参与者对我…

Linux Samba 低版本漏洞(远程控制)复现与剖析

目录 前言 漏洞介绍 漏洞原理 产生条件 漏洞影响 防御措施 复现过程 结语 前言 在网络安全的复杂生态中,系统漏洞的探索与防范始终是保障数字世界安全稳定运行的关键所在。Linux Samba 作为一款在网络共享服务领域应用极为广泛的软件,其低版本中…

ResNet 残差网络

目录 网络结构 残差块(Residual Block) ResNet网络结构示意图 残差块(Residual Block)细节 基本残差块(ResNet-18/34) Bottleneck残差块(ResNet-50/101/152) 残差连接类型对比 变体网…

【Unity3D】实现横版2D游戏角色二段跳、蹬墙跳、扶墙下滑

目录 一、二段跳、蹬墙跳 二、扶墙下滑 一、二段跳、蹬墙跳 GitHub - prime31/CharacterController2D 下载工程后直接打开demo场景:DemoScene(Unity 2019.4.0f1项目环境) Player物体上的CharacterController2D,Mask添加Wall层…

FPGA 使用 CLOCK_LOW_FANOUT 约束

使用 CLOCK_LOW_FANOUT 约束 您可以使用 CLOCK_LOW_FANOUT 约束在单个时钟区域中包含时钟缓存负载。在由全局时钟缓存直接驱动的时钟网段 上对 CLOCK_LOW_FANOUT 进行设置,而且全局时钟缓存扇出必须低于 2000 个负载。 注释: 当与其他时钟约束配合…

Excel 技巧21 - Excel中整理美化数据实例,Ctrl+T 超级表格(★★★)

本文讲Excel中如何整理美化数据的实例,以及CtrlT 超级表格的常用功能。 目录 1,Excel中整理美化数据 1-1,设置间隔行颜色 1-2,给总销量列设置数据条 1-3,根据总销量设置排序 1-4,加一个销售趋势列 2&…

Leetcode:219

1&#xff0c;题目 2&#xff0c;思路 第一种就是简单的暴力比对当时过年没细想 第二种&#xff1a; 用Map的特性key唯一&#xff0c;把数组的值作为Map的key值我们每加载一个元素都会去判断这个元素在Map里面存在与否如果存在进行第二个判断条件abs(i-j)<k,条件 符合直接…

MySQL(高级特性篇) 14 章——MySQL事务日志

事务有4种特性&#xff1a;原子性、一致性、隔离性和持久性 事务的隔离性由锁机制实现事务的原子性、一致性和持久性由事务的redo日志和undo日志来保证&#xff08;1&#xff09;REDO LOG称为重做日志&#xff0c;用来保证事务的持久性&#xff08;2&#xff09;UNDO LOG称为回…

芯片AI深度实战:进阶篇之vim内verilog实时自定义检视

本文基于Editor Integration | ast-grep&#xff0c;以及coc.nvim&#xff0c;并基于以下verilog parser(my-language.so&#xff0c;文末下载链接), 可以在vim中实时显示自定义的verilog 匹配。效果图如下&#xff1a; 需要的配置如下&#xff1a; 系列文章&#xff1a; 芯片…

C++:多继承习题5

题目内容&#xff1a; 先建立一个Point(点)类&#xff0c;包含数据成员x,y(坐标点)。以它为基类&#xff0c;派生出一个Circle(圆)类&#xff0c;增加数据成员r(半径)&#xff0c;再以Circle类为直接基类&#xff0c;派生出一个Cylinder(圆柱体)类&#xff0c;再增加数据成员h…

基于阿里云百炼大模型Sensevoice-1的语音识别与文本保存工具开发

基于阿里云百炼大模型Sensevoice-1的语音识别与文本保存工具开发 摘要 随着人工智能技术的不断发展&#xff0c;语音识别在会议记录、语音笔记等场景中得到了广泛应用。本文介绍了一个基于Python和阿里云百炼大模型的语音识别与文本保存工具的开发过程。该工具能够高效地识别东…

buu-pwn1_sctf_2016-好久不见29

这个也是栈溢出&#xff0c;不一样的点是&#xff0c;有replace替换&#xff0c;要输入0x3c字符&#xff08;60&#xff09;&#xff0c;Iyou 所以&#xff0c;20个I就行&#xff0c;找后面函数 输出提示信息&#xff0c;要求用户输入关于自己的信息。 使用fgets函数从标准输入…

【C语言】在Windows上为可执行文件.exe添加自定义图标

本文详细介绍了在 Windows 环境下,如何为使用 GCC 编译器编译的 C程序 添加自定义图标,从而生成带有图标的 .exe 可执行文件。通过本文的指导,读者可以了解到所需的条件以及具体的操作步骤,使生成的程序更具专业性和个性化。 目录 1. 准备条件2. 具体步骤步骤 1: 准备资源文…