MogFace人脸检测模型自动化测试:基于PyTest的API接口与性能压测

MogFace人脸检测模型自动化测试:基于PyTest的API接口与性能压测 MogFace人脸检测模型自动化测试基于PyTest的API接口与性能压测如果你已经成功把MogFace人脸检测模型部署到了星图GPU平台上并且通过WebUI界面能正常调用那恭喜你第一步已经走通了。但作为开发团队尤其是对工程质量有要求的团队部署完成只是开始。接下来一个很实际的问题是怎么确保这个服务一直稳定可靠今天有同事上传了一张图片测试效果不错明天用户量上来了同时有几十上百个人上传图片服务会不会挂掉或者哪天我们更新了模型版本怎么快速验证新版本没把老功能搞坏这些问题靠手动点几下WebUI是解决不了的。我们需要一套自动化测试方案它得像一个不知疲倦的质检员随时待命帮我们把关服务的功能正确性和性能底线。这篇文章我就来聊聊怎么为部署好的MogFace WebUI服务搭建一套从接口功能到并发性能的自动化测试体系。我们会用到PyTest来写功能测试用Locust来模拟高并发压力最后再把它们串到CI/CD流程里让测试自动化起来。1. 测试环境与目标确认在动手写代码之前我们得先明确两件事测什么以及在哪测。首先我们的测试对象是MogFace模型的WebUI服务。通常这类服务会提供一个HTTP API接口比如http://你的服务器地址:端口/predict它接收一张图片然后返回人脸检测的结果包括人脸框的位置、置信度等。我们的测试就要围绕这个接口展开。其次测试环境。既然模型部署在星图GPU平台上我们的测试脚本最好也能在一个与之网络互通的环境里运行。你可以选择在星图平台内部启动一个测试用的容器或者在你本地的开发机上运行前提是能访问到服务地址。为了简单起见我们假设你可以在本地通过某个IP和端口访问到部署好的MogFace服务。我们的测试目标可以拆解为两层功能正确性确保API接口在各种正常和异常输入下都能给出符合预期的响应。比如传一张标准的人脸图片能正确识别传一张空白图片或错误格式的文件能妥善报错。性能稳定性评估服务在高并发请求下的表现。它能同时处理多少个请求平均响应时间是多少在持续压力下会不会出错或崩溃这能帮助我们了解服务的容量边界为后续扩容提供依据。明确了目标接下来我们就准备测试战场。2. 使用PyTest构建API功能测试套件PyTest是Python社区里非常流行的测试框架它写起来简单功能却很强。我们就用它来给MogFace的API接口做功能验证。2.1 搭建测试项目结构一个好的开始是组织好代码结构。我建议创建一个独立的项目目录比如叫做mogface_api_tests。mogface_api_tests/ ├── tests/ │ ├── __init__.py │ ├── conftest.py │ ├── test_api_functional.py │ └── test_data/ │ ├── normal_face.jpg │ ├── no_face.jpg │ └── huge_image.jpg ├── requirements.txt └── README.mdtests/目录存放所有测试代码。conftest.py是PyTest的配置文件我们可以在这里定义一些全局的fixture比如HTTP客户端、服务的基础URL。test_api_functional.py是我们的主测试文件。test_data/目录放测试用的图片。requirements.txt列出项目依赖。我们的requirements.txt文件可以先简单点pytest7.4.3 requests2.31.0用pip安装它们pip install -r requirements.txt。2.2 编写基础配置与测试用例现在我们来填充conftest.py。这里的关键是定义一个base_urlfixture这样所有测试用例都能方便地知道要测哪个服务。# tests/conftest.py import pytest pytest.fixture(scopesession) def base_url(): 返回MogFace WebUI服务的基础URL。请根据你的实际部署地址修改。 # 示例如果你在本地通过端口7860访问地址可能是 http://localhost:7860 # 如果是星图平台提供的服务请替换为对应的访问地址。 return http://localhost:7860接下来是重头戏编写功能测试test_api_functional.py。我们会用requests库来发送HTTP请求。# tests/test_api_functional.py import os import pytest import requests # 获取测试图片的路径 TEST_DATA_DIR os.path.join(os.path.dirname(__file__), test_data) def test_api_health_check(base_url): 测试1服务健康检查。确认接口是否可以访问。 # 假设服务有一个健康检查端点或者我们直接调用预测接口看是否返回非5xx错误 # 这里我们直接调用预测接口但使用一个最小化的有效请求或检查其响应状态 # 更稳妥的做法是如果服务提供了/health端点则测试它。 # 我们这里以预测接口为例但期望它对于格式错误的请求也能返回一个结构化的错误而不是崩溃。 url f{base_url}/predict # 发送一个空的POST请求这应该会触发一个客户端错误如400 response requests.post(url) # 我们并不期望它成功但期望它不会返回服务器内部错误500 # 一个健壮的服务应该对错误输入返回4xx状态码而不是5xx。 assert response.status_code ! 500, f服务端内部错误{response.text} print(f健康检查通过。服务响应状态码{response.status_code}) def test_detect_single_face(base_url): 测试2正常场景 - 检测单张人脸。 url f{base_url}/predict image_path os.path.join(TEST_DATA_DIR, normal_face.jpg) with open(image_path, rb) as f: files {image: f} response requests.post(url, filesfiles) assert response.status_code 200, f请求失败{response.status_code}, {response.text} result response.json() # 假设返回的JSON结构包含一个faces列表里面是检测到的人脸信息 assert faces in result faces result[faces] assert isinstance(faces, list) # 我们预期这张测试图片中至少有一个人脸 assert len(faces) 1 # 检查第一个人脸是否包含必要的字段如边界框和置信度 if len(faces) 0: first_face faces[0] assert bbox in first_face # 边界框 [x1, y1, x2, y2] assert confidence in first_face # 置信度 print(f成功检测到 {len(faces)} 张人脸。第一张人脸置信度{first_face[confidence]:.2f}) def test_detect_no_face(base_url): 测试3边界场景 - 图片中无人脸。 url f{base_url}/predict image_path os.path.join(TEST_DATA_DIR, no_face.jpg) # 一张风景或物体图片 with open(image_path, rb) as f: files {image: f} response requests.post(url, filesfiles) assert response.status_code 200 result response.json() faces result.get(faces, []) # 预期检测到0张人脸或者返回一个空列表 assert len(faces) 0 print(无人脸图片处理正确返回空结果。) def test_invalid_image_format(base_url): 测试4异常场景 - 上传非图片文件。 url f{base_url}/predict # 创建一个临时的文本文件冒充图片 invalid_file (invalid.txt, this is not an image, text/plain) # 注意requests的files参数需要文件对象或元组这里用元组形式 files {image: invalid_file} response requests.post(url, filesfiles) # 服务应该能处理这种错误返回4xx状态码如400 Bad Request # 我们允许200但返回错误信息或4xx但不应该是500。 assert response.status_code in (200, 400, 415), f非预期的状态码{response.status_code} # 如果返回200其JSON内容应包含错误指示 if response.status_code 200: result response.json() # 可能包含一个error字段或空的faces assert len(result.get(faces, [])) 0 or error in result print(f无效文件格式已被正确处理状态码{response.status_code}) def test_large_image_handling(base_url): 测试5压力场景 - 处理大尺寸图片。 url f{base_url}/predict image_path os.path.join(TEST_DATA_DIR, huge_image.jpg) # 一张分辨率很高的图片 with open(image_path, rb) as f: files {image: f} response requests.post(url, filesfiles) # 大图片可能处理成功也可能因超时或内存限制失败。 # 我们主要测试服务不会因为大图片而崩溃返回5xx错误。 assert response.status_code ! 500, 服务在处理大图片时发生内部错误。 # 如果是4xx或200带错误信息说明服务有相应的处理机制。 print(f大图片处理完成状态码{response.status_code})写完这些用例在项目根目录下运行pytest tests/ -v就能看到测试执行结果了。绿色代表通过红色代表失败失败的信息会帮你快速定位问题。3. 使用Locust进行性能压力测试功能没问题了那性能呢我们需要知道这个服务能扛住多大压力。Locust是一个用Python写的开源负载测试工具它允许你用代码定义用户行为然后模拟成千上万的并发用户去“攻击”你的服务非常直观。3.1 安装与编写Locust性能测试脚本首先安装Locustpip install locust。然后在项目根目录创建一个locustfile.py。# locustfile.py from locust import HttpUser, task, between import random import os class MogFaceUser(HttpUser): 模拟一个用户的行为访问MogFace人脸检测API。 # 模拟用户在每个任务执行后等待1到3秒 wait_time between(1, 3) # 准备一些测试图片路径。假设我们在当前目录的test_data文件夹下放了几张图片。 test_images [ test_data/normal_face.jpg, test_data/no_face.jpg, # 可以添加更多图片路径以增加测试多样性 ] def on_start(self): 当虚拟用户启动时执行可以用来初始化或登录本例不需要。 # 确保图片文件存在 for img_path in self.test_images: if not os.path.exists(img_path): print(f警告测试图片 {img_path} 不存在请确保路径正确。) task(weight3) # weight表示任务权重权重越高被执行的频率越高 def detect_face(self): 任务上传一张图片进行人脸检测高频率任务。 # 随机选择一张测试图片 image_path random.choice(self.test_images) with open(image_path, rb) as f: files {image: f} # 注意这里使用的是相对路径 /predictLocust会自动拼接到host上。 with self.client.post(/predict, filesfiles, catch_responseTrue) as response: # 检查响应状态码是否为200并且响应中包含预期的字段 if response.status_code 200: try: json_resp response.json() if faces in json_resp: response.success() else: response.failure(f响应中未找到faces字段: {json_resp}) except Exception as e: response.failure(f响应JSON解析失败: {e}) else: response.failure(f请求失败状态码: {response.status_code}) task(weight1) # 低频率任务模拟一些无效请求 def send_invalid_request(self): 任务发送一个格式错误的请求低频率任务测试服务健壮性。 # 发送一个空的POST请求或错误格式的数据 with self.client.post(/predict, data{wrong_key: wrong_value}, catch_responseTrue) as response: # 我们期望服务能妥善处理错误返回4xx而不是5xx或崩溃。 if 400 response.status_code 500: response.success() # 服务正确处理了错误请求对我们测试来说是“成功” else: response.failure(f对无效请求的响应状态码异常: {response.status_code})这个脚本定义了一个MogFaceUser用户类它有两个主要行为taskdetect_face: 模拟正常用户上传图片进行检测权重为3意味着更常发生。send_invalid_request: 模拟发送错误请求权重为1频率较低用来测试服务的容错能力。3.2 执行压力测试并分析结果打开终端进入项目目录运行以下命令启动Locust的Web界面locust -f locustfile.py --hosthttp://localhost:7860请将--host参数替换为你实际的MogFace服务地址。然后在浏览器中打开http://localhost:8089你会看到Locust的控制台。在这里你需要设置Number of users (peak concurrency): 你想模拟的最大并发用户数比如100。Spawn rate (users started/second): 每秒启动多少个用户比如10。Host: 应该已经填好了。点击 “Start swarming”压力测试就开始了。Locust会实时展示各种图表和数据RPS (Requests per second): 每秒请求数代表吞吐量。Response Times (ms): 响应时间包括平均值、中位数、以及P95、P99代表95%或99%的请求在这个时间内完成。P95/P99是评估服务稳定性的关键指标它们能告诉你大多数用户的体验以及长尾延迟的情况。Number of Failures: 失败请求数。如果这个数字在压力下持续增长说明服务可能出现了问题。你可以观察在不同并发用户数下响应时间的变化趋势。如果随着用户数增加响应时间急剧上升或失败率飙升那就找到了服务的性能瓶颈。测试完成后你还可以在Locust界面下载完整的CSV报告用于更深入的分析。4. 集成到CI/CD流程手动运行测试当然可以但我们的目标是自动化。理想的状态是每次代码更新、模型版本升级或者重新部署服务后这套测试都能自动跑一遍只有通过了才能上线。这就需要把它集成到CI/CD持续集成/持续部署流程中。4.1 使用GitHub Actions进行自动化测试这里以GitHub Actions为例展示如何将PyTest功能测试集成进去。在你的项目仓库根目录下创建.github/workflows/run-tests.yml文件。# .github/workflows/run-tests.yml name: MogFace API Tests on: push: branches: [ main, develop ] # 在推送到主分支或开发分支时触发 pull_request: branches: [ main ] # 在向主分支提PR时触发 jobs: test: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkoutv3 - name: Set up Python uses: actions/setup-pythonv4 with: python-version: 3.9 - name: Install dependencies run: | pip install -r requirements.txt # 如果需要也可以在这里安装Locust用于后续可能的性能测试步骤 - name: Run API functional tests with pytest run: | # 这里假设你的MogFace服务已经在某个地址运行并通过环境变量传入。 # 如果测试需要先启动服务可以在此步骤前添加启动服务的步骤例如使用docker-compose。 # 我们通过环境变量将服务地址传递给测试。 BASE_URL${{ secrets.TEST_BASE_URL }} pytest tests/ -v --tbshort env: # 在GitHub仓库的Settings - Secrets中设置 TEST_BASE_URL BASE_URL: ${{ secrets.TEST_BASE_URL }}这个工作流做了几件事在代码推送或拉取请求时自动触发。准备一个Ubuntu环境安装Python和项目依赖。运行PyTest测试。注意这里需要一个关键信息被测试的MogFace服务地址BASE_URL。我们把它放在GitHub仓库的Secrets里避免硬编码在代码中。对于性能测试你也可以在Actions中集成Locust但通常性能测试尤其是压测不会在每次提交都运行而是定期如每晚或在发布前运行。你可以创建另一个工作流文件用schedule触发器来定时执行Locust测试并将结果报告保存为工件。4.2 关键实践建议把测试自动化起来之后有几点经验分享测试数据管理测试用的图片文件不要太大可以放进代码仓库但要注意仓库大小。也可以考虑使用固定的外部URL或对象存储来存放测试资源。测试隔离与稳定性确保你的测试不会影响到线上服务。最好有一个独立的“预发布”或“测试”环境来运行这些自动化测试。失败告警在CI/CD流程中如果测试失败应该能及时通知到团队比如通过邮件、Slack或钉钉。性能基线为性能测试结果如P95响应时间、吞吐量建立一个“基线”。每次性能测试后将结果与基线对比如果出现显著退化比如响应时间增加了50%则让CI/CD流程失败或发出警告促使团队去排查原因。5. 总结与后续思考走完这一套流程你会发现为MogFace这样的人脸检测服务搭建自动化测试并没有想象中那么复杂。PyTest帮我们牢牢守住了功能的底线确保接口在各种情况下都能按预期工作Locust则像一次次的压力演练让我们对服务的承载能力心中有数知道它的极限在哪里什么时候该考虑扩容。把它们集成到CI/CD里更是把质量保障从“事后检查”变成了“流程卡点”。每次改动无论是模型更新、依赖升级还是配置调整自动化测试都会第一时间告诉你有没有引入新的问题。这为团队的快速迭代提供了坚实的信心。当然这只是个开始。你可以根据实际需求继续丰富你的测试套件比如增加对返回结果精度的断言与标注数据对比、测试更复杂的图片场景多人脸、遮挡、模糊、或者将性能测试的结果可视化形成历史趋势图。测试的世界很大但核心思想不变用自动化的手段持续地、可靠地守护你的服务质量。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。