寫在前面
這本書是我們老板推薦過的,我在《價值心法》的推薦書單里也看到了它。用了一段時間 Cursor 軟件后,我突然思考,對于測試開發工程師來說,什么才更有價值呢?如何讓 AI 工具更好地輔助自己寫代碼,或許優質的單元測試是一個切入點。 就我個人而言,這本書確實很有幫助。第一次讀的時候,很多細節我都不太懂,但將書中內容應用到工作中后,我受益匪淺。比如面對一些讓人抓狂的代碼設計時,書里的方法能讓我逐步深入理解代碼的邏輯與設計。 作為一名測試開發工程師,我想把學習這本書的經驗分享給大家,希望能給大家帶來幫助。因為現在工作中大多使用 Python 代碼,所以我把書中JAVA案例都用 Python 代碼進行了改寫 。
在測試驅動開發(TDD)的實踐中,設計模式是解決軟件開發常見問題、提升代碼質量與可維護性的有力工具。以下詳細介紹多種設計模式及其 Python 示例代碼。
命令(Command)模式
概念與應用場景
當需調用復雜運算時,命令模式將請求封裝為對象,實現發送與執行請求解耦,便于添加日志記錄、撤銷等功能。
示例代碼
# 命令基類
class Command:def execute(self):pass# 加法命令
class AddCommand(Command):def __init__(self, calculator, operand):self.calculator = calculatorself.operand = operanddef execute(self):self.calculator.add(self.operand)# 減法命令
class SubtractCommand(Command):def __init__(self, calculator, operand):self.calculator = calculatorself.operand = operanddef execute(self):self.calculator.subtract(self.operand)# 計算器類
class Calculator:def __init__(self):self.result = 0def add(self, num):self.result += numdef subtract(self, num):self.result -= num# 測試代碼
import unittestclass 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 = amountdef 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.amountdef __hash__(self):return hash(self.amount)# 測試代碼
import unittestclass 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 = loggerdef do_something(self):self.logger.log("Doing something...")# 測試代碼
import unittestclass 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.abstractmethoddef set_up(self):pass@abc.abstractmethoddef run_test(self):pass@abc.abstractmethoddef tear_down(self):pass# 具體測試用例類
class MyTestCase(TestCase):def __init__(self):self.result = 0def set_up(self):self.result = 0def run_test(self):self.result = 5 + 3def tear_down(self):self.result = 0# 測試代碼
import unittestclass 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):passdef move(self):passdef 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 = modedef mouse_down(self):self.mode.select()def mouse_move(self):self.mode.move()def mouse_up(self):self.mode.unselect()# 測試代碼
import unittestclass 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_messagedef 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 = reportdef print(self):self.report.print_report()# 測試代碼
import unittestclass 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 = amountdef times(self, multiplier):pass# 美元類
class Dollar(Money):def times(self, multiplier):return Dollar(self.amount * multiplier)# 貨幣工廠類
class MoneyFactory:@staticmethoddef dollar(amount):return Dollar(amount)# 測試代碼
import unittestclass 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 = xself.y = yself.width = widthself.height = heightdef 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 = xself.y = yself.width = widthself.height = heightdef 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 += messagedef get_log(self):return self.log_content# 測試代碼
import unittestclass 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 = valuedef 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 = 0for 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 = 0for account in self.accounts:sum_balance += account.balance()return sum_balance# 測試代碼
import unittestclass 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 = augendself.addend = addenddef to_string(self, writer):writer.println("(")writer.indent()self.augend.to_string(writer)writer.print(" + ")self.addend.to_string(writer)writer.dedent()writer.println(")")# 貨幣類
class Money(Expression):def __init__(self, amount, currency):self.amount = amountself.currency = currencydef to_string(self, writer):writer.print(f"{self.amount} {self.currency}")# 縮進流類,用于格式化輸出
class IndentingStream:def __init__(self):self.lines = []self.indent_level = 0def indent(self):self.indent_level += 1def dedent(self):self.indent_level -= 1def print(self, text):self.lines.append(" " * self.indent_level * 4 + text)def println(self, text=""):self.print(text)self.lines.append("")def contents(self):return "\n".join(self.lines)# 測試代碼
import unittestclass TestCollectingParameterPattern(unittest.TestCase):def test_expression_printing(self):sum_expr = Sum(Money(5, "USD"), Money(7, "CHF"))writer = IndentingStream()sum_expr.to_string(writer)self.assertEqual(writer.contents(), "( \n 5 USD + \n 7 CHF \n)")if __name__ == '__main__':unittest.main()
單例模式(Singleton)
概念與應用
單例模式確保類只有一個實例,提供全局訪問點,在無全局變量機制語言中實現類似功能。
示例代碼
class Singleton:_instance = Nonedef __new__(cls):if cls._instance is None:cls._instance = super().__new__(cls)return cls._instance# 測試代碼
import unittestclass TestSingletonPattern(unittest.TestCase):def test_singleton(self):instance1 = Singleton()instance2 = Singleton()self.assertEqual(instance1, instance2)if __name__ == '__main__':unittest.main()
總結
上述介紹的設計模式在TDD中各有其獨特價值。命令模式封裝請求;值對象模式保證對象狀態穩定;Null對象模式簡化特殊情況處理;模板方法模式定義算法骨架;可插入對象和選擇器模式提升靈活性;工廠方法模式優化對象創建;道具模式引入變化;組合模式處理對象組合;收集參數模式管理復雜結果;單例模式控制全局實例。
在實際TDD項目中,開發者應依據具體需求靈活選用設計模式,以優化代碼結構,增強代碼的可維護性、擴展性與復用性,開發出更優質的軟件系統。