当前位置: 首页 > news >正文

hyperf API 契约测试平台开源完整流程(从 0 到持续维护)==写一个开源项目全流程

一套能直接开源落地的 Hyperf API 契约测试平台方案: 目标是做成一个独立服务,支持 导入契约(OpenAPI)、执行回归测试、比对响应、生成报告、持续集成触发。 ---1)项目定位(MVP) 先做5个能力:1. 契约管理(OpenAPI JSON/YAML,版本化)2. 用例管理(从契约生成 + 手工补充)3. 执行引擎(发请求、断言状态码/JSON Schema/关键字段)4. 报告中心(通过/失败、差异详情)5. CI 接入(PR 或主干提交自动跑) ---2)仓库结构(建议) hyperf-contract-testing/ ├─ app/ │ ├─ Controller/ │ │ ├─ ContractController.php │ │ ├─ TestCaseController.php │ │ └─ RunController.php │ ├─ Model/ │ │ ├─ ApiContract.php │ │ ├─ ContractTestCase.php │ │ └─ ContractTestRun.php │ ├─ Service/ │ │ ├─ OpenApiParser.php │ │ ├─ ContractImporter.php │ │ ├─ ContractTestRunner.php │ │ ├─ SchemaAssertService.php │ │ └─ DiffService.php │ ├─ Job/ │ │ └─ RunContractSuiteJob.php │ └─ Command/ │ └─ RunContractCommand.php ├─ config/autoload/ │ ├─ routes.php │ ├─ databases.php │ └─ async_queue.php ├─ migrations/ ├─ tests/ ├─ .github/workflows/ci.yml ├─ docker-compose.yml ├─ README.md ├─ SECURITY.md └─ LICENSE ---3)0初始化composercreate-project hyperf/hyperf-skeleton hyperf-contract-testingcdhyperf-contract-testingcomposerrequire hyperf/db-connection hyperf/database hyperf/async-queue hyperf/rediscomposerrequire guzzlehttp/guzzle opis/json-schema symfony/yamlcomposerrequire--devphpunit/phpunit phpstan/phpstan friendsofphp/php-cs-fixer ---4)核心数据表(迁移) api_contracts 契约版本 Schema::create('api_contracts',function(Blueprint$table){$table->bigIncrements('id');$table->string('service_name',100);$table->string('version',50);// 如 v1.2.0 / commit sha$table->string('source_type',20)->default('openapi');$table->longText('raw_content');// 原始 openapi 文档$table->timestamps();$table->unique(['service_name','version']);});contract_test_cases 用例 Schema::create('contract_test_cases',function(Blueprint$table){$table->bigIncrements('id');$table->unsignedBigInteger('contract_id');$table->string('case_name',150);$table->string('method',10);$table->string('path',255);$table->json('headers')->nullable();$table->json('query_params')->nullable();$table->json('request_body')->nullable();$table->unsignedInteger('expected_status')->default(200);$table->json('expected_schema')->nullable();$table->json('expected_fragments')->nullable();// 关键字段断言$table->tinyInteger('enabled')->default(1);$table->timestamps();$table->index(['contract_id','enabled']);});contract_test_runs 执行记录 Schema::create('contract_test_runs',function(Blueprint$table){$table->bigIncrements('id');$table->unsignedBigInteger('contract_id');$table->string('triggered_by',50)->default('manual');// manual/ci$table->unsignedInteger('total')->default(0);$table->unsignedInteger('passed')->default(0);$table->unsignedInteger('failed')->default(0);$table->tinyInteger('status')->default(1);//1=running,2=done,3=error$table->longText('report_json')->nullable();$table->timestamps();});---5)核心代码(可直接改)5.1OpenAPI 导入服务 app/Service/ContractImporter.php<?php declare(strict_types=1);namespace App\Service;use App\Model\ApiContract;use App\Model\ContractTestCase;use Hyperf\DbConnection\Db;class ContractImporter{publicfunction__construct(private OpenApiParser$parser){}publicfunctionimport(string$service, string$version, string$raw): int{returnDb::transaction(function()use($service,$version,$raw){$contract=ApiContract::query()->create(['service_name'=>$service,'version'=>$version,'raw_content'=>$raw,'source_type'=>'openapi',]);$cases=$this->parser->generateCases($raw);foreach($casesas$c){ContractTestCase::query()->create(['contract_id'=>$contract->id,'case_name'=>$c['case_name'],'method'=>$c['method'],'path'=>$c['path'],'expected_status'=>$c['expected_status']??200,'expected_schema'=>$c['expected_schema']?? null,]);}return(int)$contract->id;});}}5.2Runner(执行 + 断言 + 差异) app/Service/ContractTestRunner.php<?php declare(strict_types=1);namespace App\Service;use App\Model\ContractTestCase;use GuzzleHttp\Client;class ContractTestRunner{publicfunction__construct(private SchemaAssertService$schemaAssert, private DiffService$diffService){}publicfunctionrunOne(ContractTestCase$case, string$baseUrl): array{$client=new Client(['timeout'=>10,'http_errors'=>false]);$resp=$client->request($case->method, rtrim($baseUrl,'/').$case->path,['headers'=>$case->headers ??[],'query'=>$case->query_params ??[],'json'=>$case->request_body ?? null,]);$actualStatus=$resp->getStatusCode();$bodyText=(string)$resp->getBody();$json=json_decode($bodyText,true);$errors=[];if($actualStatus!==(int)$case->expected_status){$errors[]="status mismatch: expected={$case->expected_status}, actual={$actualStatus}";}if(!empty($case->expected_schema)){$schemaErrors=$this->schemaAssert->validate($json,$case->expected_schema);$errors=array_merge($errors,$schemaErrors);}if(!empty($case->expected_fragments)){$fragErrors=$this->diffService->assertFragments($json,$case->expected_fragments);$errors=array_merge($errors,$fragErrors);}return['case_id'=>$case->id,'case_name'=>$case->case_name,'pass'=>count($errors)===0,'errors'=>$errors,'actual_status'=>$actualStatus,'actual_body'=>$json,];}}5.3JSON Schema 断言 app/Service/SchemaAssertService.php<?php declare(strict_types=1);namespace App\Service;use Opis\JsonSchema\Validator;class SchemaAssertService{publicfunctionvalidate(mixed$data, array$schema): array{$validator=new Validator();$result=$validator->validate(json_decode(json_encode($data)), json_decode(json_encode($schema)));if($result->isValid()){return[];}return['schema validation failed'];}}5.4任务异步执行 app/Job/RunContractSuiteJob.php<?php declare(strict_types=1);namespace App\Job;use App\Model\ContractTestRun;use App\Model\ContractTestCase;use App\Service\ContractTestRunner;use Hyperf\AsyncQueue\Job;class RunContractSuiteJob extends Job{publicfunction__construct(public int$runId, public int$contractId, public string$baseUrl){}publicfunctionhandle(): void{$run=ContractTestRun::query()->findOrFail($this->runId);$runner=di(ContractTestRunner::class);$cases=ContractTestCase::query()->where('contract_id',$this->contractId)->where('enabled',1)->get();$report=[];$passed=0;foreach($casesas$case){$r=$runner->runOne($case,$this->baseUrl);$report[]=$r;if($r['pass'])$passed++;}$run->total=count($report);$run->passed=$passed;$run->failed=$run->total -$passed;$run->status=2;$run->report_json=json_encode($report, JSON_UNESCAPED_UNICODE);$run->save();}}---6)API 路由(最小闭环) config/autoload/routes.php Router::addGroup('/api/contracts',function(){Router::post('/import',[App\Controller\ContractController::class,'import']);Router::get('/{id:\d+}/cases',[App\Controller\TestCaseController::class,'list']);Router::post('/{id:\d+}/run',[App\Controller\RunController::class,'run']);Router::get('/runs/{runId:\d+}',[App\Controller\RunController::class,'detail']);});---7)CI 集成(开源必须) .github/workflows/ci.yml 最少包含:1.composervalidate2. php-cs-fixer --dry-run3. phpstan4. phpunit5. 启动 demo API + 本平台,跑一次契约执行,校验失败时 CI fail ---8)开源发布流程(完整)1. LICENSE:MIT 或 Apache-2.02. README:5 分钟启动、架构图、示例 OpenAPI、报告截图3. SECURITY.md:漏洞提交流程4. GitHub Issue 模板:bug / feature / contract-support5. 首版 Tag:v0.1.0(声明 API 仍可能调整)6. 每次 Release 写清楚:新增断言能力、破坏性变更、迁移步骤 ---9)持续维护路线图 - v0.1: OpenAPI 导入 + 基础执行 + 报告 - v0.2: 环境矩阵(dev/staging/prod-like)+ webhook 通知 - v0.3: 历史趋势(通过率、接口稳定性排行) - v1.0: 多项目隔离、权限模型、插件化断言器(gRPC/GraphQL) ---10)最容易踩坑的点1. 用例直接绑定真实动态数据,导致频繁误报2. 契约变更没版本化,历史报告不可追溯3. 只比状态码,不比 schema 和关键字段4. 报告无原始响应,排障困难5. CI 超时控制缺失,整条流水线卡住 --- 这套结构可以直接做开源首版。先把 导入器 + Runner + 报告 + CI 跑通,社区就能用;后续再补通知、趋势和权限。
http://www.jsqmd.com/news/701967/

相关文章:

  • Kurtosis封装AutoGPT:一键部署AI智能体,告别环境依赖地狱
  • Qwen-Image镜像实测:RTX4090D环境下的图像理解与对话体验
  • ccmusic-database/music_genre实战案例:在线音乐教育平台智能教案生成流派依据模块
  • 2026权威翻译服务名录:国内翻译公司十强/正规翻译公司/翻译公司报价/翻译公司推荐/翻译机构/药品类翻译/药品翻译/选择指南 - 优质品牌商家
  • Phi-3.5-mini-instruct企业落地指南:从单实例测试到生产环境多实例编排
  • hyperf 事故复盘与演练平台(工程版) 开源完整流程(从 0 到持续维护)=)====写一个开源项目全流程
  • 5分钟快速上手:让Windows任务栏焕然一新的终极美化方案
  • AI编码助手如何实现Web质量优化:从Lighthouse审计到工程实践
  • 基于FastAPI与Hugging Face构建高效LLM API服务
  • Qianfan-OCR多场景落地:支持A4扫描件/手机截图/证件照/低分辨率图像
  • Real Anime Z在同人创作中的应用:3步生成可商用级二次元角色原画
  • 2026在线气体分析哪家靠谱:氨逃逸测定/氯化氢气体在线测量/氯化钠气体在线测量/激光气体分析仪/激光气体分析设备/选择指南 - 优质品牌商家
  • Unity UI粒子特效3大核心优势:告别传统限制,实现无缝集成
  • 基于MCP协议的EVM区块链AI智能体交互服务器部署与实战
  • EgerGergeeert数据库课程设计助手:从需求分析到SQL生成
  • hyperf Rector + PHPStan 升级自动化工具开源完整流程(从 0 到持续维护)====写一个开源项目全流程
  • 2024机器学习工程师薪资趋势与技能溢价分析
  • 实测Qwen2.5-Coder-1.5B:自动生成Python代码效果展示
  • 机器学习预测区间:原理与Python实战
  • 边缘AI模型部署实战:telanflow/mps框架解析与性能优化
  • hyperf 安全基线工具箱开源完整流程(从 0 到持续维护)===写一个开源项目全流程
  • nli-MiniLM2-L6-H768效果展示:630MB模型精准识别蕴含/矛盾/中立关系
  • 如何在Windows上解锁苹果触控板的原生级体验?mac-precision-touchpad驱动完全指南
  • YOLOv8鹰眼检测数据导出教程:如何保存检测结果?
  • Java的java.lang.ModuleLayer层次结构与模块隔离在复杂应用中的组织
  • 朴素贝叶斯算法原理与实战应用指南
  • 构建混合特征机器学习流水线:TF-IDF与LLM嵌入的工程实践
  • 2026 必报!未来 5 年 “钱景” 最好的 4 个专业,缺口大、薪资高、不内卷
  • ECOC多分类方法:原理、实现与优化策略
  • 如何提交网站到谷歌网站收录? Shopify卖家必看:解决产品页不收录难题 | 零代码指南