测试基础 掌握这五招,让你代码质量翻倍!

花菜 · 2023年11月11日 · 最后由 测试新人 回复于 2023年11月23日 · 8863 次阅读

引言

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

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):
    # 仅包含删除用户的逻辑

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

总结

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

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

公众号原文

共收到 21 条回复 时间 点赞
花菜 #20 · 2023年11月11日 Author

如果你有更好的提升代码质量方法,欢迎留言讨论哦

咨询下花总,例 4 中,涉及到自动选择函数(关键字驱动),怎么用 if ,else?使用 eval?match?


发现一个 bug

测试写代码纯属自嗨。反正领导也不看写的烂不烂,就像明明前端用 Json schema 做动态表单比较优雅,多写几个 ifelse 不是省事么,难不难看好不好维护有啥用?😂 领导用我只是因为工作效率高,他只会看东西有没有做出来,用例数是多少仅此而已。

Aconment 回复

动态导入模块自定义函数文件 importlib.import_module(''), eval 调用文件里的函数,完事

disable 回复

应该说是测试注重代码质量属于自嗨,烂不烂没人关心的,不如写好注释和写好说明文档,性能都不是要关注的点,测试写都是内部使用,谈不上什么并发量,稳定就行,我之前在 github 看到有个叫七月的小姐姐开源的接口自动化框架,那代码,为了复杂而复杂的封装,绕得头都晕了。所以我觉得最好的还是以 稳定性 + 完整注释 + 好维护 为主。

测试新人 回复

嗯,之前也接受过别人写的代码,明明 2 个文件可以解决,再那炫技呢。绕来绕去看的都烦,我直接重写了。😂 简单直接注释好,减少调用链。看一眼就明白了

disable 回复

本来写代码就是为了去方便验证的,现在反而本末倒置,追求技术难度。。。去不断复杂化,越复杂就导致 bug 越多。这就形成原本你要去验证其他人的程序的,现在反而关注点全在自己的程序上😂

测试新人 回复

七月?是这个么?写的太啰嗦,不太想看了😂
还要生成 py 文件额,然后再 remove 文件。。。
为啥不用 type 动态生成,

花菜 #12 · 2023年11月13日 Author
测试新人 回复

为什么测试的代码就不需要质量。

没有人关心,但自己总得为自己写的代码负责。

过度封装,导致不方便维护,容易出问题,那最终受害的也是自己呀。

质量好就是易读,方便维护,方便测试。

那些过度封装,肯定是糟糕的代码。

花菜 #10 · 2023年11月13日 Author
disable 回复

这个不是 bug,data 传进来的参数,这个 processed_data 是表示已经处理过的数据。

花菜 #10 · 2023年11月13日 Author
Aconment 回复

这个例子的核心是单一原则,一个函数只干一件事情。

你说的可以通过字典来处理就好

disable 回复

是呢,炫技并不是高质量的代码,反而是糟糕的代码。

高质量的代码往往就是朴实无华,keep stupid,keep simple

disable 回复

就是这个,卧槽,360°旋转的绕呀

测试新人 回复

这套代码不好维护,直接劝退,代码层面不复杂就是太啰嗦太臃肿😂

花菜 回复

明了,谢谢

disable 回复

求教,那有没有比较不错的 python 接口自动化开源项目推荐呢?

测试新人 回复

明明可以直接写代码,就硬要整一层 yaml 转换。😁

httprunner?

古一 回复

看来你也看过,那个真的是我看过最扯蛋的程序。。。。。😂

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册