
上周接到一個需求新版 App 要上三個新增功能領(lǐng)導(dǎo)給了兩周時間要求自動化回歸必須跟上不能只靠手工點點點。我當時的方案是接口層用 pytest 和 requests 做覆蓋UI 層用 Appium 跑回歸再掛一個定時任務(wù)每天自動執(zhí)行。實際動手的時候大部分代碼是讓 Trae 幫我寫和改的。Trae 是一個 AI 編程 IDE更準確地說它可以當作一個能理解整個項目上下文的輔助引擎來用。在自動化測試這種樣板代碼多、結(jié)構(gòu)重復(fù)的場景里它的效率優(yōu)勢比寫業(yè)務(wù)代碼更明顯。這篇文章就把整個過程拆開講從 pytest 接口框架搭建到 Appium 回歸再到定時任務(wù)和報告通知全部是可落地、可以直接復(fù)現(xiàn)的內(nèi)容希望能給你省掉不少試錯成本。1. Trae 在自動化測試里的定位與關(guān)鍵功能1.1 自動化測試的痛點不是代碼難寫而是變更太頻繁很多人以為自動化測試的難點是 Python 語法、pytest 用法或者 Appium 的 API我實際干下來的感受完全不是這樣。真正的瓶頸是“維護”接口字段一變斷言代碼跟著改頁面改版所有頁面對象里的定位符要過一遍環(huán)境地址一換公共配置到處找跑完還要整理報告、盯失敗原因。技術(shù)上每件事都不難但串在一起非常消耗時間。這正好是 AI 輔助發(fā)揮優(yōu)勢的地方。Trae 和傳統(tǒng)編輯器最大的區(qū)別是它能感知上下文我打開整個測試項目在對話框里直接說“把 requests 請求統(tǒng)一封裝成 HttpClientheader 從全局配置文件讀取”它能基于當前目錄結(jié)構(gòu)去生成而不是給你一段孤立的示例代碼。更關(guān)鍵的是跨文件感知——新增功能通常不是只改一個文件入口、用例、數(shù)據(jù)、斷言四處都要動Trae 可以把這四處關(guān)聯(lián)起來一起改。換句話說它適合的不是“寫一段教程代碼”而是“對現(xiàn)有項目做工程化改造”。1.2 這次實測下來最值得用的三個 Trae 能力我這次用得最多、效果也最明顯的功能有三個。第一個是會話式代碼生成與重構(gòu)。以前寫一個接口的完整用例從請求封裝到數(shù)據(jù)驅(qū)動到斷言至少要寫四五個文件?,F(xiàn)在把接口文檔粘貼給 Trae然后要求“按項目現(xiàn)有風格生成 pytest 用例使用 yaml 數(shù)據(jù)驅(qū)動”它生成的結(jié)構(gòu)基本能直接跑。我在實際過程中發(fā)現(xiàn)只要先給它看一兩個現(xiàn)有文件的寫法它生成的風格和團隊規(guī)范高度一致。第二個是跨文件搜索與批量修改。Trae 可以基于自然語言定位代碼位置例如我輸入“找出所有直接調(diào)用 requests.post 的測試文件改成走 HttpClient”它會把涉及的文件列出來逐文件改完。這個能力在做新增功能回歸時特別有用因為新功能往往會引入新的公共邏輯需要批量遷移舊代碼。第三個是 Trae CLI。命令行模式下可以執(zhí)行 AI 任務(wù)我主要用來做兩件事一是提交代碼前自動跑一遍關(guān)鍵字簡單檢查二是把 pytest 失敗的堆棧喂給 CLI讓 AI 先給出一個“失敗原因初步判斷”。引入到日常工作流后每天早上看報告的成本低了一個量級。話說回來Trae 也不是萬能的。它生成代碼的能力取決于你給的信息量。接口文檔、現(xiàn)有代碼風格、依賴庫版本這三樣越明確產(chǎn)出越靠譜。后面每章我都會講到怎么通過 Prompt 把信息喂得更全。2. 用 Trae 從 0 到 1 搭建 pytest 接口自動化框架2.1 環(huán)境準備版本鎖定比“最新版”更重要如果你維護的是自己一個人用的腳本直接pip install pytest requests沒問題。但如果是團隊項目我強烈建議把版本鎖死在 requirements.txt 里。原因很簡單Trae 在生成代碼時會假設(shè)某些方法存在不同版本的 requests 和 pytest 差異雖然不大但allure-pytest恰好遇到過兼容性問題版本不鎖今天能跑明天同事一把依賴升級全部用例報錯查起來非常浪費時間。我這次用的依賴版本組合如下寫在 requirements.txt 里pytest7.4.0 requests2.31.0 PyYAML6.0 allure-pytest2.13.2 pytest-xdist3.3.1 appium-python-client2.11.0初始化環(huán)境python -m venv .venv source .venv/bin/activate pip install -r requirements.txt這里有一個實操細節(jié)新增功能往往還要操作數(shù)據(jù)庫做數(shù)據(jù)準備建議順手裝一個pymysql但不要一開始就裝一堆用不到的庫。我踩過坑Trae 看你環(huán)境里什么庫都有容易生成一些很炫但不必要的代碼比如用 SQLAlchemy 連數(shù)據(jù)庫其實你只需要一個fetchone()。2.2 公共請求模塊讓 Trae 生成可復(fù)用的 HTTP 客戶端接口測試框架里最核心的模塊就是 HTTP 客戶端。它的作用是統(tǒng)一處理 base_url、token、超時、日志和請求頭避免每個用例里裸調(diào)requests.get/post。我給 Trae 的 Prompt 大概是這樣的“在 common/http_client.py 里生成一個 HttpClient 類使用 requests.Session支持傳入 base_url 和 token提供 request 方法日志要打出請求方法、URL、狀態(tài)碼和耗時?!鄙珊笪疑晕⒄{(diào)整最終代碼如下# common/http_client.py import logging import time import uuid import requests class HttpClient: def __init__(self, base_url: str, token: str ): self.session requests.Session() self.base_url base_url if token: self.session.headers.update({Authorization: fBearer {token}}) self.session.headers.update({Content-Type: application/json}) def request(self, method: str, path: str, **kwargs): url f{self.base_url}{path} kwargs.setdefault(timeout, 15) request_id uuid.uuid4().hex[:8] logging.info([%s] %s %s begin, request_id, method.upper(), url) start time.time() response self.session.request(method, url, **kwargs) cost round((time.time() - start) * 1000, 2) logging.info([%s] status%s cost%sms, request_id, response.status_code, cost) return response為什么用requests.Session而不是直接requests.get因為 Session 會自動管理連接池和 cookie接口自動化里登錄態(tài)依賴 cookie 的場景特別多比如你先調(diào)登錄接口后續(xù)接口都帶著 session這比每個請求手動傳 cookie 干凈得多。2.3 數(shù)據(jù)驅(qū)動用例新增功能場景參數(shù)化新增功能的需求往往是同一接口要覆蓋多組參數(shù)組合比如“訂單改簽”這個場景可能需要測試不同的改簽時間、改簽原因、訂單狀態(tài)。如果每個組合寫一個函數(shù)代碼會膨脹到?jīng)]法維護。正確做法是用 yaml 保存測試數(shù)據(jù)用parametrize實現(xiàn)數(shù)據(jù)驅(qū)動。先建配置文件# config/config.yaml base_url: https://api.example.com token: your_token_here新建一個通用讀取工具# common/yaml_util.py import yaml from pathlib import Path def read_yaml(path: str): abs_path Path(__file__).parent.parent / path with open(abs_path, r, encodingutf-8) as f: return yaml.safe_load(f)然后是測試數(shù)據(jù)文件# data/order_change.yaml - name: 改簽到當天成功 order_id: A1001 change_time: 2025-06-01 14:00 reason: 個人行程變更 - name: 改簽到過去時間應(yīng)該失敗 order_id: A1002 change_time: 2025-01-01 08:00 reason: 測試用例# test_cases/test_order_change.py import pytest from common.http_client import HttpClient from common.yaml_util import read_yaml client HttpClient(https://api.example.com, your_token_here) pytest.mark.parametrize(case, read_yaml(data/order_change.yaml)) def test_order_change(case): payload { order_id: case[order_id], change_time: case[change_time], reason: case[reason], } resp client.request(POST, /api/order/change, jsonpayload) assert resp.status_code 200 data resp.json() assert data[code] 0 if case[reason] : assert data[data][status] REJECTED else: assert data[data][status] CHANGED這里我特別想讓 Trae 幫忙的是“生成用例的邊界條件”。你直接問它“這個接口還有什么邊界情況”它會根據(jù)接口文檔給出空值、超長字符串、非法枚舉等補充建議比自己坐那兒想全快很多。不過要留個心眼AI 給的邊界條件不一定都對應(yīng)當前版本最后還是得對一下產(chǎn)品需求和接口文檔。2.4 斷言與報告用例寫得再好報告難看也白搭斷言是自動化測試的靈魂。很多人只寫一句assert resp.status_code 200這種用例基本是自欺欺人。接口返回 200 只代表請求通了不代表業(yè)務(wù)成功。我讓 Trae 生成用例的時候會明確要求“斷言必須包含業(yè)務(wù)字段校驗”比如 code、status、關(guān)鍵業(yè)務(wù)字段的值。報告方面我習(xí)慣用 Allure因為它的展示效果對非技術(shù)人員友好領(lǐng)導(dǎo)能一眼看懂有多少用例通過、失敗在哪個模塊。項目根目錄建一個 pytest.ini[pytest] testpaths test_cases addopts -s -q --alluredirallure-results --clean-alluredir運行一次pytest allure generate allure-results -o allure-report --clean allure open allure-report生成報告這個過程也可以讓 Trae 寫成一個腳本后面定時任務(wù)直接調(diào)用不用每次手敲命令。這一步看起來不起眼但它決定了自動化測試能不能長期堅持下來——報告越容易看團隊越愿意用用例越會被維護。3. Trae 輔助 Appium 完成移動端新增功能回歸3.1 Appium 環(huán)境準備與 capability 配置移動端回歸比接口層麻煩因為涉及真機或模擬器、Appium Server、UiAutomator2 三套環(huán)境。我這次用 Appium 2.0 配合 UiAutomator2capability 配置如下# app/config.py desired_caps { platformName: Android, appium:automationName: UiAutomator2, appium:deviceName: emulator-5554, appium:appPackage: com.example.app, appium:appActivity: .MainActivity, appium:noReset: True, appium:autoGrantPermissions: True, }有幾個容易忽略的點。第一noReset一定設(shè)成 True否則每次跑用例都清 App 數(shù)據(jù)登錄態(tài)全丟。第二autoGrantPermissions建議打開否則彈窗權(quán)限會把用例打斷。第三模擬器要提前起來并且確保adb devices能看到設(shè)備再啟動 Appium。這些配置我自己寫容易忘所以直接讓 Trae 生成一個driver_fixture.py要求它把啟動、等待、退出都封裝進 fixture。生成的代碼大概如此# app/driver_fixture.py import pytest from appium import webdriver from app.config import desired_caps pytest.fixture(scopefunction) def driver(): driver webdriver.Remote(http://127.0.0.1:4723/wd/hub, desired_caps) driver.implicitly_wait(5) yield driver driver.quit()這里我特意把scope設(shè)置為function因為 UI 用例之間的狀態(tài)經(jīng)常互相干擾一個用例結(jié)束就重啟會話雖然慢一點但可靠性高。后續(xù)如果追求執(zhí)行速度再按模塊去調(diào)整 fixture 作用域。3.2 BasePage 封裝與 Page Object 改造Appium 用例最怕的就是定位符寫成一坨直接鋪在用例里。頁面一改版幾十個用例全部要動。所以 Page Object 模式是移動端自動化標配。我先讓 Trae 生成一個基礎(chǔ) BasePage把通用操作抽出來# app/page/base_page.py from appium.webdriver.common.appiumby import AppiumBy from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class BasePage: def __init__(self, driver): self.driver driver self.wait WebDriverWait(driver, 15) def find_element(self, locator): return self.wait.until(EC.presence_of_element_located(locator)) def click(self, locator): self.find_element(locator).click() def send_keys(self, locator, text): element self.find_element(locator) element.clear() element.send_keys(text) def swipe_up(self, times1): size self.driver.get_window_size() x size[width] // 2 start_y int(size[height] * 0.8) end_y int(size[height] * 0.3) for _ in range(times): self.driver.swipe(x, start_y, x, end_y, 800)封裝完 BasePage 之后我讓 Trae 按同樣的風格為新增功能頁面生成 Page Object。比如“訂單詳情頁”新增了“預(yù)計送達時間”展示我就把大概的頁面布局描述給它它生成了類似這樣的類# app/page/order_detail_page.py from appium.webdriver.common.appiumby import AppiumBy from app.page.base_page import BasePage class OrderDetailPage(BasePage): arrive_time_locator (AppiumBy.ID, com.example.app:id/tv_arrive_time) def get_arrive_time(self): return self.find_element(self.arrive_time_locator).text生成之后我做的第一件事是檢查定位符是不是真的對應(yīng)開發(fā)給到的 resource-id。AI 不會騙你但它只能根據(jù)你的描述生成一個“大概率的正確值”。3.3 一條完整的下單回歸用例是怎么組合出來的新增功能回歸不是只測新增的那幾個點而是要確認新增功能沒有破壞老流程。所以我說一下完整用例的組織方式。一個端到端用例通常包含登錄、跳轉(zhuǎn)、操作、斷言四步。借用 Page Object 后用例層可以寫得非常干凈# test_cases/test_buy_flow.py import pytest from app.page.home_page import HomePage from app.page.order_detail_page import OrderDetailPage from app.page.order_page import OrderPage pytest.mark.usefixtures(driver) class TestBuyFlow: def test_create_order(self, driver): home HomePage(driver) home.click_category(數(shù)碼) home.click_first_product() detail OrderDetailPage(driver) detail.click_buy_now() order OrderPage(driver) order.confirm_order() assert order.is_order_success() # step5: 校驗新增的預(yù)計送達時間 assert detail.get_arrive_time() ! , 預(yù)計送達時間不應(yīng)為空這種結(jié)構(gòu)的好處是用例本身只描述業(yè)務(wù)行為真正的定位符和操作細節(jié)全部在 Page Object 層。新增功能上線后主要改動落到 page 層和少量新用例上而不是翻遍所有歷史用例。有一點要提醒UI 自動化用例的斷言要比接口層更克制。不要斷言太多細節(jié)否則每條用例都異常脆弱。UI 層只要能證明“核心流程沒壞、關(guān)鍵信息有展示”就夠了把精確的字段校驗留給接口層。這是我自己吃過大虧之后才想明白的分工。4. 讓用例每天自動跑serverless 定時任務(wù)與報告通知4.1 為什么需要定時任務(wù)接口和 UI 用例都寫完了如果每次都要人手動執(zhí)行自動化測試的價值就砍掉一半。我這邊的要求是每天凌晨自動跑全量回歸早上來公司直接看報告。實現(xiàn)方式可以選 Linux 服務(wù)器上的 crontab也可以選云廠商的 serverless 定時觸發(fā)器。兩者原理差不多都是按 cron 表達式在指定時間點執(zhí)行一個命令區(qū)別只是有沒有臺服務(wù)器。如果你手頭正好有測試服務(wù)器用 crontab 最直接。如果是團隊沒有常駐服務(wù)器Serverless 定時任務(wù)更輕量按次計費還不用維護機器。不管哪種都需要一個“一鍵執(zhí)行”的入口腳本。4.2 一鍵運行腳本與定時觸發(fā)器配置我寫了一個run_daily.py它的職責是跑 pytest、生成報告、根據(jù)結(jié)果發(fā)通知# run_daily.py import subprocess import sys from common.notify import send_wecom_webhook if __name__ __main__: code pytest_main() # 生成 allure 報告 subprocess.run([allure, generate, allure-results, -o, allure-report, --clean]) if code 0: send_wecom_webhook(https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyxxxx, 自動化測試全部通過) else: send_wecom_webhook(https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyxxxx, 自動化測試有失敗請查看報告) sys.exit(code) def pytest_main(): import pytest return pytest.main([-s, -q, test_cases, --alluredirallure-results, --clean-alluredir])如果使用 crontab配置如下0 1 * * * cd /opt/autotest python run_daily.py logs/daily.log 21這個 cron 表達式的意思是每天凌晨 1 點執(zhí)行一次。關(guān)鍵點是一定要把日志重定向到文件里。實際出現(xiàn)過定時任務(wù)跑掛了但因為在終端里直接跑正常所以一直沒發(fā)現(xiàn)直到加了日志才知道是環(huán)境變量缺失導(dǎo)致命令找不到。日志是排查定時任務(wù)問題的最好朋友。如果走 Serverless原理是一樣的在控制臺創(chuàng)建函數(shù)運行環(huán)境選 Python入口設(shè)置為run_daily.index然后配置定時觸發(fā)器cron 表達式寫0 1 * * * *。函數(shù)執(zhí)行完會返回日志比服務(wù)器上查日志更方便。4.3 測試報告輸出與機器人通知報告生成之后不能讓工程師自己登錄服務(wù)器看。最簡單的做法是接一個企業(yè)微信群機器人通過 webhook 把結(jié)果文本推送到群里。核心代碼# common/notify.py import requests def send_wecom_webhook(webhook_url: str, content: str): body { msgtype: text, text: {content: content} } requests.post(webhook_url, jsonbody, timeout10)通知文本不要只寫“成功/失敗”我會讓 Trae 生成一段小工具從 allure-results 目錄里統(tǒng)計失敗用例名然后拼進去。這樣群里看到消息就知道是哪個模塊掛了不用再打開報告翻半天。通知這個環(huán)節(jié)很容易被忽略但它決定了自動化測試能不能“被”用起來。5. 常見問題與排查技巧實錄5.1 我踩過的幾個坑第一Trae 生成了代碼里不存在的 API。比如它可能在 pytest 里用了一個自定義 fixture但我沒定義同名 fixture跑用例直接報錯?,F(xiàn)在我每次讓 Trae 生成代碼都會加一句“只使用項目已有依賴庫和已有文件不得假設(shè)額外模塊存在”這句話能顯著減少返工。第二生成用例時fixture 作用域沒搞清導(dǎo)致數(shù)據(jù)污染。Trae 默認生成的 fixture 往往是scopefunction但接口自動化里登錄態(tài)可能希望是session級別。如果混用很容易出現(xiàn)“用例單獨跑通過全量跑失敗”的詭異問題。建議生成后立刻檢查 fixture 的作用域。第三Appium 定位等待方式不對。presence_of_element_located和visibility_of_element_located是兩回事某些情況下元素在 DOM 里存在但不可見用 visibility 才會等到正確狀態(tài)。AI 默認生成的代碼不一定符合實際場景這條尤其要人工校驗。第四定時任務(wù)運行環(huán)境與本地不一致。本地跑 pytest 能找到命令云函數(shù)環(huán)境里卻找不到 spawn 的 allure因為環(huán)境變量不同。我的解決方法是腳本里用絕對路徑或者把 allure 調(diào)用封裝成在函數(shù)內(nèi)通過shutil.which探測。5.2 問題排查速查表我把這段時間遇到的典型問題整理成一個表方便你直接對照現(xiàn)象可能原因排查看法pytest 一個用例都沒執(zhí)行testpaths 配錯或文件名不是 test_ 開頭先pytest --collect-only看收集結(jié)果用例單獨跑通過全量跑失敗fixture 作用域沖突或用例間共享數(shù)據(jù)被改寫檢查是否有 module 級 fixture 修改了全局狀態(tài)Appium 定位不到元素resource-id 對不上或元素在另一個 WebView用 Appium Inspector 看當前頁面 UI 層級定時任務(wù)沒跑cron 時間格式錯誤、腳本無執(zhí)行權(quán)限、環(huán)境變量缺失先看腳本日志再手動執(zhí)行入口腳本對比Trae 生成的斷言太弱沒有明確要求業(yè)務(wù)斷言重新生成時要求“必須校驗 code 和狀態(tài)字段”Allure 報告為空allure-results 路徑不一致確認 pytest.ini 的 addopts 與實際報告路徑一致5.3 給新人的一個團隊協(xié)作建議如果你不是一個人維護這套東西建議在項目里沉淀一個docs/prompt-template.md把常用的 Trae 提示詞規(guī)范寫下來。比如“生成用例時必須使用 data 目錄下的 yaml 數(shù)據(jù)驅(qū)動”“發(fā)送請求必須走 HttpClient”“不得假設(shè)額外依賴庫”。這樣不管是老同事還是新同學(xué)生成的代碼風格都是一致的Long-term維護成本會低很多。另一個小技巧是讓 Trae 順手生成“用例和需求單號”的映射注釋。新增功能往往對應(yīng)產(chǎn)品需求單把單號寫進用例注釋里將來需求變更、字段調(diào)整時你能快速找到是哪批用例要改。這個細節(jié)幫我省過好幾次返工。最后說點個人體會。Trae 這類工具真正改變的是我做自動化測試的手感以前碰到大改版至少兩三天耗在改定位符和公共方法上現(xiàn)在可以把相當一部分機械工作丟給 AI我集中精力看用例邏輯和邊界條件。但記得一條AI 寫出來的測試代碼一定要親自走一遍數(shù)據(jù)流尤其要檢查斷言是不是真的能抓到問題。如果斷言寫得淺AI 生成一百個用例也救不了后臺那幾行 bug。希望這篇攻略能讓你下次接新增功能時少熬幾個夜。