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

第32章 测试驱动开发(TDD)的原理、实践、关联与争议(Python 版)

写在前面


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

一、TDD 关键问题深入剖析

在测试驱动开发的应用场景中,一系列关键问题的思考对于开发者深度掌握 TDD 至关重要,有助于显著提升软件开发的质量与效率。

迭代步长的抉择

迭代步长的确定包含两个关键维度:单个测试的覆盖范畴以及重构过程中伴随的中间步骤多寡。以 Python 实现的学生成绩管理系统为例,较大的迭代步长可能促成一个综合性测试,涵盖成绩录入、查询与统计等多项功能。

# 较大迭代步长示例测试
import unittest


class TestStudentGradeSystem(unittest.TestCase):
    def test_full_functionality(self):
        student_grades = {"Alice": 85, "Bob": 90}
        self.assertEqual(student_grades.get("Alice"), 85)
        total = sum(student_grades.values())
        average = total / len(student_grades)
        self.assertEqual(isinstance(average, float), True)


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

然而,TDD 的践行者往往更青睐较小的迭代步长,例如率先编写专注于成绩录入功能的单项测试。

# 较小迭代步长示例测试 - 成绩录入
import unittest


class TestStudentGradeEntry(unittest.TestCase):
    def test_grade_entry(self):
        student_grades = {}
        student_grades["Charlie"] = 78
        self.assertEqual(student_grades.get("Charlie"), 78)


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

较小的迭代步长能够实现对开发进程的精细化把控,及时察觉潜在问题,并且在手动重构时降低出错几率。诸如 Python 中的 autopep8 等自动化重构工具的应用,极大地加速了重构进程,激励开发者进行更多的尝试与探索。

测试范围的界定

对于“哪些内容可以不必测试”这一问题,简洁的回答是“编写测试直至从恐惧转变为厌倦”,但具体的判断仍需开发者依据实际情况进行。一般而言,条件语句、循环语句、操作语句以及多态性等通常属于需要编写测试的范畴。以一个图形绘制的 Python 程序为例,对多态性的测试示例如下:

import unittest


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


class TestShapePolymorphism(unittest.TestCase):
    def test_polymorphism(self):
        shapes = [Rectangle(5, 3), Circle(4)]
        for shape in shapes:
            self.assertEqual(isinstance(shape.area(), (int, float)), True)


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

不过,通常只需对自己编写的相关代码进行测试,对于外部代码,除非存在合理的怀疑理由,否则可以不予测试。

测试有效性的判断

测试宛如煤矿中的金丝雀,能够敏锐地揭示设计层面的致命缺陷。诸如冗长繁杂的设置代码、重复冗余的设置代码、耗时冗长的测试以及脆弱易损的测试等,均属于测试不当的表现形式。以电商购物车系统为例,若在测试设置过程中需要初始化大量的商品和用户信息,这便属于冗长的设置代码。理想的测试应当具备简洁高效的特质,能够精准地验证核心功能,如下所示:

import unittest


class ShoppingCart:
    def __init__(self):
        self.items = []

    def add_item(self, item):
        self.items.append(item)

    def total_price(self):
        total = 0
        for item in self.items:
            total += item.price
        return total


class Item:
    def __init__(self, price):
        self.price = price


class TestShoppingCart(unittest.TestCase):
    def test_cart_functionality(self):
        cart = ShoppingCart()
        item1 = Item(10)
        item2 = Item(20)
        cart.add_item(item1)
        cart.add_item(item2)
        self.assertEqual(cart.total_price(), 30)


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

上述测试聚焦于购物车的核心功能,设置代码简洁明了,能够有效地验证商品添加和总价计算功能。

框架形成的驱动机制

TDD 秉持“编码为未来考量,设计为当下服务”的理念。以 Web 应用框架的开发为例,在初始阶段实现用户注册功能时,代码呈现出简单直接的特点。

def register_user(username, password):
    print(f"Registering user: {username} with password: {password}")
    return True

随后,在实现登录功能的过程中,发现注册与登录均涉及用户信息验证环节,于是将这部分逻辑提取为公共方法。

def validate_user(username, password):
    if username and password:
        return True
    return False


def register_user_improved(username, password):
    if validate_user(username, password):
        print(f"Registering user: {username} with password: {password}")
        return True
    return False


def login_user(username, password):
    if validate_user(username, password):
        print(f"User {username} logged in successfully")
        return True
    return False

随着功能的不断拓展,持续提取公共逻辑,逐步促成框架雏形的形成,进而满足“开放 - 封闭”原则,使得系统在面对扩展需求时具备开放性,在应对修改操作时具备封闭性。

反馈量的权衡

以判断三角形类型的问题为例,给定表示三角形三边的三个整数,依据不同的条件返回相应的结果。

import unittest


def triangle_type(a, b, c):
    if a + b <= c or a + c <= b or b + c <= a:
        raise Exception("无法组成三角形")
    elif a == b == c:
        return 1
    elif a == b or a == c or b == c:
        return 2
    else:
        return 3


class TestTriangleType(unittest.TestCase):
    def test_equilateral_triangle(self):
        self.assertEqual(triangle_type(3, 3, 3), 1)

    def test_isosceles_triangle(self):
        self.assertEqual(triangle_type(3, 3, 4), 2)

    def test_scalene_triangle(self):
        self.assertEqual(triangle_type(3, 4, 5), 3)

    def test_invalid_triangle(self):
        with self.assertRaises(Exception):
            triangle_type(1, 1, 3)


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

在这个过程中,编写测试的数量取决于对系统功能的信心程度、代码路径的覆盖情况等因素,需要在过多测试导致的冗余和过少测试引发的风险之间寻求平衡。

编程语言与环境的影响

不同的编程语言和环境对 TDD 的实施效果存在显著影响。在 Python 语言环境中,丰富的测试框架,如 unittestpytest 等,极大地降低了测试编写的难度。以 pytest 编写一个简单的测试示例如下:

def add_numbers(a, b):
    return a + b


def test_add_numbers():
    result = add_numbers(3, 5)
    assert result == 8

然而,在一些测试、编译、运行或重构过程较为困难的编程语言和环境中,开发者可能会倾向于采用更大步长的迭代方式,每个测试的覆盖范围更广,同时重构的次数相对较少。而在频繁应用 TDD 的环境中,开发者则更易于进行各种实验和尝试新的解决方案。

大型系统开发的适用性

TDD 在大型系统开发中同样具有适用性,例如 LifeWare 系统就是一个成功的案例。在使用 Python 进行企业级资源管理系统开发时,可以通过划分模块的方式,分别为各个模块编写测试。以员工信息管理模块为例:

import unittest


class Employee:
    def __init__(self, name, id):
        self.name = name
        self.id = id


class EmployeeManagement:
    def __init__(self):
        self.employees = []

    def add_employee(self, employee):
        self.employees.append(employee)

    def get_employee_by_id(self, id):
        for employee in self.employees:
            if employee.id == id:
                return employee
        return None


class TestEmployeeManagement(unittest.TestCase):
    def test_employee_management(self):
        manager = EmployeeManagement()
        employee1 = Employee("John", 1)
        manager.add_employee(employee1)
        retrieved_employee = manager.get_employee_by_id(1)
        self.assertEqual(retrieved_employee.name, "John")


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

通过逐步细化模块并编写相应的测试,能够有条不紊地构建起大型系统,确保系统的稳定性和可维护性。

应用级测试驱动开发的可行性

小规模测试固然具有一定的作用,但应用级测试驱动开发也面临着一些问题。例如,在开发企业级应用的财务报表生成功能时,编写应用级测试需要模拟数据库连接、用户权限等复杂的环境。

import unittest
import sqlite3


class DatabaseMock:
    def __init__(self):
        self.connection = sqlite3.connect(':memory:')
        self.cursor = self.connection.cursor()

    def execute_query(self, query):
        self.cursor.execute(query)
        return self.cursor.fetchall()


class FinancialReportGenerator:
    def __init__(self, db):
        self.db = db

    def generate_report(self):
        data = self.db.execute_query("SELECT * FROM financial_data")
        return f"Generated report with data: {data}"


class TestFinancialReportGenerator(unittest.TestCase):
    def test_report_generation(self):
        db_mock = DatabaseMock()
        generator = FinancialReportGenerator(db_mock)
        report = generator.generate_report()
        self.assertEqual(isinstance(report, str), True)


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

此外,还会面临组织层面的挑战,如团队成员之间需要协作编写测试,以及责任转换等问题。

中途转换到 TDD 的策略

如果已经拥有大量代码,想要转换到 TDD 开发模式,最大的问题在于未考虑测试编写的代码通常可测性较差。此时,可以先对代码进行评估,识别出关键的功能和模块。以一个遗留的文件处理系统为例,可以先挑选文件读取和解析功能模块进行测试编写。

import unittest


def read_file(file_path):
    try:
        with open(file_path, 'r') as file:
            return file.read()
    except FileNotFoundError:
        return ""


def parse_file_content(content):
    return content.splitlines()


class TestFileProcessing(unittest.TestCase):
    def test_file_reading_and_parsing(self):
        content = read_file("test.txt")
        parsed_content = parse_file_content(content)
        self.assertEqual(isinstance(parsed_content, list), True)


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

然后逐步对代码进行重构,使其更具可测性,并补充更多的测试,从而实现向 TDD 的平稳转换。

初始条件对 TDD 的影响

TDD 在一定程度上容易受到初始条件的影响,例如测试的顺序可能会对开发流程产生影响。在 Python 测试中,当测试之间存在依赖关系时,不同的测试顺序可能会导致不同的结果。

import unittest


class TestDependency(unittest.TestCase):
    data = []

    def test_append_data(self):
        self.data.append(1)
        self.assertEqual(len(self.data), 1)

    def test_read_data(self):
        self.assertEqual(self.data[0], 1)


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

因此,应该尽量避免测试之间的强依赖关系,确保测试的独立性和可重复性。

TDD 与设计模式的关联

在 TDD 的实施过程中,会自然地产生一些类似于专家行为的基本规则,这些规则与设计模式之间存在着紧密的联系。例如,在开发过程中,为了解决重复代码的问题,可能会运用工厂模式来创建对象。以创建图形对象为例:

class Shape:
    def draw(self):
        pass


class Circle(Shape):
    def draw(self):
        print("Drawing a circle")


class Rectangle(Shape):
    def draw(self):
        print("Drawing a rectangle")


class ShapeFactory:
    @staticmethod
    def create_shape(shape_type):
        if shape_type == "circle":
            return Circle()
        elif shape_type == "rectangle":
            return Rectangle()
        return None


import unittest


class TestShapeFactory(unittest.TestCase):
    def test_shape_creation(self):
        circle = ShapeFactory.create_shape("circle")
        self.assertEqual(isinstance(circle, Circle), True)
        rectangle = ShapeFactory.create_shape("rectangle")
        self.assertEqual(isinstance(rectangle, Rectangle), True)


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

TDD 能够推动代码的不断演进和优化,促使开发者运用设计模式来提高代码的可维护性、可扩展性和复用性。

二、TDD 有效性的多维度解析

TDD 能够有力地协助团队构建出具有高内聚、低耦合特性的系统,有效降低缺陷率和维护成本。其中一个重要原因在于缺陷数量的减少,因为越早发现并修复缺陷,所付出的代价就越小。例如,在在线电商系统的商品管理模块开发中,采用 TDD 方法,先编写测试用例来验证商品的添加、删除和修改功能。

import unittest

class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

class ProductManager:
    def __init__(self):
        self.products = []

    def add_product(self, product):
        self.products.append(product)

    def remove_product(self, product):
        if product in self.products:
            self.products.remove(product)

    def update_product(self, product, new_name=None, new_price=None):
        if product in self.products:
            if new_name:
                product.name = new_name
            if new_price:
                product.price = new_price

class TestProductManager(unittest.TestCase):
    def test_product_operations(self):
        product_manager = ProductManager()
        product1 = Product("Shirt", 29.99)

        product_manager.add_product(product1)
        self.assertEqual(len(product_manager.products), 1)

        product_manager.remove_product(product1)
        self.assertEqual(len(product_manager.products), 0)

        product_manager.add_product(product1)
        product_manager.update_product(product1, new_name="New Shirt", new_price=39.99)
        self.assertEqual(product1.name, "New Shirt")
        self.assertEqual(product1.price, 39.99)

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

通过先测试后开发的方式,能够在开发的早期阶段就发现并解决潜在的问题,减少后期因缺陷而导致的大量修改工作,进而降低维护成本。同时,在开发过程中,开发者只需要关注当前的测试内容,压力相对较小,团队成员之间的协作更加积极,客户对系统也会更加有信心。

TDD 的另一个显著优势在于它能够缩短设计构思之间的反馈周期。例如,在开发一个 API 时,每完成一个小功能,就编写对应的测试用例并运行。以一个简单的用户认证 API 为例:

import unittest

class UserAuthenticator:
    def authenticate(self, username, password):
        if username == "testuser" and password == "testpass":
            return True
        return False

class TestUserAuthenticator(unittest.TestCase):
    def test_authentication(self):
        authenticator = UserAuthenticator()
        result = authenticator.authenticate("testuser", "testpass")
        self.assertEqual(result, True)

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

在这个过程中,开发者可以迅速验证自己的设计想法是否正确,如果测试失败,可以立即发现问题并进行调整;如果测试通过,则可以继续进行下一个功能的开发。这种及时的反馈机制避免了在整个设计完成后才进行测试所导致的长时间等待反馈的问题,大大提高了开发效率。

此外,如果为每个功能特性编写单元测试,并在每一步都使用重构来简化代码,只有当所有单元测试都运行通过时,才逐个添加功能特性,这样会产生一种类似于“吸引子”的效果。以开发一个图形绘制库为例,先编写绘制简单图形(如圆形、矩形)的测试用例和代码,然后通过重构来优化代码结构,接着再逐步添加绘制复杂图形(如多边形组合图形)的功能。在这个过程中,通过不断的测试和重构,代码的质量和可读性会不断提高,代码会随着时间的推移变得更好,而不是变得更糟。

三、TDD 相关术语精准阐释

开发(Development)

在软件开发领域,传统的阶段性思维方式往往难以在被时间分隔的决策之间获得及时的反馈。而测试驱动开发中的开发概念,涵盖了从分析、逻辑设计、物理设计、实现、测试、复审、集成到部署的一系列错综复杂的活动。以开发一款移动应用程序为例,首先要进行详尽的需求分析,明确应用的各项功能与特性,例如社交互动、数据存储、用户界面交互等方面的具体要求;接着开展逻辑设计,规划应用的整体架构,包括模块划分、数据流程以及各部分之间的交互方式;随后进行物理设计,关注界面的布局、色彩搭配以及用户操作的便捷性等;在实现阶段,遵循 TDD 的流程,先编写测试用例来验证各个功能模块的正确性,比如针对用户注册功能,编写测试用例检查用户名的唯一性、密码的强度要求等是否满足;然后编写代码实现相应功能;完成实现后,进行严格的测试和复审工作,及时发现并修复潜在的问题;最后进行集成和部署,将应用发布到应用商店,供用户下载和使用。

驱动(Driven)

TDD 又被称作“测试优先编程”,与“测试滞后”(即先编写程序,完成后再进行测试)形成鲜明对比。在不采用 TDD 的情况下,开发过程可能依赖推断或需求来推进。例如,在开发一个企业级的项目管理系统时,如果不使用 TDD,可能会先依据需求文档进行系统架构设计和代码编写,在完成大部分功能后才进行测试。此时,若发现问题,可能需要对已有的代码进行大规模的修改,不仅耗费时间和精力,还可能引入新的问题。而采用 TDD 时,会先针对项目管理系统中的任务创建、任务分配、进度跟踪等功能分别编写测试用例,然后根据测试用例的要求编写代码,确保代码能够满足各项功能需求,从开发的起始阶段就保障代码的质量和可靠性。

测试(Test)

TDD 中的测试具有自动化、具体且明确的特点。通过编写自动化测试用例,能够实现一键运行所有测试。例如,在一个基于 Python 的数据分析项目中,使用 unittest 框架编写自动化测试用例来验证数据读取、数据清洗和数据分析功能的正确性。假设我们有一个数据读取模块,用于从 CSV 文件中读取数据:

import unittest
import csv

def read_csv_data(file_path):
    data = []
    try:
        with open(file_path, 'r', encoding='utf - 8') as file:
            reader = csv.reader(file)
            for row in reader:
                data.append(row)
        return data
    except FileNotFoundError:
        return []

class TestDataReading(unittest.TestCase):
    def test_read_csv_data(self):
        file_path = 'test_data.csv'
        result = read_csv_data(file_path)
        self.assertEqual(isinstance(result, list), True)

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

这样的自动化测试能够快速、准确地验证代码的正确性,提高开发效率,并且可以在每次代码修改后自动运行,及时发现因代码变更而可能引入的问题。

四、TDD 与极限编程实践的紧密关联

结对编程(Pairing)

在 TDD 中,编写的测试用例成为结对编程时绝佳的讨论素材。当结对的双方在处理问题时意见不一致,通过对测试用例和代码逻辑的深入讨论,可以达成共识。例如,在开发一个机器学习算法的项目中,结对的两位开发者对于算法的优化方向产生了分歧。通过查看和讨论为该算法编写的测试用例,分析不同优化方案对测试结果的影响,最终确定了更优的算法实现方式。此外,结对编程还能在一方疲惫时,另一方及时接手继续工作,确保开发工作的连续性,进一步增强了 TDD 的实施效果。

精力充沛地工作(Work fresh)

该原则倡导在精力充沛时进行工作,当感到疲倦时及时停下。例如,在开发一个复杂的人工智能模型时,开发者可能会遇到难以使某个测试通过的情况,经过长时间的尝试仍无法解决。此时,按照这一原则,应该选择休息。经过适当的休息后,以全新的思维和精力重新投入工作,往往能够找到解决问题的新思路。曾经在开发一个图像识别模型时,开发者在尝试多种方法都无法使模型的准确率达到预期后,选择休息。重新开始工作后,通过调整模型的参数和结构,成功解决了问题,提高了模型的性能。

持续集成(Continuous integration)

测试在持续集成中扮演着至关重要的角色,它促使开发者更频繁地进行集成操作。通过不断地将代码签入版本控制系统,并自动运行测试用例,能够及时发现和解决集成过程中出现的问题。例如,在一个多人协作的开源项目中,每个开发者每天都会将自己的代码提交到代码库中。每次提交后,系统会自动触发测试流程,如果测试不通过,开发者可以迅速定位问题并进行修复,避免问题在项目后期集成时集中爆发,确保项目的顺利推进。

简单设计(Simple design)

通过仅依据测试需求进行编码,并去除所有重复部分,能够获得完全符合当前需求的简洁设计。例如,在开发一个小型的图书馆管理系统时,根据测试用例的要求,先实现图书的借阅、归还和查询功能,确保代码简洁明了,满足当前的业务需求。随着业务的发展,如增加读者预约功能、图书分类管理功能等,再逐步扩展代码,同时始终保持代码的简洁性和可维护性,避免过度设计带来的复杂性。

重构(Refactoring)

测试为开发者进行更大规模的重构提供了信心,确保重构过程中系统的行为不发生改变。例如,在一个已经运行了一段时间的 Web 应用程序中,随着功能的不断增加,代码变得愈发复杂和臃肿。通过编写全面的测试用例,确保在重构过程中应用的各项功能不受影响。然后对代码进行重构,如提取重复的代码片段为公共函数、优化数据库访问代码等,提高代码的可读性和可维护性,同时也使得后续的测试编写更加容易。

持续交付(Continuous delivery)

如果 TDD 能够提高系统的平均无故障时间,那么就可以更频繁地将代码部署到生产环境中。例如,在一个在线教育平台中,通过 TDD 确保代码的高质量,每天晚上自动将经过测试的代码部署到生产服务器上,为用户提供最新的功能和修复的问题。这种持续交付的方式能够及时响应用户的需求,提高用户满意度,同时也要求开发团队具备高效的测试和部署流程。

五、关于 TDD 应用范围的争议

Darach Ennis 对扩展 TDD 的应用范围提出了一系列挑战,其观点引发了广泛的思考和讨论。

图形用户界面测试难题

他认为在图形用户界面(如 Swing、CGI、JSP/Servlets/Struts 等)的开发中,难以使用 TDD 进行自动化测试。由于图形用户界面具有高度的复杂性和交互性,涉及到众多的用户操作和界面状态变化,如按钮点击、文本输入、窗口切换、菜单操作等。以一个使用 Swing 开发的桌面应用程序为例,模拟用户在界面上的各种操作并编写相应的自动化测试用例是一项极具挑战性的任务。不同的操作系统、屏幕分辨率和用户交互方式都可能对界面的显示和操作产生影响,使得自动化测试难以覆盖所有的情况,从而限制了 TDD 在图形用户界面开发中的应用。

分布式对象测试困境

对于分布式对象(如 RPC 和 Messaging style,或者 CORBA/EJB 和 JMS 等)的自动化单元测试,TDD 也面临着诸多困难。分布式对象的运行依赖于网络通信和远程调用,其运行环境和状态受到网络延迟、连接中断、分布式节点故障等多种因素的影响,难以进行精确的控制和模拟。例如,在一个基于 RPC 的分布式系统中,测试一个远程方法的调用时,需要考虑网络不稳定导致的调用失败、数据传输过程中的数据丢失或损坏等情况,编写可靠的自动化单元测试变得异常困难,这也对 TDD 在分布式系统开发中的应用提出了挑战。

数据库架构开发挑战

在使用测试优先的方式开发数据库架构(如 JDBC)时,存在一定的困难。数据库架构的设计和实现需要综合考虑性能、数据完整性、事务处理、数据安全性等多个方面的因素。在开发初期,由于对数据库的整体结构和业务需求的理解还在不断深化,很难先编写测试用例来指导数据库架构的设计。例如,在设计一个大型企业的数据库表结构时,需要考虑表之间的复杂关联关系、索引的创建和优化、存储过程的编写等问题,这些问题在测试优先的开发模式下较难解决,因为测试用例的编写需要依赖于数据库架构的具体设计,而在架构设计初期,这些细节往往尚未确定。

外部工具生成代码的测试争议

对于由外部工具生成的代码,是否有必要进行测试存在争议。外部工具生成的代码通常经过了工具提供商的测试和验证,具有一定的可靠性。例如,使用代码生成器生成的数据库访问代码,工具提供商可能已经对其进行了全面的测试,确保其能够正确地与数据库进行交互。在这种情况下,开发者再对这些代码进行测试可能会被认为是浪费时间和精力。然而,也有观点认为,即使是外部工具生成的代码,在与项目中的其他代码进行集成时,也可能会出现兼容性问题或不符合项目特定需求的情况,因此仍然需要进行适当的测试。

编译器 / 解释器开发困境

从巴科斯范式(Backus Normal Form,BNF)到产品质量实施来定义一种编译器 / 解释器时,难以采用测试优先的方式。编译器和解释器的开发涉及到复杂的语法分析、语义分析、代码生成和优化等过程,这些过程相互关联且具有高度的专业性和复杂性。在开发初期,很难确定具体的测试用例,因为测试用例的编写需要依赖于对编译器 / 解释器功能和行为的准确理解,而在开发的早期阶段,这些内容往往还在不断的设计和调整中。例如,在开发一个新的编程语言的编译器时,需要先定义语法规则(BNF 形式),然后进行语法分析和语义分析,最后生成目标代码。在这个过程中,很难先编写测试用例来指导整个开发过程,这也限制了 TDD 在编译器 / 解释器开发领域的应用。

虽然目前无法确切判断 Darach Ennis 观点的对错,但这些观点引发了开发者对 TDD 应用范围和局限性的深入思考,促使开发者在实际项目中更加谨慎地选择和应用 TDD 方法,根据项目的特点和需求,探索适合的开发模式和测试策略。


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

相关文章:

  • Edge-TTS在广电系统中的语音合成技术的创新应用
  • HTML<kbd>标签
  • c#使用log4Net配置日志文件
  • TypeScript 学习 -类型 - 7
  • 通义灵码插件保姆级教学-IDEA(安装及使用)
  • Oracle 创建用户和表空间
  • 【设计模式-行为型】迭代器模式
  • 构建自定义 AI 模型服务:集成到 Spring AI 处理特定任务
  • 算法刷题Day28:BM66 最长公共子串
  • AAAI2024论文合集解读|Multi-granularity Causal Structure Learning-water-merged
  • 82,【6】BUUCTF WEB .[CISCN2019 华东南赛区]Double Secret
  • 电脑怎么格式化?格式化详细步骤
  • App UI自动化--Appium学习--第一篇
  • 【机器学习】自定义数据集 使用tensorflow框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测
  • LabVIEW相位差测量系统
  • [微服务]服务保护原理
  • Redis数据库笔记——数据类型及其底层实现
  • stm32内存溢出怎么办
  • 如何在AWS上部署一个Web应用?
  • 【设计模式-行为型】备忘录模式
  • 《CPython Internals》读后感
  • 使用Python和Qt6创建GUI应用程序--关于Qt的一点介绍
  • WordPress event-monster插件信息泄露漏洞复现(CVE-2024-11396)(附脚本)
  • 【MySQL】 数据类型
  • Hive详细讲解-各类函数速通
  • GO 库与框架篇