本文分享如何使用驭码CodeRider 的单元测试功能生成单元测试文件。

在之前的文章如何用 Python 手撸一个 GitLab 代码安全审查工具?中,我用 Python 写了一个接受极狐GitLab 代码安全审计事件流并且将消息推送到钉钉群的脚本,完整的 python 代码为:

    from fastapi import FastAPI
    import uvicorn
    import requests
    import json

    app = FastAPI()

    @app.post("/jh-gitlab")
    async def gitlab_payload(data: dict):
        # 抓取审计事件中的主要信息
        audit_event_info = {
            "Action": data['details']['custom_message'],
            "Author": data['details']['author_name'],
            "IP Address": data['details']['ip_address'],
            "Entity Path": data['details']['entity_path'],
            "Target Details": data['target_details']
        }
        print(audit_event_info)

        # 发送消息通知
        notification(audit_event_info)

    def notification(payload: dict):
        webhook_url = "https://oapi.dingtalk.com/robot/send?access_token=你的钉钉 webhook token"

        # 发送消息的内容
        message = {
            "msgtype": "text",
            "text": {
                "content" : "GitLab: {}".format(json.dumps(payload))
            }
        }

        # 发送 POST 请求
        headers = {'Content-Type': 'application/json'}
        response = requests.post(webhook_url, data=json.dumps(message), headers=headers)
        print(response.text)
        if json.loads(response.text)['errcode'] == 0:
            print("Send Message Success!")
            return True
        else:
            print("Send Message Failed!")
            return json.loads(response.text)['errmsg']


    if __name__ == "__main__":
        uvicorn.run(app, host="0.0.0.0", port=8000)

对于这段代码写的是否 OK 最好的验证方法就是生成一些单元测试来测试。为了方便,我决定用 AI 工具来帮我生成单元测试代码。

我选择的是极狐GitLab 公司自研发布的 AI 产品驭码CodeRider(当前该产品可以免费试用【https://dl.gitlab.cn/w84csrf4】)。 该产品就有一个单元测试生成功能,当我在 PyCharm 中安装好驭码CodeRier 并直接点击test命令时:

file

就开始了单元测试代码的自动生成:

file

最终生成了如下测试代码:

from fastapi.testclient import TestClient
from unittest.mock import patch
import json

from main import app, notification

client = TestClient(app)

def test_gitlab_payload_success():
    payload = {
        "details": {
            "custom_message": "Project marked for deletion",
            "author_name": "Administrator",
            "ip_address": "36.133.246.166",
            "entity_path": "devsecops/ai-deleted-7"
        },
        "target_details": "ai-deleted-7"
    }

    with patch('main.notification') as mock_notification:
        mock_notification.return_value = True
        response = client.post("/jh-gitlab", json=payload)
        assert response.status_code == 200
        mock_notification.assert_called_once_with({
            "Action": "Project marked for deletion",
            "Author": "Administrator",
            "IP Address": "36.133.246.166",
            "Entity Path": "devsecops/ai-deleted-7",
            "Target Details": "ai-deleted-7"
        })

def test_gitlab_payload_missing_fields():
    payload = {
        "details": {
            "custom_message": "Project marked for deletion",
            "author_name": "Administrator",
            "ip_address": "36.133.246.166"
        },
        "target_details": "ai-deleted-7"
    }

    response = client.post("/jh-gitlab", json=payload)
    assert response.status_code == 422

def test_notification_success():
    payload = {
        "Action": "Project marked for deletion",
        "Author": "Administrator",
        "IP Address": "36.133.246.166",
        "Entity Path": "devsecops/ai-deleted-7",
        "Target Details": "ai-deleted-7"
    }

    with patch('requests.post') as mock_post:
        mock_response = mock_post.return_value
        mock_response.text = json.dumps({"errcode": 0, "errmsg": "ok"})
        result = notification(payload)
        assert result == True
        mock_post.assert_called_once()

def test_notification_failure():
    payload = {
        "Action": "Project marked for deletion",
        "Author": "Administrator",
        "IP Address": "36.133.246.166",
        "Entity Path": "devsecops/ai-deleted-7",
        "Target Details": "ai-deleted-7"
    }

    with patch('requests.post') as mock_post:
        mock_response = mock_post.return_value
        mock_response.text = json.dumps({"errcode": 400, "errmsg": "Bad Request"})
        result = notification(payload)
        assert result == "Bad Request"
        mock_post.assert_called_once()

看到这些代码,我就想知道怎么测试呢。然后就问驭码CodeRider,如果要运行这些单元测试,我该怎么操作?

file

驭码CodeRider 给的答案是四步:

  • 第一步安装依赖
  • 第二步将测试代码存放在 test_main.py文件中
  • 第三步运行测试文件
  • 第四步查看答案

我就遵照四步进行了测试,出现了错误:

Screenshot 2024-10-30 at 15.27.35.png

提示 FAILED pytest_main.py::test_gitlab_payload_missing_entity_path - KeyError: 'entity_path'。仔细看了一下,测试代码中有一个检测缺失字段的环节,我代码中的 payload 有五个参数:ActionAuthorIP AddressEntity Path以及 Target Details

下面代码

def test_gitlab_payload_missing_fields():
    payload = {
        "details": {
            "custom_message": "Project marked for deletion",
            "author_name": "Administrator",
            "ip_address": "36.133.246.166"
        },
        "target_details": "ai-deleted-7"
    }

    response = client.post("/jh-gitlab", json=payload)
    assert response.status_code == 422

用来测试在缺失 entity_path字段的情况。比较遗憾的是,我在源代码中并没有对 payload 中的字段进行校验处理。所以我把这个错误发给了驭码CodeRider:

file

驭码CodeRider 给出了两种解决方案:

  • 方案一:在测试中添加 entity_path 字段
  • 方案二:修改 gitlab_payload 函数以处理缺失字段

按照这两种方式都可以,我选择了修改 gitlab_payload相关代码,于是继续问了驭码CodeRider:

file

驭码给的修改代码为:

    audit_event_info = {
        "Action": data['details'].get('custom_message', 'Unknown Action'),
        "Author": data['details'].get('author_name', 'Unknown Author'),
        "IP Address": data['details'].get('ip_address', 'Unknown IP'),
        "Entity Path": data['details'].get('entity_path', 'Unknown Path'),
        "Target Details": data.get('target_details', 'Unknown Target')
    }

就是给缺失的字段增加默认值。接着执行测试命令:

Screenshot 2024-10-30 at 15.39.17.png

可以看到 4 条测试全部通过。

当然,上面的整个流程仅仅为测试使用,生成的单元测试不一定是最准确、最后直接可以使用的,但是我们可以看到用 AI 来生成单元测试文件至少是靠谱的、能够减轻不少工作量,先用 AI 生成,然后做一些修改,这样工作能轻松不少。

用 AI 来帮助生成单元测试文件看来靠谱,驭码CodeRider 可以的!