Bootstrap

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

写在前面


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

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

重构相关模式

调和差异(Reconcile Differences)

概念与应用场景

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

示例代码

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

# 原始两个相似函数
def calculate_rectangle_area(length, width):
    return length * width


def calculate_square_area(side):
    return side * side


# 调和差异后的函数
def calculate_area(dimension1, dimension2=None):
    if dimension2 is None:
        return dimension1 * dimension1
    return 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.01
        if term > 5:
            base_rate += 0.005
        return base_rate

    # 隔离改变部分逻辑
    def _calculate_extra_rate(self, amount, term):
        extra_rate = 0
        if amount > 10000:
            extra_rate += 0.01
        if term > 5:
            extra_rate += 0.005
        return extra_rate

    def 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 = None

    def add(self, test):
        self.test = test

    def 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 = name

    def run(self, result):
        print(f"Running test: {self.name}")

提取方法(Extract Method)

概念与操作步骤

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

示例代码

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

# 原始复杂方法
def calculate_order_total(products, discount_rate):
    subtotal = 0
    for product in products:
        subtotal += product.price
    discount_amount = subtotal * discount_rate
    return subtotal - discount_amount


# 提取方法后的代码
def calculate_subtotal(products):
    subtotal = 0
    for product in products:
        subtotal += product.price
    return subtotal


def calculate_discount(subtotal, discount_rate):
    return subtotal * discount_rate


def 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 + b


def 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 = width
        self.height = height

    def area(self):
        return self.width * self.height


class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def 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 = price
        self.quantity = quantity

    def calculate_total(self):
        return self.price * self.quantity


# 原始 Order 类
class Order:
    def __init__(self, products):
        self.products = products

    def total_price(self):
        total = 0
        for product in self.products:
            total += product.calculate_total()
        return total


# 转移方法后的代码
class NewProduct:
    def __init__(self, price, quantity):
        self.price = price
        self.quantity = quantity


class NewOrder:
    def __init__(self, products):
        self.products = products

    def calculate_product_total(self, product):
        return product.price * product.quantity

    def total_price(self):
        total = 0
        for 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_flows
        self.discount_rate = discount_rate

    def run(self):
        npv = 0
        for 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 math
        return math.pi * radius * radius
    return 3.14 * radius * radius

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

概念与转换步骤

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

示例代码

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

# 原始类
class Person:
    def __init__(self):
        self.age = None

    def set_age(self, age):
        self.age = age


# 转换后的类
class NewPerson:
    def __init__(self, age):
        self.age = age

总结

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

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

;