ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Trae辅助查询功能自动化测试:从骨架搭建到数据库断言的完整实践

用Trae辅助查询功能自动化测试:从骨架搭建到数据库断言的完整实践 上周我们组一个查询接口的自动化用例挂了查了快两个小时最后发现原因特别蠢接口返回的商品名称里带小写mac而断言里写死了MacBook。数据没问题、接口没问题、断言逻辑单看也没问题但组合在一起就是必挂。这种破事干过测试的人都懂——查询功能的自动化测试真正麻烦的从来不是怎么发起请求而是怎么证明查出来的是对的那一批。Trae这类的AI编程工具出现之后很多人以为测试代码能一句话生成实际上确实能省很多事但前提是你自己得先想清楚查询测试里哪些断言是业务规则哪些是约束条件哪些是易碎实现。这篇内容就以Trae辅助实现查询功能自动化测试为主线从骨架搭建、参数构造、数据库校验、边界条件到报告集成把我实际用下来的一套做法完整过一遍。适合刚接触AI辅助测试的组员也适合被查询用例反复折腾过、想换个思路的同行。1. 查询功能自动化测试最难的环节往往不是测出来而是测对1.1 查询测试的两条主线接口查询与页面查询一提到查询功能很多测试第一反应是自己天天都在做——打开页面、输入关键词、点搜索、看列表有没有数据。但自动化测试里说的查询功能至少要拆成两条线来看。第一条是接口查询。被测对象是后端提供的查询接口比如GET /api/products?keywordmacpage1size10。这类测试关注的是入参怎么组合、返回结构是否稳定、返回结果和数据库里的真实数据能不能对上、分页总数对不对、排序是否稳定。第二条是页面查询。被测对象是前端交互层比如搜索框里输入内容、点击查询、列表重新渲染。这类测试关注的是异步请求有没有竞态、加载态和空态是否正常、翻页后参数是否保留、查询条件重置是否生效。很多团队只做了接口层面的查询测试觉得页面查询是点几下按钮没技术含量。但接口全绿、页面一操作就出毛病的案例我见过不少最典型的是分页组件把page从0开始计数而后端从1开始计数接口测通过是因为直接用参数请求前端页面因为传参错了根本查不出第二页。两条线都得有只是侧重点不同。1.2 为什么查询用例容易写崩数据污染、脆弱断言与组合爆炸查询功能测试用例翻车原因通常集中在三类问题上。第一类是数据污染。测试环境里数据不是静止的别的用例可能正在插入、修改、删除数据。你今天断言keywordlaptop返回25条明天开发在环境里灌了2条数据变成27条用例就红了。这不能怪环境只能怪测试用例没有把数据准备好再断言。第二类是脆弱断言。只断言HTTP 200接口直接返回{code: 500}也过断言列表不为空结果查出来的是完全无关的数据也过断言字段name等于一个硬编码值数据稍微变点就挂。这类断言的问题在于它校验的是请求没报错而不是查询逻辑正确。第三类是组合爆炸。查询条件一多参数组合呈指数上涨。一个查询表单有筛选条件A、B、C再加上分页、排序、关键词手工穷举根本测不完。没有参数化设计用例要么写到几十上百个函数要么只覆盖了最典型的几条路径。这些问题的共同点是测试设计必须先于代码生成。Trae再强也不知道你们的查询接口该返回什么业务结果。它能帮你把断言框架、数据库校验、参数化代码写得又快又规范但查这个关键词应该出哪几条记录这类业务规则必须由你来定义。2. 先用Trae把测试骨架搭起来别急着写断言2.1 为什么选Trae对话式生成、上下文理解与模型切换选Trae来辅助这套工作不是因为它是最好的AI编程工具而是它有几个对测试场景特别友好的点。首先是对话式生成。Trae的对话面板比单纯代码补全更适合搭建项目骨架你直接把用pytest写一个接口自动化测试项目包含conftest、用例目录、报告输出这类需求丢过去它生成的就是一整套目录和文件不用一个个手敲。其次是上下文理解。Trae能读取当前打开项目的目录结构和关键文件你在项目里选中conftest.py再提需求它生成的代码会和已有代码风格衔接上不会另起炉灶。这个能力在测试项目里很有价值因为测试代码高度依赖既有配置base_url从哪来、环境变量怎么读、登录token怎么拿都写在工程配置里。AI能看到这些配置生成的代码才不容易跑崩。第三是模型可切换。Trae的模型选择面板里Claude系列的代码理解和改写能力在实际写测试代码时比较顺手对长上下文的把握更稳。如果你用的是免费额度或者兑换码换来的积分建议把复杂的生成任务集中在一次对话里完成因为每次开启新对话上下文窗口就清空一次反复问AI还记得我之前的项目结构吗其实是浪费。2.2 用Trae生成的pytest项目骨架长什么样我在Trae里给过这样一个需求帮我生成一个pytest接口自动化测试项目骨架结构包含conftest.pysession和fixture、testcases目录、utils目录放数据库连接、requirements.txt、pytest.ini配置HTML报告被测API是RESTful风格base_url用环境变量BASE_URL传入。Trae生成的结构基本是这样的query_test_project/ ├── conftest.py ├── pytest.ini ├── requirements.txt ├── testcases/ │ └── test_query_products.py ├── utils/ │ └── db_utils.py └── report/其中conftest.py里最关键的是session和client的fixtureimport os import pytest import requests BASE_URL os.getenv(BASE_URL, https://api.example.com) pytest.fixture(scopesession) def session(): s requests.Session() s.headers.update({Content-Type: application/json}) token os.getenv(API_TOKEN) if token: s.headers.update({Authorization: fBearer {token}}) return s pytest.fixture() def client(session): yield sessionutils/db_utils.py里是数据库连接与查询函数import os import pymysql def get_conn(): return pymysql.connect( hostos.getenv(DB_HOST), useros.getenv(DB_USER), passwordos.getenv(DB_PASSWORD), databaseos.getenv(DB_NAME), ) def query_count(sql, paramsNone): conn get_conn() cur conn.cursor() try: cur.execute(sql, params or ()) row cur.fetchone() return row[0] if row else 0 finally: cur.close() conn.close()这套骨架本身不难但Trae一次生成后能省掉不少敲键盘的时间。我这里要提醒一句生成完先跑一遍pytest --collect-only确认所有用例能被正常收集再往里填断言逻辑。AI生成的文件偶尔会有导入错误先做收集检查能避免后面排查用例时被环境问题干扰。2.3 给Trae下提示词的技巧把约束条件写清楚Trae生成代码质量的分水岭在于你给的提示词是否明确。同样一句帮我写个查询用例下面两种问法得到的结果完全不同。低质量提示词帮我写一个查询接口的测试用例。高质量提示词我项目用pytestrequests被测接口是GET /api/products参数有keyword、page、size。请用pytest.mark.parametrize写参数化用例要求断言HTTP状态码为200断言返回body中的total字段与SQL COUNT查询结果一致分页数据断言返回列表长度不超过size不要对业务字段做强断言保持可读性。项目里conftest.py已经定义了client fixture直接使用。看到区别了吗高质量的提示词里包含了框架信息、接口信息、参数信息、断言要求、边界约束、代码风格Trae生成的代码基本可以直接进代码评审。在测试工程里我应该重点约束的就是断言边界。明确告诉AI哪些不能强断言往往比告诉它哪些要断言更管用。因为AI默认会把具体字段值写死而测试数据一变就挂。3. 核心用例实现从参数构造到数据库断言3.1 参数化组合别把用例写成十多个雷同函数查询功能的用例设计第一原则就是参数化。Trae生成参数化代码很利索但用例组合还是得按业务来设计。我一般会把查询参数分成几类参数类型例子覆盖要点基础必传参数page, size缺省、非法值、最小值、最大值查询关键词keyword精确、模糊、大小写、特殊符号、中文筛选条件category, status单选、多选、组合、互斥项排序参数sort, order升序、降序、多字段排序无参数请求不带任何参数默认返回与默认分页把这些组合交给Trae生成参数化用例时我的提示词长这样为GET /api/products接口写参数化用例使用pytest.mark.parametrizeparams定义如下组合1. keywordmacpage1size102. keyword 为空字符串3. keyword不存在的关键词期望total04. page05. page9996. size07. size1000。每组用例中若预期是正常查询断言status_code、total类型和list长度不要写死具体业务数据。生成后的用例类似import pytest pytest.mark.parametrize( params, [ {keyword: mac, page: 1, size: 10}, {keyword: , page: 1, size: 10}, {keyword: 不存在的名字, page: 1, size: 10}, {page: 0, size: 10}, {page: 999, size: 10}, {page: 1, size: 0}, {page: 1, size: 1000}, ], ) def test_query_products_basic(client, params): resp client.get(f{BASE_URL}/products, paramsparams) assert resp.status_code 200 body resp.json() assert total in body assert list in body assert isinstance(body[total], int) assert isinstance(body[list], list) if body[total] 0: assert len(body[list]) 0到这里还没完。参数化只解决了多组参数怎么组织的问题核心的每组参数该返回什么结果还得靠下面的数据库校验来解决。3.2 数据库层断言让接口返回和真实数据对账接口测试最常见的假绿就是接口返回了{total: 20}但数据库里实际符合条件的记录只有18条。接口吞了2条数据或者过滤逻辑写歪了光看接口自身看不出问题。真正有效的做法是做数据对账接口返回的total、list里的ID集合和数据库聚合查询的结果一一对比。我给Trae的需求是项目里utils/db_utils.py有query_count函数。请为/products接口写一个测试keyword参数从测试数据表读取断言接口返回的total等于数据库里SELECT COUNT(*) WHERE name LIKE %keyword%的结果并断言接口list里的product_id集合与数据库查询的id集合一致。对这类对账测试我通常会在测试数据准备阶段插入一批已知的、独立命名的数据然后用这些数据做精确断言。比如在fixture里插入5条qa_test_xxx相关的数据再断言查询接口返回total5这样就不受环境中其他数据干扰import pytest from utils.db_utils import get_conn, query_count pytest.fixture() def qa_test_data(): conn get_conn() cur conn.cursor() base_sql INSERT INTO products (name, category, status, created_at) VALUES (%s, %s, %s, NOW()) for i in range(5): cur.execute(base_sql, (fQA 自动化测试商品 {i}, QA_CATEGORY, 1)) conn.commit() cur.close() conn.close() yield # 清理删除测试数据 conn get_conn() cur conn.cursor() cur.execute(DELETE FROM products WHERE name LIKE QA 自动化测试商品 %) conn.commit() cur.close() conn.close() def test_qa_data_total_consistent(client, qa_test_data): keyword QA 自动化测试商品 resp client.get(f{BASE_URL}/products, params{keyword: keyword, page: 1, size: 10}) assert resp.status_code 200 body resp.json() db_total query_count( SELECT COUNT(*) FROM products WHERE name LIKE %s, (f%{keyword}%,) ) assert body[total] db_total这里有个细节数据清理也必须放在fixture里而且最好放在yield之后。这样即使断言失败teardown部分也会执行不会污染下一次运行。如果你用Trae生成fixture很容易得到只有yield没有清理逻辑的半成品我踩过一次坑测试数据越积越多最后全组人的查询用例都跟着遭殃。3.3 让Trae做重复劳动别让它替你做测试决策我在让Trae帮忙写测试代码时给自己定了一条规则AI负责把我确定的验证逻辑翻译成代码但验证什么必须我自己想清楚。举个例子keywordmac这个搜索场景测试预期到底是返回所有名字里含mac的商品还是返回名字精确等于mac的商品这个问题只有研发和产品的统一口径能回答AI猜不出来。如果你不确认就让它生成断言它大概率会写一个mac in item[name]而这个断言在高分页结果里可能完全不适用。再比如模糊搜索的执行逻辑有的后端是WHERE name LIKE %keyword%有的会用全文索引匹配分词结果有的是对拼音首字母也做匹配。同样的keyword在不同实现下返回结果集完全不同。数据库断言要写对前提是先看后端的SQL或确认产品规则而不是让AI推测。4. 查询边界条件分页、模糊搜索、空结果与超时4.1 分页与排序最容易接口通过但用例失败的地方分页是查询功能里翻车率最高的区域。很多人只断言第一页有数据结果翻到第三页发现接口返回了空列表或者total和page对不上。分页涉及几个关键校验点total必须代表全部符合条件的记录数不能是当前页的记录数每页条数不能超过请求的size除非后端有上限限制当page * size大于total时返回列表应该为空但total仍然不变排序字段要稳定翻页后不能出现同一条记录在不同页重复出现或者漏掉某条记录。我给Trae生成分页用例时会要求它同时验证total、page、size三个值之间的数学关系pytest.mark.parametrize( page,size, [ (1, 5), (2, 5), (1, 10), (3, 10), (1, 50), ], ) def test_query_pagination(client, page, size): resp client.get(f{BASE_URL}/products, params{page: page, size: size}) assert resp.status_code 200 body resp.json() total body[total] returned_items body[list] assert len(returned_items) size # 如果还有下一页当前页应该返回满一页数据 if total page * size: assert len(returned_items) size else: assert len(returned_items) max(0, total - (page - 1) * size)这段逻辑本身不复杂但Trae经常只生成列表长度小于等于size这半截剩下半截还要等于预期条数得自己补上否则用例就是个半拉子断言。4.2 模糊搜索的断言in和like都只是工具不是规则模糊搜索的自动化用例最大的坑在于大家都习惯用包含来表达预期搜索mac预期结果里包含MacBookMac mini。但实际业务里模糊搜索可能有你完全没预料到的行为。比如大小写问题数据库是utf8mb4_bin排序规则时LIKE %mac%是大小写敏感的换成utf8mb4_general_ci时MacBook的小写也能被mac匹配到。你的断言如果按大小写写死换个库就挂。再比如中文和拼音搜索很多电商系统支持拼音首字母搜索冰箱可能通过bx搜出来。这种规则没法用普通LIKE在数据库层复现数据库断言就不适用了只能依赖接口的已知返回结果做快照断言。我的建议是按业务实现方式分类处理纯LIKE模糊匹配可以用数据库COUNT对账带分词/拼音/同义词扩展的搜索不要用数据库对账改用已知数据集合断言在测试环境插入固定数据直接断言接口返回的ID集合等于预期ID集合搜索不带关键词断言返回全部数据且total等于环境总数。这类规则如果你不确定先把实测结果拉出来看一遍再决定断言方式不要想当然。4.3 空结果、异常输入与超时AI最常漏掉的三类边界AI生成查询测试用例时最常见的毛病是三件事都不覆盖空结果、异常入参、超时。空结果用例的价值在于验证前端和后端对没有数据的处理是否符合预期。接口层面空结果应该返回total0和空列表前端层面页面应该展示空态文案而不是加载失败。我一般会坚持加一条keyword完全不存在的随机字符串的用例而且随机字符串最好每次跑都不一样避免环境里真的出现同名数据import random import string def test_query_empty_result(client): random_keyword 不存在_ .join(random.choices(string.ascii_letters, k8)) resp client.get(f{BASE_URL}/products, params{keyword: random_keyword}) assert resp.status_code 200 body resp.json() assert body[total] 0 assert body[list] []异常入参则是传page0、page-1、page99999、size0、size100000、keyword超长到几千字符。不同后端对这些参数的处理差异极大有的返回400有的自动修正为默认值有的直接500。这类用例不需要断言统一结果而是把当前行为固化下来——一次测完把实际结果记录成断言防止后续改动把行为改坏。超时用例需要引入pytest的超时插件。查询接口一旦出现慢SQL或大表全扫往往不是报错而是卡住用例会挂很久才出现connection timeout。加个总超时能快速暴露问题pytest.mark.timeout(5) def test_query_should_finish_within_five_seconds(client): resp client.get(f{BASE_URL}/products, params{keyword: mac}) assert resp.status_code 2005. 跑完以后的事报告、失败定位与灰度数据5.1 用Trae配置HTML报告和失败现场日志自动化测试跑完没报告相当于踢完球不看录像。pytest的HTML报告是测试团队的标配Trae能一次性帮你把配置写好。在pytest.ini里加上[pytest] addopts --htmlreport/result.html --self-contained-html--self-contained-html会把CSS和JS都打包进HTML文件方便发给同事看。加上之后每次跑完pytest自动生成一个独立报告文件。但这只能让报告存在还不足以让失败可排查。我一般还会加一个失败时自动记录请求和响应的钩子把现场日志留全。Trae生成这个钩子的能力很强因为它只需要把pytest_runtest_makereport的固定逻辑套用过来。这里我直接给出一版可用的import json import pytest import requests pytest.hookimpl(tryfirstTrue, hookwrapperTrue) def pytest_runtest_makereport(item, call): outcome yield report outcome.get_result() if report.when call and report.failed: request_fixture item.funcargs.get(client) if request_fixture: for resp in request_fixture.history [request_fixture.get( item.funcargs.get(params, {}), timeout3 )]: pass这段代码只是个雏形实际用的时候你需要把最近一次请求的URL、请求参数、响应状态码、响应体文本写进report.longrepr。详细实现我就不贴全了每个项目的请求方式不一样重点是让Trae理解你的client和断言结构让它生成的失败日志包含当时请求了什么、后端返回了什么。否则报告里只有一行AssertionError: 25 ! 27你连是哪个环境的数据都回忆不起来。5.2 当Trae生成的断言和真实业务冲突时该听谁的这个问题我几乎每次用AI辅助写测试都会遇到。最典型的是三种第一种是精度冲突。接口返回{price: 39.999999}Trae生成的断言是assert body[price] 40。接口逻辑明明是因为浮点误差强行断言等于40就是在过拟合。正确的做法是断言价格在某个精度范围内或者断言格式化后的字符串。第二种是时间冲突。查询列表里有一个created_at字段Trae可能默认断言它等于2024-01-01 00:00:00这种固定值。只要测试环境数据一更新用例就挂。对这种字段应该断言能解析为合法时间格式就足够。第三种是空值冲突。数据库允许NULL的字段接口层可能返回null也可能返回空字符串还可能直接不返回该字段。Trae生成断言时大概率会按返回里必须有这个字段来写。你需要根据接口协议文档确定到底哪种是正常的。碰到这几种冲突我处理的原则是AI是协作方不是决策方。让Trae帮你生成一个备选方案可以但断言规则一定要以研发给出的接口定义和业务文档为准。多花两分钟翻文档比跑完用例后争议半天省时间多了。5.3 把查询测试接入定时执行让它变成常态化监控查询功能是最容易回归的功能之一今天全绿不代表下周还绿因为后端会改SQL、改索引、改缓存策略。我建议至少把核心查询用例放到一个固定的执行频率里去跑。做法可以用最简单的方式在CI里加一个定时任务每天凌晨跑一次全量查询测试出报告发到测试群里。也可以参考热搜里提到的serverless定时任务思路用一个云函数定时触发一个封装好的测试脚本再把结果写入在线表格或推送通知。不过这一步属于基建先把用例本身写稳固再上定时执行否则每天凌晨收到十几条失败通知大家只会越来越麻木。6. 我在实际使用中的几条经验用Trae辅助自动化测试写了两个多月最大的体会是它把写代码的环节压缩到几乎为零但把定义正确性的环节放大了。以前手写查询用例不小心漏了某个边界条件的概率很高现在AI生成代码又快又全但你要是不告诉它业务规则它可以生成一套看着很专业、实际什么都没验证的断言出来。我现在的习惯是每次让Trae生成完查询用例强制自己过三个问题这段断言如果数据量翻十倍还会稳定成立吗如果研发改了查询逻辑但结果对用户是无感的这个用例会不会误报如果这个用例跑挂了光看报错信息我能不能定位到是参数问题、数据问题还是后端逻辑问题还有个很实用的小技巧在提示词末尾主动加一句不要优化我的代码降低复杂度优先可读性。Trae默认有把代码改高级的倾向但测试代码要的是稳定、简单、一眼能看明白。复杂化的工厂类、反射调用、动态代理出现在测试工程里基本是灾难。最后分享一个让查询测试效果翻倍的做法把测试数据里的keyword设计得和环境数据天然隔离。比如所有自动化测试数据都用QA_TEST_开头断言的时候只关心这个前缀下的数据数据库对账也带着这个前缀做过滤。这样一来环境里闹翻天也影响不到你的查询断言用例的稳定性会提升一大截。这招比起让Trae写再多的智能等待动态断言都管用——测试数据可控了查询测试的断言才能真的硬起来。
返回列表