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

时间:2025-01-26 16:09:05

写在前面


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

在测试驱动开发(TDD)的实践中,设计模式是解决软件开发常见问题、提升代码质量与可维护性的有力工具。以下详细介绍多种设计模式及其 Python 示例代码。

命令(Command)模式

概念与应用场景

当需调用复杂运算时,命令模式将请求封装为对象,实现发送与执行请求解耦,便于添加日志记录、撤销等功能。

示例代码

# 命令基类
class Command:
    def execute(self):
        pass


# 加法命令
class AddCommand(Command):
    def __init__(self, calculator, operand):
        self.calculator = calculator
        self.operand = operand

    def execute(self):
        self.calculator.add(self.operand)


# 减法命令
class SubtractCommand(Command):
    def __init__(self, calculator, operand):
        self.calculator = calculator
        self.operand = operand

    def execute(self):
        self.calculator.subtract(self.operand)


# 计算器类
class Calculator:
    def __init__(self):
        self.result = 0

    def add(self, num):
        self.result += num

    def subtract(self, num):
        self.result -= num


# 测试代码
import unittest


class TestCommandPattern(unittest.TestCase):
    def test_command_pattern(self):
        calculator = Calculator()
        add_command = AddCommand(calculator, 5)
        subtract_command = SubtractCommand(calculator, 3)

        add_command.execute()
        subtract_command.execute()

        self.assertEqual(calculator.result, 2)


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

值对象(Value Object)模式

概念与特性

值对象模式用于创建可广泛共享、身份不重要且状态不可变的对象,操作返回新对象,避免别名问题。

示例代码

# 货币值对象类
class Money:
    def __init__(self, amount):
        self.amount = amount

    def add(self, other):
        return Money(self.amount + other.amount)

    def subtract(self, other):
        return Money(self.amount - other.amount)

    def __eq__(self, other):
        return isinstance(other, Money) and self.amount == other.amount

    def __hash__(self):
        return hash(self.amount)


# 测试代码
import unittest


class TestValueObjectPattern(unittest.TestCase):
    def test_value_object_pattern(self):
        five = Money(5)
        three = Money(3)

        sum_result = five.add(three)
        self.assertEqual(sum_result.amount, 8)

        difference = five.subtract(three)
        self.assertEqual(difference.amount, 2)


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

Null 对象(Null Object)模式

概念与用途

Null 对象模式创建特定对象表示特殊情况,避免频繁空值检查,使代码简洁优雅。

示例代码

# 日志记录器接口
class Logger:
    def log(self, message):
        pass


# 实际的日志记录器
class ConsoleLogger(Logger):
    def log(self, message):
        print(f"Logging: {message}")


# Null 日志记录器
class NullLogger(Logger):
    def log(self, message):
        pass


# 使用日志记录器的类
class MyClass:
    def __init__(self, logger):
        self.logger = logger

    def do_something(self):
        self.logger.log("Doing something...")


# 测试代码
import unittest


class TestNullObjectPattern(unittest.TestCase):
    def test_null_object_pattern(self):
        with_console_logger = MyClass(ConsoleLogger())
        with_console_logger.do_something()

        with_null_logger = MyClass(NullLogger())
        with_null_logger.do_something()


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

模板方法(Template Method)模式

概念与结构

模板方法模式定义操作的算法骨架,将部分步骤延迟到子类实现,子类可在不改变算法结构时重定义某些步骤。

示例代码

import abc


# 测试用例模板类
class TestCase(metaclass=abc.ABCMeta):
    def run_bare(self):
        self.set_up()
        try:
            self.run_test()
        finally:
            self.tear_down()

    @abc.abstractmethod
    def set_up(self):
        pass

    @abc.abstractmethod
    def run_test(self):
        pass

    @abc.abstractmethod
    def tear_down(self):
        pass


# 具体测试用例类
class MyTestCase(TestCase):
    def __init__(self):
        self.result = 0

    def set_up(self):
        self.result = 0

    def run_test(self):
        self.result = 5 + 3

    def tear_down(self):
        self.result = 0


# 测试代码
import unittest


class TestTemplateMethodPattern(unittest.TestCase):
    def test_template_method_pattern(self):
        test_case = MyTestCase()
        test_case.run_bare()
        self.assertEqual(test_case.result, 8)


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

可插入对象(Pluggable Object)模式

概念与应用

可插入对象模式通过插入不同实现逻辑实现不同工作流,满足多样化需求。

示例代码

# 选择模式接口
class SelectionMode:
    def select(self):
        pass

    def move(self):
        pass

    def unselect(self):
        pass


# 单选模式
class SingleSelection(SelectionMode):
    def select(self):
        print("Single selection")

    def move(self):
        print("Move single selection")

    def unselect(self):
        print("Unselect single selection")


# 多选模式
class MultipleSelection(SelectionMode):
    def select(self):
        print("Multiple selection")

    def move(self):
        print("Move multiple selection")

    def unselect(self):
        print("Unselect multiple selection")


# 选择工具类
class SelectionTool:
    def __init__(self, mode):
        self.mode = mode

    def mouse_down(self):
        self.mode.select()

    def mouse_move(self):
        self.mode.move()

    def mouse_up(self):
        self.mode.unselect()


# 测试代码
import unittest


class TestPluggableObjectPattern(unittest.TestCase):
    def test_pluggable_object_pattern(self):
        single_tool = SelectionTool(SingleSelection())
        single_tool.mouse_down()
        single_tool.mouse_move()
        single_tool.mouse_up()

        multiple_tool = SelectionTool(MultipleSelection())
        multiple_tool.mouse_down()
        multiple_tool.mouse_move()
        multiple_tool.mouse_up()


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

可插入选择器(Pluggable Selector)模式

概念与实现方式

可插入选择器模式通过动态调用不同实例方法,避免过多子类和复杂条件分支,提升代码灵活性与可维护性。

示例代码

# 报表抽象类
class Report:
    def __init__(self, print_message):
        self.print_message = print_message

    def print_report(self):
        pass


# HTML 报表类
class HTMLReport(Report):
    def print_report(self):
        print(f"Printing HTML report: {self.print_message}")


# XML 报表类
class XMLReport(Report):
    def print_report(self):
        print(f"Printing XML report: {self.print_message}")


# 可插入选择器类
class ReportSelector:
    def __init__(self, report):
        self.report = report

    def print(self):
        self.report.print_report()


# 测试代码
import unittest


class TestPluggableSelectorPattern(unittest.TestCase):
    def test_pluggable_selector_pattern(self):
        html_report = HTMLReport("Sample HTML content")
        html_selector = ReportSelector(html_report)
        html_selector.print()

        xml_report = XMLReport("Sample XML content")
        xml_selector = ReportSelector(xml_report)
        xml_selector.print()


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

工厂方法(Factory Method)模式

概念与优势

工厂方法模式将对象创建逻辑封装在方法中,创建对象时可依条件返回不同类型对象,增强代码灵活性与扩展性。

示例代码

# 货币抽象类
class Money:
    def __init__(self, amount):
        self.amount = amount

    def times(self, multiplier):
        pass


# 美元类
class Dollar(Money):
    def times(self, multiplier):
        return Dollar(self.amount * multiplier)


# 货币工厂类
class MoneyFactory:
    @staticmethod
    def dollar(amount):
        return Dollar(amount)


# 测试代码
import unittest


class TestFactoryMethodPattern(unittest.TestCase):
    def test_factory_method_pattern(self):
        five_dollars = MoneyFactory.dollar(5)
        ten_dollars = five_dollars.times(2)

        self.assertEqual(ten_dollars.amount, 10)


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

道具(Imposter)模式

概念与应用场景

道具模式引入与现有对象协议相同但实现不同的对象,将新变化引入计算,避免大量条件逻辑。

示例代码

# 图形抽象类
class Figure:
    def draw(self, brush):
        pass


# 矩形图形类
class RectangleFigure(Figure):
    def __init__(self, x, y, width, height):
        self.x = x
        self.y = y
        self.width = width
        self.height = height

    def draw(self, brush):
        brush.log(f"rectangle {self.x} {self.y} {self.width} {self.height}\n")


# 椭圆形图形类
class OvalFigure(Figure):
    def __init__(self, x, y, width, height):
        self.x = x
        self.y = y
        self.width = width
        self.height = height

    def draw(self, brush):
        brush.log(f"oval {self.x} {self.y} {self.width} {self.height}\n")


# 绘制媒介类
class RecordingMedium:
    def __init__(self):
        self.log_content = ""

    def log(self, message):
        self.log_content += message

    def get_log(self):
        return self.log_content


# 测试代码
import unittest


class TestImposterPattern(unittest.TestCase):
    def test_rectangle_drawing(self):
        drawing = []
        rectangle = RectangleFigure(0, 10, 50, 100)
        brush = RecordingMedium()
        rectangle.draw(brush)
        self.assertEqual(brush.get_log(), "rectangle 0 10 50 100\n")

    def test_oval_drawing(self):
        drawing = []
        oval = OvalFigure(0, 10, 50, 100)
        brush = RecordingMedium()
        oval.draw(brush)
        self.assertEqual(brush.get_log(), "oval 0 10 50 100\n")


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

组合(Composite)模式

概念与用途

组合模式实现对象行为由一组其他对象行为组合而成,将对象组合成树形结构,统一单个与组合对象使用方式。

示例代码

# 交易类
class Transaction:
    def __init__(self, value):
        self.value = value

    def balance(self):
        return self.value


# 账户类
class Account:
    def __init__(self):
        self.transactions = []

    def add_transaction(self, transaction):
        self.transactions.append(transaction)

    def balance(self):
        sum_value = 0
        for transaction in self.transactions:
            sum_value += transaction.balance()
        return sum_value


# 总体账户类,用于组合多个账户
class OverallAccount:
    def __init__(self):
        self.accounts = []

    def add_account(self, account):
        self.accounts.append(account)

    def balance(self):
        sum_balance = 0
        for account in self.accounts:
            sum_balance += account.balance()
        return sum_balance


# 测试代码
import unittest


class TestCompositePattern(unittest.TestCase):
    def test_account_balance(self):
        account = Account()
        transaction1 = Transaction(100)
        transaction2 = Transaction(200)
        account.add_transaction(transaction1)
        account.add_transaction(transaction2)
        self.assertEqual(account.balance(), 300)

    def test_overall_account_balance(self):
        overall_account = OverallAccount()
        account1 = Account()
        account2 = Account()
        transaction1 = Transaction(100)
        transaction2 = Transaction(200)
        account1.add_transaction(transaction1)
        account2.add_transaction(transaction2)
        overall_account.add_account(account1)
        overall_account.add_account(account2)
        self.assertEqual(overall_account.balance(), 300)


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

收集参数(Collecting Parameter)模式

概念与实现方式

收集参数模式通过添加参数收集操作中分散于多个对象的结果,处理复杂期望结果时使代码结构清晰。

示例代码

# 表达式抽象类
class Expression:
    def to_string(self, writer):
        pass


# 加法表达式类
class Sum(Expression):
    def __init__(self, augend, addend):
        self.augend = augend
        self.addend = addend

    def to_string(self, writer):
        writer.println("(")
        writer.indent()
        self.augend.to_string(writer)
        writer.print(" + ")
        self.addend.to_string(writer)
        writer.dedent()
        writer.println(")")