单元测试与代码异味:通过测试发现设计问题

单元测试与代码异味:通过测试发现设计问题

关键词:单元测试、代码异味、设计缺陷、测试驱动设计、代码质量

摘要:单元测试的价值远不止“验证功能是否正确”——它更是一面能照见代码设计问题的“镜子”。本文将带你从“测试写得累”“测试总失败”“测试跑不快”等常见现象出发,用生活案例和代码示例揭示单元测试与代码异味的深层关联,学会通过测试反推设计问题,最终提升代码的可维护性和健壮性。


背景介绍

目的和范围

你是否遇到过这样的场景?写单元测试时,需要初始化10个关联对象才能测一个方法;改一行代码导致50个测试用例报错;测试跑一次要10分钟……这些“测试之痛”的背后,往往藏着代码的设计缺陷(代码异味)。本文将聚焦“单元测试如何暴露设计问题”,覆盖常见代码异味的测试表现、分析方法及重构思路。

预期读者

本文适合有一定编程经验(能写基础单元测试),希望提升代码质量的开发者。无论是后端、前端还是客户端工程师,只要关心代码可维护性,都能从本文中找到共鸣。

文档结构概述

本文将从“用生活案例理解核心概念”入手,逐步拆解单元测试与代码异味的关联逻辑;通过实际代码示例展示“测试难写”与“设计缺陷”的对应关系;最后给出通过测试优化设计的实战方法。

术语表

核心术语定义
  • 单元测试:对软件中最小可测试单元(如函数、方法)进行的测试,目标是验证单个功能点正确性。
  • 代码异味(Code Smell):代码中可能暗示更深层设计问题的表面特征(如重复代码、过长函数),类似“咳嗽”是“感冒”的症状。
  • 设计缺陷:代码架构层面的问题(如紧耦合、职责不单一),会导致代码难以维护、扩展。
相关概念解释
  • 测试脆弱性:修改无关代码导致测试用例意外失败的现象(如修改A模块导致B模块的测试报错)。
  • 测试 Setup 复杂度:运行测试前需要初始化的环境/对象数量(如为测一个方法需要创建5个模拟对象)。

核心概念与联系

故事引入:从“奶茶店质检”看测试与设计的关系

想象你开了一家奶茶店,每天要“测试”(质检)每杯奶茶是否合格。如果有一天:

  • 质检员抱怨:“测一杯芒果冰沙要先切10个芒果、煮2锅珍珠,太麻烦了!”(测试Setup复杂)
  • 你调整了芒果的采购渠道(修改代码),结果所有含芒果的奶茶质检都失败了(测试脆弱)
  • 质检效率越来越低,每天要花2小时才能完成所有测试(测试运行慢)

这些现象的背后,可能不是质检方法的问题,而是奶茶制作流程的设计缺陷:

  • 芒果冰沙的制作依赖太多前置步骤(职责不单一);
  • 所有芒果类奶茶共享同一个芒果处理模块(紧耦合);
  • 质检需要真实煮珍珠(依赖真实环境,未隔离)。

单元测试与代码的关系,就像奶茶质检与制作流程的关系——测试过程中的“不舒服”,往往指向设计的“不健康”。

核心概念解释(像给小学生讲故事一样)

概念一:单元测试——代码的“体检报告”
单元测试就像给代码做“体检”:每个方法/函数是一个“器官”,测试用例是“检查项目”(比如测“计算订单总价”是否正确,就像检查“心脏跳动是否正常”)。体检报告(测试结果)不仅能告诉我们“器官是否健康”(功能是否正确),还能通过“检查过程是否麻烦”(测试是否易写)、“检查结果是否稳定”(测试是否脆弱)等线索,推断“身体整体是否健康”(设计是否合理)。

概念二:代码异味——代码的“咳嗽打喷嚏”
代码异味是代码的“小毛病”,比如:

  • “重复代码”:像感冒时的“咳嗽”——同样的代码写了三遍,说明可能有“代码复用设计”的问题;
  • “过长函数”:像搬重物时的“喘气”——一个函数干了10件事,说明“职责划分”可能不清;
  • “紧耦合”:像穿了连脚裤的连体衣——修改A模块必须改B模块,说明“模块隔离”没做好。

这些“小毛病”本身不致命,但会让代码越来越难维护(就像感冒不及时治可能转成肺炎)。

概念三:设计缺陷——代码的“体质虚弱”
设计缺陷是代码的“根本病”,比如:

  • “职责不单一”:一个类既管用户数据存储,又管订单计算,就像一个人既要做饭又要打扫,迟早手忙脚乱;
  • “依赖混乱”:A类必须调用B类,B类又必须调用A类,就像两个小朋友互相抱着转圈,谁也走不快;
  • “缺乏抽象”:所有支付方式(支付宝、微信、信用卡)都写在一个函数里,新增支付方式要改50行代码,就像用胶水粘的拼图,想换一块就得撕掉一片。

设计缺陷会让代码“体质虚弱”,容易“生病”(出bug),且“治病”(修复)成本极高。

核心概念之间的关系(用小学生能理解的比喻)

单元测试、代码异味、设计缺陷的关系,就像“体检报告→症状→病因”:

  • 单元测试(体检报告)能发现代码异味(咳嗽、打喷嚏等症状);
  • 代码异味(症状)是设计缺陷(病因)的外在表现;
  • 通过分析测试中的“症状”(如测试难写、脆弱),可以找到设计的“病因”(如职责不单一、紧耦合)。

举个例子:
你给“订单计算”方法写测试时,发现需要创建用户对象、商品对象、促销规则对象……一共8个对象(测试Setup复杂,这是“症状”)。这可能暗示“订单计算”方法依赖了太多外部模块(职责不单一,这是“病因”)——它本应只负责“计算”,但现在还在“协调”其他模块的数据。

核心概念原理和架构的文本示意图

单元测试执行过程 → 暴露测试编写/运行中的问题(如Setup复杂、脆弱性高) → 这些问题对应代码异味(如过长函数、紧耦合) → 代码异味指向设计缺陷(如职责不单一、依赖混乱)

Mermaid 流程图

graph TD
    A[编写/运行单元测试] --> B{测试过程是否"痛苦"?}
    B -->|是| C[发现代码异味]
    C --> D[分析设计缺陷]
    D --> E[重构优化设计]
    E --> F[测试变得简单/稳定]
    B -->|否| G[代码设计合理]

核心算法原理 & 具体操作步骤:如何通过测试发现设计问题?

单元测试本身不直接“检测”设计缺陷,但可以通过观察测试过程中的4类典型现象,反向推断设计问题:

现象1:测试Setup代码比被测代码还长(测试编写成本高)

表现:为了测一个方法,需要初始化大量关联对象(如模拟用户、商品、数据库连接),Setup代码占测试用例的80%。
可能的代码异味:被测方法/类依赖过多外部对象(依赖膨胀)。
指向的设计缺陷:职责不单一(一个类需要协调太多外部资源)、缺乏依赖注入(依赖硬编码在类内部)。

示例代码(坏味道)

# 被测类:订单计算器(坏设计)
class OrderCalculator:
    def calculate_total(self, user_id, product_ids):
        # 直接调用用户服务获取用户等级
        user_service = UserService()  # 依赖UserService
        user = user_service.get_user(user_id)
        # 直接查询商品服务获取价格
        product_service = ProductService()  # 依赖ProductService
        products = [product_service.get_product(pid) for pid in product_ids]
        # 直接计算折扣(硬编码规则)
        discount = 0.9 if user.level == "VIP" else 1.0
        return sum(p.price * discount for p in products)

# 单元测试(需要初始化UserService和ProductService)
def test_calculate_total():
    # Setup:模拟UserService返回VIP用户
    user_service = MockUserService()  # 自定义模拟类
    user_service.mock_get_user(1, User(level="VIP"))
    # Setup:模拟ProductService返回两个价格100的商品
    product_service = MockProductService()
    product_service.mock_get_product(101, Product(price=100))
    product_service.mock_get_product(102, Product(price=100))
    # 初始化被测对象(需传入模拟服务)
    calculator = OrderCalculator()
    # 注意:原代码中OrderCalculator硬编码了UserService和ProductService,
    # 测试时需要通过猴子补丁替换(复杂且脆弱)
    calculator.user_service = user_service
    calculator.product_service = product_service
    # 执行测试
    total = calculator.calculate_total(1, [101, 102])
    assert total == 180  # 100*2*0.9=180

分析:测试需要模拟两个外部服务(UserService、ProductService),且被测类OrderCalculator硬编码了这些依赖,导致测试Setup复杂。这说明OrderCalculator的设计存在“依赖膨胀”和“紧耦合”问题——它本应只负责“计算”,但现在还在“获取用户”“获取商品”,职责不单一。

现象2:改一行代码导致10个测试失败(测试脆弱性高)

表现:修改模块A的一个方法,结果模块B、C、D的测试用例全部报错。
可能的代码异味:模块间耦合过紧(A的变化直接影响B、C、D)。
指向的设计缺陷:缺乏接口抽象(模块通过具体实现而非接口交互)、过度共享状态(多个模块依赖同一全局变量)。

示例代码(坏味道)

# 模块A:用户等级服务(原设计)
class UserService:
    def get_user_level(self, user_id):
        # 原逻辑:返回"VIP"/"NORMAL"
        return "VIP" if user_id % 2 == 0 else "NORMAL"

# 模块B:订单折扣计算(依赖UserService的具体返回值)
class DiscountCalculator:
    def calculate_discount(self, user_id):
        user_service = UserService()
        level = user_service.get_user_level(user_id)
        # 硬编码依赖"VIP"/"NORMAL"
        return 0.9 if level == "VIP" else 1.0

# 单元测试(模块B的测试)
def test_discount_vip():
    assert DiscountCalculator().calculate_discount(2) == 0.9

def test_discount_normal():
    assert DiscountCalculator().calculate_discount(1) == 1.0

# 现在修改模块A的逻辑(将返回值改为"GOLD"/"SILVER")
class UserService:
    def get_user_level(self, user_id):
        return "GOLD" if user_id % 2 == 0 else "SILVER"  # 修改了返回值

# 结果:模块B的两个测试全部失败(因为依赖"VIP"/"NORMAL")

分析:模块B的DiscountCalculator直接依赖模块A的具体返回值(字符串"VIP"),当模块A修改返回值时,模块B的测试必然失败。这说明两个模块之间是“紧耦合”设计——它们通过具体实现(字符串值)交互,而非更稳定的抽象(如枚举类型或接口)。

现象3:测试运行时间越来越长(测试效率低)

表现:项目初期测试跑1分钟,半年后需要10分钟,且越来越慢。
可能的代码异味:测试中包含真实IO操作(如访问数据库、调用外部API)。
指向的设计缺陷:缺乏测试隔离(生产代码与测试代码未分离关注点)、过度使用真实依赖(未用模拟对象替代)。

示例代码(坏味道)

# 被测类:用户数据保存(直接操作数据库)
class UserSaver:
    def save_user(self, user):
        # 真实连接数据库(MySQL)
        conn = mysql.connector.connect(host="localhost", user="root", password="123456")
        cursor = conn.cursor()
        cursor.execute("INSERT INTO users VALUES (%s, %s)", (user.id, user.name))
        conn.commit()
        conn.close()

# 单元测试(包含真实数据库操作)
def test_save_user():
    user = User(id=1, name="Alice")
    saver = UserSaver()
    saver.save_user(user)  # 每次测试都要连接数据库,执行SQL
    # 验证数据是否保存(再次查询数据库)
    conn = mysql.connector.connect(host="localhost", user="root", password="123456")
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM users WHERE id=1")
    result = cursor.fetchone()
    assert result == (1, "Alice")
    # 清理数据(否则下次测试会冲突)
    cursor.execute("DELETE FROM users WHERE id=1")
    conn.commit()
    conn.close()

分析:测试中包含真实的数据库连接、插入、查询、删除操作,导致每个测试用例需要几百毫秒(甚至秒级)。这说明UserSaver的设计存在“测试隔离性差”的问题——它直接依赖真实数据库,而不是通过抽象接口(如UserRepository)与数据存储交互,导致测试无法快速运行。

现象4:测试覆盖了所有分支但依然漏bug(测试有效性低)

表现:测试覆盖率100%,但上线后仍有功能异常。
可能的代码异味:被测代码逻辑复杂(如嵌套5层if-else)。
指向的设计缺陷:缺乏抽象(未将复杂逻辑拆分为小函数/类)、逻辑分散(核心逻辑与边缘逻辑混杂)。

示例代码(坏味道)

# 被测函数:计算快递费用(逻辑复杂)
def calculate_shipping_fee(weight, region, is_express):
    # 运费规则:按地区、重量、是否加急计算
    if region == "A":
        base_fee = 10 if weight <= 1 else 10 + (weight - 1) * 3
    elif region == "B":
        base_fee = 15 if weight <= 1 else 15 + (weight - 1) * 5
    elif region == "C":
        base_fee = 20 if weight <= 1 else 20 + (weight - 1) * 8
    else:
        base_fee = 30  # 其他地区
    # 加急费:基础运费的50%
    if is_express:
        base_fee *= 1.5
    # 重量超过10kg加20元超重费
    if weight > 10:
        base_fee += 20
    return round(base_fee, 2)

# 单元测试(覆盖所有分支)
def test_calculate_shipping_fee():
    # 测试地区A,重量<=1,非加急
    assert calculate_shipping_fee(0.5, "A", False) == 10
    # 测试地区A,重量>1,非加急
    assert calculate_shipping_fee(2, "A", False) == 13  # 10 + (2-1)*3=13
    # 测试地区B,重量<=1,加急
    assert calculate_shipping_fee(0.8, "B", True) == 22.5  # 15*1.5=22.5
    # 测试地区C,重量>10,加急
    assert calculate_shipping_fee(15, "C", True) == (20 + (15-1)*8)*1.5 +20  # 计算复杂
    # ... 其他分支测试

分析:虽然测试覆盖了所有if-else分支,但代码逻辑过于复杂(嵌套多层条件判断),导致测试用例难以覆盖所有“边界组合”(如“地区B+重量>10+加急”)。这说明calculate_shipping_fee函数存在“过长函数”的代码异味,指向“缺乏抽象”的设计缺陷——应该将地区规则、加急规则、超重规则拆分为独立的函数或类,降低复杂度。


数学模型和公式 & 详细讲解 & 举例说明

我们可以用“测试痛苦指数”量化测试过程中的“不舒服”程度,从而判断设计问题的严重性。公式如下:
测试痛苦指数 = 测试Setup代码行数 被测代码行数 × 测试脆弱性系数 + 单测运行时间(秒) \text{测试痛苦指数} = \frac{\text{测试Setup代码行数}}{\text{被测代码行数}} \times \text{测试脆弱性系数} + \text{单测运行时间(秒)} 测试痛苦指数=被测代码行数测试Setup代码行数×测试脆弱性系数+单测运行时间(秒)

  • 测试Setup代码行数/被测代码行数:比值越大,说明被测对象依赖越多(如>1.5时,需警惕依赖膨胀);
  • 测试脆弱性系数:修改非相关代码后,失败的测试用例数(如>0.3时,说明模块耦合过紧);
  • 单测运行时间(秒):单测平均运行时间越长(如>1秒),说明测试依赖真实资源(如数据库)。

举例
被测方法有10行代码,测试Setup代码有20行(比值2);修改A模块后,5个测试用例失败(总测试用例10个,系数0.5);单测运行时间2秒。则:
测试痛苦指数 = 2 × 0.5 + 2 = 3 \text{测试痛苦指数} = 2 \times 0.5 + 2 = 3 测试痛苦指数=2×0.5+2=3
当指数>2时,说明设计问题较严重,需要重构。


项目实战:通过测试优化设计的完整案例

背景

我们有一个“用户积分系统”,其中PointsCalculator类负责计算用户积分。随着业务扩展,测试出现以下问题:

  • 测试Setup需要初始化OrderService(获取订单)、ActivityService(获取活动)、Database(查询历史积分),Setup代码占测试的70%;
  • 修改ActivityService的接口(如新增活动类型)后,PointsCalculator的测试大量失败;
  • 单测运行时间长(每次需要查询数据库)。

开发环境搭建

  • 语言:Python 3.9+
  • 测试框架:pytest
  • 模拟工具:unittest.mock(用于模拟外部依赖)

源代码(优化前) & 问题分析

# 优化前的PointsCalculator(紧耦合、依赖真实资源)
class PointsCalculator:
    def __init__(self):
        self.order_service = OrderService()  # 真实订单服务
        self.activity_service = ActivityService()  # 真实活动服务
        self.db = Database()  # 真实数据库连接

    def calculate(self, user_id):
        # 1. 获取用户本月订单金额
        orders = self.order_service.get_orders(user_id, month=202403)
        order_amount = sum(o.amount for o in orders)
        # 2. 获取用户参与的活动
        activities = self.activity_service.get_activities(user_id)
        activity_bonus = sum(a.bonus for a in activities)
        # 3. 查询历史积分(用于计算连续登录奖励)
        history_points = self.db.query("SELECT points FROM user_points WHERE user_id = %s", user_id)
        consecutive_days = history_points.get("consecutive_days", 0)
        login_bonus = consecutive_days * 10
        # 总积分=订单金额*0.1 + 活动奖励 + 登录奖励
        total = order_amount * 0.1 + activity_bonus + login_bonus
        return total

测试问题

  • 测试需要模拟OrderServiceActivityServiceDatabase,Setup代码复杂;
  • ActivityService接口变化(如活动对象新增is_valid字段)会导致测试失败;
  • 依赖真实数据库,测试运行慢且可能污染数据。

重构思路:通过测试反推设计优化

根据测试暴露的问题,我们推断设计缺陷为:

  • 依赖膨胀PointsCalculator直接依赖3个外部服务;
  • 紧耦合:与ActivityService通过具体对象交互;
  • 测试隔离差:依赖真实数据库。

重构步骤

  1. 依赖注入:将外部服务通过构造函数传入,而非硬编码在类内部;
  2. 抽象接口:为OrderServiceActivityService定义接口,测试时用模拟对象替代;
  3. 分离数据访问:将数据库查询逻辑封装到UserPointsRepository类中,测试时模拟该仓库。

优化后的源代码 & 代码解读

# 步骤1:定义接口(抽象)
class OrderServiceInterface(ABC):
    @abstractmethod
    def get_orders(self, user_id, month):
        pass

class ActivityServiceInterface(ABC):
    @abstractmethod
    def get_activities(self, user_id):
        pass

class UserPointsRepositoryInterface(ABC):
    @abstractmethod
    def get_history_points(self, user_id):
        pass

# 步骤2:重构PointsCalculator(依赖接口而非实现)
class PointsCalculator:
    def __init__(
        self,
        order_service: OrderServiceInterface,
        activity_service: ActivityServiceInterface,
        points_repository: UserPointsRepositoryInterface
    ):
        self.order_service = order_service  # 通过接口注入
        self.activity_service = activity_service
        self.points_repository = points_repository

    def calculate(self, user_id):
        # 1. 获取订单金额(通过接口调用)
        orders = self.order_service.get_orders(user_id, month=202403)
        order_amount = sum(o.amount for o in orders)
        # 2. 获取活动奖励(通过接口调用)
        activities = self.activity_service.get_activities(user_id)
        activity_bonus = sum(a.bonus for a in activities)
        # 3. 获取历史积分(通过仓库接口)
        history = self.points_repository.get_history_points(user_id)
        consecutive_days = history.get("consecutive_days", 0)
        login_bonus = consecutive_days * 10
        # 总积分计算
        total = order_amount * 0.1 + activity_bonus + login_bonus
        return total

# 步骤3:测试用例(使用模拟对象,Setup简单)
def test_points_calculation():
    # 模拟OrderService(返回2个订单,金额各100)
    mock_order_service = Mock(spec=OrderServiceInterface)
    mock_order_service.get_orders.return_value = [Order(amount=100), Order(amount=100)]
    # 模拟ActivityService(返回1个活动,奖励50)
    mock_activity_service = Mock(spec=ActivityServiceInterface)
    mock_activity_service.get_activities.return_value = [Activity(bonus=50)]
    # 模拟UserPointsRepository(返回连续登录5天)
    mock_repository = Mock(spec=UserPointsRepositoryInterface)
    mock_repository.get_history_points.return_value = {"consecutive_days": 5}
    # 初始化被测对象(注入模拟依赖)
    calculator = PointsCalculator(
        order_service=mock_order_service,
        activity_service=mock_activity_service,
        points_repository=mock_repository
    )
    # 执行测试
    total = calculator.calculate(user_id=1)
    # 验证计算逻辑:(100+100)*0.1 + 50 + 5*10 = 20 + 50 + 50 = 120
    assert total == 120

代码解读

  • 通过接口抽象(OrderServiceInterface等),将PointsCalculator与具体实现解耦;
  • 依赖通过构造函数注入,测试时用unittest.mock创建模拟对象,Setup代码从70%减少到30%;
  • 分离数据访问逻辑到UserPointsRepository,测试不再依赖真实数据库,运行时间从2秒缩短到0.1秒;
  • ActivityService接口变化时(如新增is_valid字段),只需修改模拟对象的返回值,不会导致测试大面积失败。

实际应用场景

单元测试与代码异味的关联在以下场景中尤为明显:

测试现象对应代码异味设计缺陷典型场景举例
测试Setup复杂依赖膨胀职责不单一一个类调用5个外部服务
测试脆弱(改A模块影响B)紧耦合缺乏接口抽象模块通过具体类而非接口交互
测试运行慢真实IO依赖测试隔离性差测试中包含数据库/文件操作
测试覆盖全但漏bug逻辑复杂(过长函数)缺乏抽象一个函数包含10层if-else

工具和资源推荐

  • 测试框架:Python的pytest、Java的JUnit(提供简洁的测试语法,降低编写成本);
  • 模拟工具unittest.mock(Python)、Mockito(Java)(快速创建模拟对象,隔离外部依赖);
  • 代码异味检测SonarQube(静态分析工具,自动检测重复代码、过长函数等异味);
  • 测试覆盖率工具coverage.py(Python)、JaCoCo(Java)(帮助识别未覆盖的代码逻辑)。

未来发展趋势与挑战

  • 左移测试(Shift Left):将测试嵌入开发流程早期(如TDD测试驱动开发),通过“先写测试再写代码”提前发现设计问题;
  • 智能测试生成:AI工具(如GitHub Copilot Test)自动生成测试用例,通过分析代码结构识别潜在设计缺陷;
  • 测试与架构治理结合:将“测试痛苦指数”纳入架构评审指标,推动团队关注设计质量。

挑战在于如何平衡“测试的严格性”与“开发效率”——过度追求测试覆盖可能导致代码过度设计,需要团队建立“基于风险的测试策略”(优先测试核心功能)。


总结:学到了什么?

核心概念回顾

  • 单元测试:不仅是“功能验证工具”,更是“设计诊断工具”;
  • 代码异味:测试过程中的“痛苦”是代码异味的外在表现(如测试Setup复杂、脆弱);
  • 设计缺陷:代码异味的根源(如职责不单一、紧耦合),需要通过重构优化。

概念关系回顾

单元测试像“探测器”,代码异味像“信号”,设计缺陷像“目标”——通过观察测试中的“信号”(如测试难写),可以定位“目标”(如依赖膨胀),最终通过重构“消除目标”(优化设计)。


思考题:动动小脑筋

  1. 你在项目中写单元测试时,遇到过哪些“痛苦”场景(如测试Setup复杂、测试总失败)?这些场景可能对应哪些代码异味?
  2. 假设你要为一个“用户登录”功能写单元测试,发现需要模拟PasswordEncoder(密码加密)、UserRepository(用户查询)、LoginLogService(登录日志),这可能暗示什么设计问题?如何优化?
  3. 如果你负责一个遗留系统的重构,该系统的测试覆盖率很低且测试非常脆弱,你会如何通过单元测试推动设计优化?

附录:常见问题与解答

Q:单元测试写得痛苦,是否应该先优化测试,还是先优化代码?
A:测试的“痛苦”是代码设计的“症状”,应该优先优化代码设计(如解耦、抽象接口),测试会随着设计优化变得简单。

Q:测试覆盖率100%,但仍有漏bug,是否说明测试没用?
A:测试覆盖率高不代表测试质量高。如果代码逻辑复杂(如多层条件判断),即使覆盖100%,也可能遗漏“条件组合”的测试。此时应优先拆分复杂逻辑(如提取小函数),再补充测试。

Q:使用模拟对象(Mock)是否会影响测试的真实性?
A:模拟对象用于隔离外部依赖,验证“当前模块的逻辑是否正确”(如“是否正确调用了外部服务”)。对于“集成测试”(验证多个模块协作),仍需要使用真实依赖。


扩展阅读 & 参考资料

  • 《代码整洁之道》(罗伯特·C·马丁):第9章“单元测试”详细讲解测试与代码设计的关系;
  • 《重构:改善既有代码的设计》(马丁·福勒):第3章“代码的坏味道”列举常见异味及测试表现;
  • 《Python单元测试实战》(Brian Okken):提供Python测试工具(如pytest、mock)的具体使用案例。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值