引言

昨晚在技术群里面讨论到单元测试和代码的测可性

image.png

让我想起来曾犯过的一些错误。

现在需要实现一个定时执行的任务函数 mytask,其核心功能是调用一个第三方接口,并使用当前日期 today 作为参数。这个函数的设计直接从系统获取当前日期,并没有提供修改日期的选项。

示例代码:

def mytask():
    dt = datetime.today()  # 获取并使用当前日期
    # 调用第三方接口
    # 更新数据库

这种设计在日常运行中没有问题,但是当遇到需要针对特定日期重新执行任务的情况时,就显得不够灵活。

例如,在一个愉快的周末后,领导告诉你第三方接口上周五出现了 bug,导致数据出错,需要重新获取数据。

这时,由于 mytask 函数中的日期是动态获取的,重新执行任务并不能获得上周五的数据。

一种粗暴的解决方案是直接修改代码中的 today 为上周五的日期,重新部署并执行一次。然而,这种方法显然不是长久之计。

更好的做法是将日期作为一个参数传递给 mytask 函数,这样既可以保持定时任务的正常运行,也可以在需要时手动指定日期来重跑任务。

优化后的代码:

from typing import Optional
from datetime import datetime

def mytask(dt: Optional[datetime] = None):
    dt = dt or datetime.today()  # 允许传递特定日期
    # 调用第三方接口
    # 更新数据库

这样,mytask 函数就不再依赖于固定的日期值,而是可以根据需要传入任何日期。当需要重新拉取特定日期的数据时,只需要手动调用 mytask 并传入相应的日期参数即可。这种设计增强了代码的灵活性和可测性,使得维护和测试变得更加方便。
下面将通过四个例子继续探讨如何改进代码的可测性。

例 1:隔离外部依赖以便模拟测试

不良示范:

def send_email():
    smtp_server.connect()  # 直接连接SMTP服务器
    # 发送邮件

好的代码示范:

from typing import Any

def send_email(smtp_server: Any):
    smtp_server.connect()  # 将SMTP服务器作为参数传递
    # 发送邮件

将外部服务如 SMTP 服务器作为参数传递,方便在测试中使用模拟对象。

例 2:分离业务处理器和请求处理

不良示范:

# 依赖于具体的请求对象,与特定的web框架紧密耦合
from flask import request

def process_request():
    data = request.get_json()  # 直接从请求对象中获取数据
    # 在此处包含数据处理逻辑
    # ...
    return {"status": "success", "data": processed_data}

好的代码示范:

# 解耦数据处理逻辑,不再依赖特定的请求对象或web框架
from typing import Dict

def process_data(data: Dict) -> Dict:
    # 独立的数据处理逻辑
    # ...
    return {"status": "success", "data": processed_data}

# Web框架的请求处理函数只负责调用业务逻辑函数
def api_endpoint(request_data: Dict):
    # 从请求中提取数据并传递给独立的处理函数
    result = process_data(request_data)
    return result

在这种方式中,process_data 函数现在是一个纯粹的业务逻辑函数,它只关心数据的处理,不再关心数据来源。

而 api_endpoint 函数则作为 web 框架的入口点,负责接收请求和调用业务逻辑函数。

这样的分离使得 process_data 更容易进行单元测试,同时也提高了代码的可重用性。

例 3:使用配置文件而非硬编码

不良示范:

def connect_database():
    db = "MySQL"  # 数据库类型硬编码
    # 连接数据库

好的代码示范:

from typing import Any

def connect_database(config: Any):
    db = config.database_type  # 从配置文件读取数据库类型
    # 连接数据库

避免硬编码,使用配置文件来管理设置,这样可以在不同环境下灵活配置,同时便于测试。

例 4:采用单一职责原则

不良示范:

def manage_user(request):
    if request.type == "create":
        # 创建用户
    elif request.type == "delete":
        # 删除用户

好的代码示范:

from typing import Any

def create_user(user_data: Any):
    # 仅包含创建用户的逻辑

def delete_user(user_id: int):
    # 仅包含删除用户的逻辑

按照单一职责原则设计函数,每个函数只做一件事情,这样便于单元测试和维护。

总结

以上五个例子展示了代码可测性的重要性以及提升可测性的常见策略。

通过参数化输入、隔离外部依赖、解耦业务逻辑、使用配置文件和单一职责原则,可以构建出更健壮、更易于测试和维护的代码。

公众号原文


↙↙↙阅读原文可查看相关链接,并与作者交流