当前位置: 首页 > article >正文

第26章 测试驱动开发(TDD)模式详解与 Python 实践

写在前面


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

一、TDD 关键模式解析

在测试驱动开发的实践中,多种模式为开发者提供了不同维度的指导,助力高效、高质量的软件开发。

单步测试(One - Step Test)

单步测试旨在从测试列表中选取具有指导意义且可实现的测试,每个测试都应是向最终目标迈进的一步。例如,在一个简易计算器程序的开发中,测试列表可能包含加(Plus)、减(Minus)、乘(Times)、除(Divide)等测试项。

import unittest

class Calculator:
    def add(self, a, b):
        return a + b

    def subtract(self, a, b):
        return a - b

    def multiply(self, a, b):
        return a * b

    def divide(self, a, b):
        if b == 0:
            raise ZeroDivisionError
        return a / b

class TestCalculator(unittest.TestCase):
    def test_add(self):
        calculator = Calculator()
        result = calculator.add(3, 5)
        self.assertEqual(result, 8)

    def test_subtract(self):
        calculator = Calculator()
        result = calculator.subtract(10, 4)
        self.assertEqual(result, 6)

    def test_multiply(self):
        calculator = Calculator()
        result = calculator.multiply(4, 6)
        self.assertEqual(result, 24)

    def test_divide(self):
        calculator = Calculator()
        result = calculator.divide(15, 3)
        self.assertEqual(result, 5)
        with self.assertRaises(ZeroDivisionError):
            calculator.divide(10, 0)

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

在这个示例中,每个测试方法都是单步测试的体现,分别验证计算器的一项基本运算功能。开发者可根据自身经验和当前进度,从测试列表中选择合适的测试进行实现,若列表中没有合适的测试,也可添加新的测试来逼近目标。

先导测试(Starter Test)

先导测试强调从一组不做任何事情的操作开始,对于新操作,先明确其所属部分,再编写测试代码。以开发一个图形绘制程序为例,假设要绘制多边形,先导测试可以从输入与输出相同的简单情况开始。

import unittest

class PolygonReducer:
    def __init__(self, polygon):
        self.polygon = polygon

    def result(self):
        return self.polygon

class TestPolygonReducer(unittest.TestCase):
    def test_starter(self):
        polygon = [ (1,1), (2,2), (3,3) ]
        reducer = PolygonReducer(polygon)
        result = reducer.result()
        self.assertEqual(result, polygon)

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

此测试创建了一个简单的多边形对象,并验证了 PolygonReducer 类在初始状态下的输出与输入一致。先导测试适用于确定具有指导意义的起始测试,帮助开发者快速进入开发状态,后续再逐步完成其他测试。

说明性测试(Explanation Test)

在团队开发中,说明性测试可扩展自动化测试的用途。例如,在一个团队协作的项目中,开发一个用户权限管理系统。

class User:
    def __init__(self, role):
        self.role = role

    def has_permission(self, permission):
        if self.role == "admin":
            return True
        elif self.role == "user" and permission == "read":
            return True
        return False

class TestUserPermissions(unittest.TestCase):
    def test_permission_explanation(self):
        admin_user = User("admin")
        self.assertEqual(admin_user.has_permission("write"), True)
        regular_user = User("user")
        self.assertEqual(regular_user.has_permission("write"), False)
        self.assertEqual(regular_user.has_permission("read"), True)

        # 为测试添加说明
        """
        对于管理员用户(role = "admin"),应具有所有权限,如 write 权限测试为 True;
        对于普通用户(role = "user"),仅具有 read 权限,write 权限测试为 False,read 权限测试为 True。
        """

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

在上述代码中,不仅对用户权限判断功能进行了测试,还在测试方法中添加了详细的说明,方便团队成员理解测试的意图和预期结果,促进团队对测试和代码的理解,提高测试的质量和可维护性。

学习测试(Learning Test)

当使用外来软件的新功能时,学习测试可用于验证 API 是否按预期工作。以使用 Python 的 sqlite3 模块进行数据库操作学习为例:

import unittest
import sqlite3

class TestSQLiteLearning(unittest.TestCase):
    def setUp(self):
        self.conn = sqlite3.connect(':memory:')
        self.cursor = self.conn.cursor()

    def tearDown(self):
        self.conn.close()

    def test_create_table(self):
        self.cursor.execute('CREATE TABLE users (id INT, name TEXT)')
        self.conn.commit()

        self.cursor.execute('SELECT name FROM sqlite_master WHERE type="table" AND name="users"')
        result = self.cursor.fetchone()
        self.assertEqual(result[0], "users")

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

在这个测试中,通过编写测试代码来学习 sqlite3 模块创建表的功能。先在 setUp 方法中建立数据库连接,在 test_create_table 方法中执行创建表操作并验证表是否创建成功,最后在 tearDown 方法中关闭数据库连接。通过学习测试,开发者能更好地理解和掌握新软件功能的使用方法。

另外的测试(Another Test)

在技术讨论中,当出现离题想法时,可将其添加到列表中,保持讨论主题的集中。在实际开发中,这意味着将暂时不相关的功能或需求记录下来,专注于当前的开发任务。例如,在开发一个电商网站的过程中,讨论支付功能时,有人提出添加积分兑换功能的想法,可将其记录在列表中,先完成支付功能的开发和测试。

# 假设当前专注于支付功能测试
class PaymentTest(unittest.TestCase):
    def test_payment(self):
        # 模拟支付操作
        result = self.make_payment(100)
        self.assertEqual(isinstance(result, bool), True)

    def make_payment(self, amount):
        # 简单模拟支付返回 True 或 False
        return True if amount > 0 else False

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

在上述代码中,专注于支付功能的测试,将其他想法暂放一边,保证开发进度和代码质量。

回归测试(Regression Test)

当发现软件缺陷时,编写简单且相应的回归测试,确保缺陷修复且不会引入新问题。例如,在一个字符串处理函数中,原本的函数用于将字符串转换为大写,但发现对于空字符串会出现错误。

import unittest

def to_uppercase(s):
    if not isinstance(s, str):
        raise TypeError
    return s.upper() if s else ""

class TestToUppercase(unittest.TestCase):
    def test_regression(self):
        self.assertEqual(to_uppercase("hello"), "HELLO")
        self.assertEqual(to_uppercase(""), "")
        with self.assertRaises(TypeError):
            to_uppercase(123)

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

在修复空字符串处理的问题后,编写回归测试,不仅验证了正常字符串的转换功能,还确保了空字符串和非字符串输入的处理都符合预期,避免了修复缺陷过程中可能引入的新问题。

休息(Break)

当开发者感到疲倦或不知所措时,休息是调整状态的有效方式。例如,在长时间开发一个复杂的人工智能模型时,连续工作数小时后思维变得迟钝,此时离开电脑,喝杯水、散散步,可能会突然想到新的思路来解决之前遇到的问题。从代码层面看,合理安排休息时间有助于保持良好的编码状态,提高代码质量。

重新开始(Do Over)

当感到困惑且经过休息仍无头绪时,可考虑扔掉已有代码重新开始。例如,在开发一个游戏引擎的过程中,由于代码结构逐渐变得混乱,导致新功能开发困难重重,此时可选择重新设计和编写代码。在结对编程中,更换搭档也是促进重新开始的有效方法,新的视角和思路可能会带来更好的解决方案。

工作环境优化

为施行 TDD,舒适的工作环境很重要。比如,选择一把舒适的椅子,保证在长时间编程时的身体舒适度,有助于提高工作效率和专注度。在硬件分配上,将性能最佳的机器用于共享开发,而把低性能的机器用于个人电子邮件服务和上网冲浪等非关键任务,以确保开发工作的顺畅进行。

二、TDD 模式综合应用与思考

这些 TDD 模式相互关联且各有侧重,单步测试和先导测试为开发提供了具体的测试选择和起始思路,帮助开发者逐步推进功能实现;说明性测试和学习测试分别从团队协作和新技术学习的角度,提升了测试的价值和开发者对新功能的掌握程度;另外的测试有助于保持开发的专注性;回归测试保障了代码的稳定性;而休息和重新开始则关注开发者的状态调整,合理的工作环境优化也为 TDD 的顺利实施提供了支持。

在实际项目中,开发者应根据项目的特点、团队的协作方式以及自身的状态,灵活运用这些模式。例如,在开发初期可更多地依赖先导测试和单步测试来搭建功能框架;在团队协作中充分发挥说明性测试的作用;面对新技术或外来软件时,利用学习测试快速上手;同时,时刻关注代码的稳定性,通过回归测试及时发现和解决问题,并合理安排休息和调整开发策略,以实现高效、高质量的测试驱动开发。


http://www.kler.cn/a/521236.html

相关文章:

  • 【MySQL】悲观锁和乐观锁的原理和应用场景
  • 中间件安全
  • 小程序-视图与逻辑
  • Java坦克大战
  • pytorch逻辑回归实现垃圾邮件检测
  • 腾讯云开发提供免费GPU服务
  • K8s运维管理平台 - xkube体验:功能较多
  • [HOT 100] 0015. 三数之和
  • 代码审查中的自动化与AI应用
  • 2025寒假作业
  • C#,入门教程(09)——运算符的基础知识
  • 参照和谐色调为PPT图形设置统一格式的要点
  • CRMEB部署的一些修改
  • 【QT】 控件 -- 显示类
  • Android-okhttp详解
  • Spark Streaming编程基础
  • 基于Java(SSM)+MySQL实现的客户管理系统
  • 3097. 或值至少为 K 的最短子数组 II
  • Direct Preference Optimization (DPO): 一种无需强化学习的语言模型偏好优化方法
  • FPGA同步复位和异步复位
  • Day37:添加元素到列表中
  • 缓存策略通用分布式缓存解决方案
  • 基于微信小程序的健身管理系统设计与实现(LW+源码+讲解)
  • 通过配置核查,CentOS操作系统当前无多余的、过期的账户;但CentOS操作系统存在共享账户r***t
  • 如何实现事件响应功能
  • 三. Redis 基本指令(Redis 快速入门-03)