ChatGLM-6B在软件测试中的应用:自动化用例生成

作为一名在测试领域摸爬滚打多年的工程师,我深知编写测试用例的痛。需求文档看了一遍又一遍,绞尽脑汁设计各种边界条件,还要考虑不同模块间的交互,一套完整的测试用例写下来,半天时间就没了。更别提那些复杂的业务逻辑,有时候自己都绕晕了。

最近,大语言模型在代码生成、文档编写上大放异彩,我就琢磨着,能不能让它也来帮我们测试工程师“减减负”?于是,我把目光投向了开源的ChatGLM-6B。经过一番折腾和实测,我发现,用它来辅助生成测试用例,效果还真不错。今天,我就来分享一下,如何让ChatGLM-6B成为你测试团队里的“编外员工”,帮你自动化生成单元测试和集成测试用例。

1. 为什么选择ChatGLM-6B来做这件事?

在开始动手之前,你可能会有疑问:市面上模型那么多,为什么偏偏是ChatGLM-6B?

首先,它完全开源免费。对于企业测试团队来说,这意味着没有额外的授权费用,可以放心地在内部环境部署和使用,数据安全也有保障。其次,它对中文的理解和生成能力非常出色。我们的需求文档、接口定义、函数注释大部分都是中文的,这一点至关重要。最后,它部署门槛相对较低。6B的参数量,经过量化后,在一台普通的GPU服务器甚至配置高一些的CPU服务器上就能跑起来,不需要动辄几十张卡的豪华配置。

简单来说,它就像一个懂中文、能理解技术需求、而且“聘用”成本极低的测试用例编写助手。

2. 快速搭建你的测试用例生成环境

理论说再多,不如实际跑起来看看。我们先花点时间,把ChatGLM-6B的服务搭起来。这里我推荐使用API服务的方式,这样我们的测试脚本、CI/CD流水线都可以方便地调用。

2.1 基础环境准备

假设你已经有一台Linux服务器(带GPU效果更好),我们直接从官方仓库拉取代码。

# 1. 克隆ChatGLM-6B仓库
git clone https://github.com/THUDM/ChatGLM-6B.git
cd ChatGLM-6B

# 2. 安装依赖(建议使用Python虚拟环境)
pip install -r requirements.txt

# 3. 如果你GPU显存有限(比如小于13GB),可以安装量化版本支持
# pip install cpm-kernels torch (版本需匹配)

2.2 启动API服务

ChatGLM-6B项目里很贴心地自带了一个基于FastAPI的API服务脚本 api.py。我们直接运行它。

# 启动API服务,默认监听8000端口
python api.py

如果一切顺利,你会看到服务启动成功的日志。现在,你的本地 http://127.0.0.1:8000 就提供了一个对话接口。我们可以用curl简单测试一下:

curl -X POST "http://127.0.0.1:8000" \
     -H 'Content-Type: application/json' \
     -d '{"prompt": "你好,请介绍下你自己", "history": []}'

如果返回了“你好👋!我是人工智能助手 ChatGLM-6B...”这样的JSON响应,恭喜你,环境搭建成功!

小提示:对于生产环境,你可能需要处理模型加载、并发、服务化管理等问题,可以考虑使用Docker容器化部署,或者用systemd管理服务进程。

3. 从函数注释到单元测试用例

单元测试是保证代码质量的第一道防线。我们来看一个最常见的场景:你写了一个功能函数,现在需要为它补充测试用例。

假设我们有一个处理用户订单折扣的函数,写在 order_calculator.py 里:

def calculate_discounted_price(original_price, user_level, coupon_type=None):
    """
    计算订单折后价格。

    参数:
        original_price (float): 商品原价,必须大于0。
        user_level (str): 用户等级,可选 'regular', 'vip', 'svip'。
        coupon_type (str, optional): 优惠券类型,可选 '10_off', '20_off', 'free_shipping'。默认为None。

    返回:
        float: 折后价格。

    规则:
        - 'vip' 用户享受95折,'svip' 用户享受9折,'regular' 用户无折扣。
        - '10_off' 券减10元,'20_off' 券减20元,'free_shipping' 不影响商品价格。
        - 最终价格不能低于0。
    """
    # 这里省略具体的实现代码...
    pass

现在,我们写一个脚本,让ChatGLM-6B帮我们生成这个函数的测试用例。核心思路是:把函数签名、详细的注释描述和我们的测试需求,一起“喂”给模型。

import requests
import json

def generate_unit_test(api_url, function_info, test_framework="pytest"):
    """
    调用ChatGLM-6B API生成单元测试用例。
    """
    prompt = f"""
    你是一个资深的软件测试工程师。请为以下Python函数编写全面的单元测试用例。
    要求使用{test_framework}框架。
    测试用例应覆盖:
    1. 正常功能场景。
    2. 边界值(如最小值、最大值、临界值)。
    3. 异常输入(如非法参数、类型错误)。
    4. 参数组合情况。
    请只输出测试函数的代码,无需解释。

    函数信息如下:
    {function_info}
    """

    data = {
        "prompt": prompt,
        "max_length": 1500,  # 根据需要调整生成长度
        "temperature": 0.2,   # 温度调低,让输出更确定、更专业
    }

    response = requests.post(api_url, json=data)
    if response.status_code == 200:
        result = response.json()
        return result['response']
    else:
        print(f"API请求失败: {response.status_code}")
        return None

# 使用示例
if __name__ == "__main__":
    API_URL = "http://127.0.0.1:8000"
    
    # 这里我们直接把函数的定义和文档字符串作为输入
    function_description = '''
    def calculate_discounted_price(original_price, user_level, coupon_type=None):
        """
        计算订单折后价格。
        参数:
            original_price (float): 商品原价,必须大于0。
            user_level (str): 用户等级,可选 'regular', 'vip', 'svip'。
            coupon_type (str, optional): 优惠券类型,可选 '10_off', '20_off', 'free_shipping'。默认为None。
        返回:
            float: 折后价格。
        规则:
            - 'vip' 用户享受95折,'svip' 用户享受9折,'regular' 用户无折扣。
            - '10_off' 券减10元,'20_off' 券减20元,'free_shipping' 不影响商品价格。
            - 最终价格不能低于0。
        """
    '''

    test_code = generate_unit_test(API_URL, function_description)
    if test_code:
        print("生成的测试用例代码:")
        print(test_code)
        # 你可以选择将代码保存到文件,例如 test_order_calculator.py
        # with open('test_order_calculator.py', 'w') as f:
        #     f.write(test_code)

运行这个脚本,ChatGLM-6B可能会返回类似下面的代码(经过整理):

import pytest
from order_calculator import calculate_discounted_price

class TestCalculateDiscountedPrice:
    """测试 calculate_discounted_price 函数"""

    # 正常功能场景
    def test_regular_user_no_coupon(self):
        """常规用户,无优惠券"""
        assert calculate_discounted_price(100.0, 'regular') == 100.0
        assert calculate_discounted_price(50.5, 'regular') == 50.5

    def test_vip_user_discount(self):
        """VIP用户折扣"""
        assert calculate_discounted_price(200.0, 'vip') == 190.0  # 200 * 0.95
        assert calculate_discounted_price(100.0, 'vip', 'free_shipping') == 95.0

    def test_svip_user_discount(self):
        """SVIP用户折扣"""
        assert calculate_discounted_price(200.0, 'svip') == 180.0  # 200 * 0.9

    def test_with_coupon_10_off(self):
        """使用减10元优惠券"""
        assert calculate_discounted_price(100.0, 'regular', '10_off') == 90.0
        assert calculate_discounted_price(15.0, 'vip', '10_off') == 4.25  # (15*0.95) -10, 但不会低于0
        # 注意:这里模型可能意识到价格不能为负,但具体处理逻辑取决于你的实现。

    # 边界值测试
    def test_price_boundary(self):
        """原价边界测试"""
        # 极小正数
        assert calculate_discounted_price(0.01, 'regular') == 0.01
        # 使用优惠券后价格触底为0
        assert calculate_discounted_price(10.0, 'regular', '20_off') == 0.0

    def test_user_level_boundary(self):
        """用户等级边界(传入列表之外的等级)"""
        # 这里需要你的函数实现如何处理非法user_level,是抛出异常还是返回默认值?
        # 模型可能会生成测试异常场景的代码,例如:
        # with pytest.raises(ValueError):
        #     calculate_discounted_price(100.0, 'invalid_level')

    # 异常输入测试
    def test_invalid_price(self):
        """无效的原价"""
        with pytest.raises(ValueError):
            calculate_discounted_price(-10.0, 'regular')
        with pytest.raises(TypeError):
            calculate_discounted_price("一百元", 'regular')  # 非数字类型

    def test_invalid_coupon(self):
        """无效的优惠券类型"""
        # 同样,取决于函数设计,是忽略、报错还是默认处理
        result = calculate_discounted_price(100.0, 'regular', 'invalid_coupon')
        # 断言结果符合预期(例如,忽略无效券,原价100)
        assert result == 100.0

看,模型不仅生成了基础的“快乐路径”测试,还考虑到了边界值(如0.01元的价格)、异常输入(负数、错误类型),甚至不同参数之间的组合情况。这为我们提供了一个非常扎实的测试用例草稿。当然,生成的内容需要你这位测试专家来审核,特别是涉及到业务规则细节(比如“价格不能低于0”的具体实现逻辑)的地方,可能需要微调。

4. 为HTTP接口生成集成测试用例

单元测试之后,集成测试是另一个大头。特别是现在微服务架构下,我们需要测试各个服务间的HTTP API接口。假设我们有一个用户注册接口的文档:

接口:POST /api/v1/user/register
请求体 (application/json):
{
    "username": "string, 必填,长度3-20位,字母数字下划线",
    "password": "string, 必填,长度8-32位,需包含大小写字母和数字",
    "email": "string, 必填,有效邮箱格式"
}
响应:
成功 (200): {"code": 0, "message": "success", "data": {"userId": 123}}
失败 (400): {"code": 1001, "message": "用户名已存在"} 等

我们可以让ChatGLM-6B基于这个接口文档,生成集成测试脚本。这次我们换个方式,在Prompt里更明确地要求它使用 requests 库,并生成一个完整的、可运行的测试类。

def generate_integration_test(api_url, api_doc):
    """
    调用ChatGLM-6B API生成HTTP接口集成测试用例。
    """
    prompt = f"""
    你是一个资深的软件测试工程师。请根据以下HTTP接口文档,编写一个完整的Python集成测试类。
    要求:
    1. 使用 `requests` 库发送HTTP请求。
    2. 使用 `pytest` 框架组织测试用例。
    3. 测试类名为 `TestUserRegisterAPI`。
    4. 测试用例应覆盖:
       - 正常注册成功场景。
       - 各个参数(username, password, email)的边界值测试(长度、格式)。
       - 各个参数缺失或为空的异常场景。
       - 重复注册等业务逻辑错误场景。
       - 无效的JSON格式或Content-Type。
    5. 假设服务基地址为 `BASE_URL = "http://localhost:8080"`。
    6. 请输出完整的、可运行的Python代码,包含必要的import和类定义。

    接口文档:
    {api_doc}
    """

    data = {
        "prompt": prompt,
        "max_length": 2000,
        "temperature": 0.2,
    }
    response = requests.post(api_url, json=data)
    if response.status_code == 200:
        return response.json()['response']
    else:
        return None

# 使用示例
api_document = """
接口:POST /api/v1/user/register
请求体 (application/json):
{
    "username": "string, 必填,长度3-20位,字母数字下划线",
    "password": "string, 必填,长度8-32位,需包含大小写字母和数字",
    "email": "string, 必填,有效邮箱格式"
}
响应:
成功 (200): {"code": 0, "message": "success", "data": {"userId": 123}}
失败 (400): {"code": 1001, "message": "用户名已存在"}
失败 (400): {"code": 1002, "message": "邮箱格式无效"}
失败 (400): {"code": 1003, "message": "密码强度不足"}
"""

test_script = generate_integration_test(API_URL, api_document)
print(test_script)

模型生成的代码可能会非常详尽,这里我摘录一部分核心的测试用例逻辑:

import pytest
import requests
import json

BASE_URL = "http://localhost:8080"

class TestUserRegisterAPI:
    """测试用户注册接口 /api/v1/user/register"""

    def test_register_success(self):
        """正常注册成功"""
        payload = {
            "username": "test_user_123",
            "password": "Passw0rd123",
            "email": "test@example.com"
        }
        resp = requests.post(f"{BASE_URL}/api/v1/user/register", json=payload)
        assert resp.status_code == 200
        data = resp.json()
        assert data["code"] == 0
        assert "userId" in data.get("data", {})

    def test_username_length_boundary(self):
        """用户名长度边界测试"""
        # 刚好3位
        payload = {"username": "ab1", "password": "Passw0rd123", "email": "test1@ex.com"}
        resp = requests.post(f"{BASE_URL}/api/v1/user/register", json=payload)
        assert resp.status_code == 200  # 或400,取决于服务端是否严格在3-20之间

        # 刚好20位
        payload["username"] = "a" * 20
        resp = requests.post(f"{BASE_URL}/api/v1/user/register", json=payload)
        assert resp.status_code == 200

        # 超过20位
        payload["username"] = "a" * 21
        resp = requests.post(f"{BASE_URL}/api/v1/user/register", json=payload)
        assert resp.status_code == 400
        # 可以进一步断言错误信息包含长度违规

    def test_password_complexity(self):
        """密码复杂度测试"""
        # 缺少大写字母
        payload = {"username": "user1", "password": "password123", "email": "test@ex.com"}
        resp = requests.post(f"{BASE_URL}/api/v1/user/register", json=payload)
        assert resp.status_code == 400
        assert resp.json()["code"] == 1003  # 密码强度不足

        # 缺少数字
        payload["password"] = "PasswordABC"
        resp = requests.post(f"{BASE_URL}/api/v1/user/register", json=payload)
        assert resp.status_code == 400

    def test_email_format(self):
        """邮箱格式测试"""
        invalid_emails = ["not-an-email", "missing@domain", "@domain.com", "test@.com"]
        for email in invalid_emails:
            payload = {"username": "userx", "password": "Passw0rd123", "email": email}
            resp = requests.post(f"{BASE_URL}/api/v1/user/register", json=payload)
            assert resp.status_code == 400
            assert resp.json()["code"] == 1002  # 邮箱格式无效

    def test_missing_required_fields(self):
        """缺失必填字段"""
        # 缺失username
        payload = {"password": "Passw0rd123", "email": "test@ex.com"}
        resp = requests.post(f"{BASE_URL}/api/v1/user/register", json=payload)
        assert resp.status_code == 400

        # 缺失password
        payload = {"username": "user1", "email": "test@ex.com"}
        resp = requests.post(f"{BASE_URL}/api/v1/user/register", json=payload)
        assert resp.status_code == 400

    # ... 可能还会生成重复注册、无效JSON等测试用例

可以看到,模型生成的测试用例已经具备了相当的“专业性”,它能够理解接口约束(如长度、格式),并将其转化为具体的边界测试数据。这极大地减少了测试工程师构造测试数据、编写重复断言代码的时间。

5. 实践中的技巧与注意事项

在实际项目中应用这套方法,有几个小技巧和坑需要注意:

第一,Prompt工程是关键。 你给模型的指令越清晰、越具体,它生成的内容质量就越高。比如,明确指定测试框架(pytest/unittest)、要求覆盖的测试类型(正常、边界、异常)、甚至输出格式。可以把你们团队的测试用例编写规范也放进Prompt里。

第二,生成的代码需要审查和调整。 模型不是万能的,尤其是对业务规则的细微之处。它生成的测试用例是一个极好的“初稿”或“灵感来源”,但你必须以测试负责人的身份,仔细检查每一行生成的代码:

  • 断言的值是否正确?(比如折扣计算是95折还是9折?)
  • 异常场景的处理是否符合你服务的实际行为?(是返回错误码还是抛出异常?)
  • 生成的测试数据是否真的能触发想要的逻辑?

第三,考虑将其集成到开发流程中。 一个理想的使用场景是:开发同学提交一个带有详细注释的新函数或API定义后,CI流水线自动调用这个“测试用例生成服务”,产出测试用例草稿,然后由开发同学或测试同学进行审查、补充和确认,最后合并到代码库。这能形成一种“AI辅助代码审查”的良性循环。

第四,注意处理模型的局限性。 ChatGLM-6B毕竟是一个通用对话模型,不是专门的代码生成模型。对于极其复杂的状态机、涉及多步骤流程的集成场景,它可能无法一次性生成完美的测试用例。这时候,可以尝试“分而治之”,先让它生成主干流程的测试,再针对每个复杂状态或分支单独生成测试。

6. 总结

整体用下来,ChatGLM-6B在辅助生成软件测试用例方面,给我的感觉是“惊喜大于预期”。它确实能理解中文的技术需求描述,并能生成结构清晰、覆盖度不错的测试用例代码,尤其在应对那些规则明确、但用例数量繁多的场景时,效率提升非常明显。

它不能完全替代测试工程师的创造性思维和对业务深层次逻辑的把握,但它绝对是一个强大的“副驾驶”。它能帮你快速完成那些重复性高、模式固定的测试设计工作,让你能把更多精力投入到更复杂的测试场景设计、性能测试、安全测试等领域。

如果你所在的团队正在为测试用例编写效率发愁,或者想探索AI在研发流程中的落地,不妨从这个小场景开始试试。搭建一个ChatGLM-6B服务,找几个典型的函数或接口练练手,感受一下AI带来的效率变化。相信你会和我一样,发现不少新的可能性。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐