VideoAgentTrek-ScreenFilter自动化测试:编写Python脚本进行大规模回归测试

最近在折腾一个视频内容过滤的服务,叫VideoAgentTrek-ScreenFilter。这东西挺有意思的,简单说就是给视频“安检”,自动识别并处理掉一些不合适的内容。随着模型迭代更新,我遇到了一个挺头疼的问题:每次更新完,怎么快速、全面地验证服务还是稳定可靠的?手动测几个视频肯定不行,覆盖面太窄,效率也低。

于是,我就琢磨着给它搭一套自动化测试流水线。核心思路就是用Python脚本,模拟各种千奇百怪的输入,然后自动检查输出是不是我们想要的。这样一来,不管是日常的模型更新,还是代码改动,跑一遍测试就能心里有底。今天就来聊聊我是怎么做的,这套方法对于其他类似的AI服务测试,应该也有参考价值。

1. 为什么需要自动化测试?

在聊具体怎么做之前,先说说为什么非得搞自动化。VideoAgentTrek-ScreenFilter这类服务,输入输出其实挺复杂的。输入是视频文件,输出可能是处理后的视频,也可能是处理报告或者错误信息。靠人工测试,你很难覆盖所有情况:

  • 场景太多:正常视频、含敏感内容的视频、完全损坏的文件、格式不支持的文件、超大文件、超长视频……手动准备这些测试用例就够喝一壶的。
  • 回归成本高:每次模型或代码有改动,哪怕只是一行,理论上所有功能点都应该重新测一遍。人工回归?不现实,时间成本太高,还容易漏测。
  • 结果判断主观:处理后的视频效果怎么样?有没有把该过滤的内容都过滤掉?人工肉眼判断,不同的人可能有不同的标准,不够客观。

自动化测试就是为了解决这些问题。它能把测试用例固定下来,用代码去模拟用户行为,用断言去客观判断结果,并且可以随时随地、反复执行。对于确保服务稳定性,尤其是模型更新后的稳定性,自动化测试不是“锦上添花”,而是“雪中送炭”。

2. 测试框架与工具选型

工欲善其事,必先利其器。搭建自动化测试流水线,第一步是选好工具。Python生态里,主流的测试框架就那两个:unittest 和 pytest。

  • unittest:Python标准库自带的,不用额外安装,结构比较规整(继承TestCase类),对于有Java JUnit背景的开发者来说很熟悉。
  • pytest:第三方框架,但现在是事实上的社区标准。它的语法更简洁,不需要写类,用起来更灵活。夹具(fixture)功能强大,参数化测试也特别方便,报告也更美观。

我最终选择了 pytest。主要原因就是它写起来更“Pythonic”,更简洁,而且扩展性很强。当我们后面要处理文件上传、调用HTTP接口、对比视频这些复杂操作时,pytest的夹具能帮我们优雅地管理测试资源。

除了测试框架,我们还需要一些辅助库:

  • requests:用于调用VideoAgentTrek-ScreenFilter的HTTP API接口。
  • opencv-python (cv2):如果需要对处理前后的视频进行像素级对比或元数据分析,会用到它。
  • pytest-html:生成漂亮的HTML测试报告,方便查看结果。

你可以用pip一键安装它们:

pip install pytest requests opencv-python pytest-html

3. 设计测试用例:模拟各种输入场景

测试用例的设计是整个自动化测试的核心。我们的目标是尽可能模拟真实世界可能遇到的各种输入。对于VideoAgentTrek-ScreenFilter,我主要设计了以下几类测试用例:

3.1 正常流程测试

这是最基本的,确保服务在理想情况下能正常工作。

  • 用例1:处理不含敏感内容的普通视频。上传一个风景、宠物等“安全”视频,预期服务能正常返回处理后的视频(可能原样返回,或仅添加了水印等元数据),并且HTTP状态码为200。
  • 用例2:处理含有敏感内容的视频。上传一个包含预设敏感元素(如特定文字、Logo、画面)的视频。预期服务能正确识别并处理(如打码、模糊、截断),返回处理后的视频,并在报告中标明处理项。

3.2 异常与边界测试

这类测试用于检验服务的健壮性和容错能力。

  • 用例3:上传损坏的视频文件。比如一个被截断的.mp4文件。预期服务应返回明确的错误信息(如“文件损坏”),HTTP状态码为4xx(客户端错误)。
  • 用例4:上传不支持格式的文件。比如上传一个.txt文本文件或.exe可执行文件。预期服务应返回“不支持的文件格式”类错误。
  • 用例5:上传空文件或超大文件。测试服务对大小限制的处理。预期应有相应的错误提示或拒绝处理。

3.3 性能与稳定性测试(可选)

在回归测试中融入一些性能检查。

  • 用例6:连续处理多个视频。模拟一个小型并发场景,检查服务是否会崩溃、内存是否泄漏、处理时间是否在可接受范围内。
  • 用例7:处理长时间视频。上传一个时长半小时或更长的视频,测试服务的稳定性和耗时。

4. 动手编写Python测试脚本

理论说完了,我们来看代码。假设我们的VideoAgentTrek-ScreenFilter服务提供了一个简单的HTTP API:POST /api/filter,用于上传视频并返回处理结果。

首先,我们创建一个测试目录,比如 tests/,并在里面创建 conftest.py 和测试文件。

conftest.py 用于存放pytest的夹具,比如配置服务地址、准备测试视频等。

# tests/conftest.py
import pytest
import os

# 定义一个夹具,返回测试服务的基地址
@pytest.fixture(scope="session")
def service_base_url():
    # 这里替换成你实际的VideoAgentTrek-ScreenFilter服务地址
    return "http://localhost:8000"

# 定义一个夹具,返回测试视频文件的路径
@pytest.fixture
def normal_video_path():
    # 指向一个不含敏感内容的测试视频
    path = "test_videos/normal.mp4"
    assert os.path.exists(path), f"测试视频文件不存在: {path}"
    return path

@pytest.fixture
def sensitive_video_path():
    # 指向一个含有敏感内容的测试视频
    path = "test_videos/sensitive_content.mp4"
    assert os.path.exists(path), f"测试视频文件不存在: {path}"
    return path

@pytest.fixture
def corrupted_video_path():
    # 指向一个损坏的测试视频文件
    path = "test_videos/corrupted.mp4"
    assert os.path.exists(path), f"测试视频文件不存在: {path}"
    return path

接下来,我们编写主要的测试文件 test_video_filter.py。

# tests/test_video_filter.py
import requests
import pytest
import time
import os

class TestVideoAgentFilter:
    """VideoAgentTrek-ScreenFilter 服务自动化测试类"""

    def test_filter_normal_video(self, service_base_url, normal_video_path):
        """测试1:处理正常视频,应成功并返回视频"""
        url = f"{service_base_url}/api/filter"
        with open(normal_video_path, 'rb') as f:
            files = {'video': f}
            response = requests.post(url, files=files)

        # 断言1: HTTP状态码为200,表示请求成功
        assert response.status_code == 200, f"正常视频处理失败,状态码: {response.status_code}"
        
        # 断言2: 响应头中应包含视频内容类型,或者有特定的成功标识
        # 这里假设成功时返回的是视频文件流
        assert 'video' in response.headers.get('Content-Type', ''), "响应内容不是视频类型"
        
        # 你可以选择将返回的视频保存下来,供后续人工复核或对比
        # output_path = 'output_normal_processed.mp4'
        # with open(output_path, 'wb') as out_f:
        #     out_f.write(response.content)
        # print(f"正常视频处理结果已保存至: {output_path}")

    def test_filter_sensitive_video(self, service_base_url, sensitive_video_path):
        """测试2:处理含敏感内容视频,应成功并返回处理后的视频及报告"""
        url = f"{service_base_url}/api/filter"
        with open(sensitive_video_path, 'rb') as f:
            files = {'video': f}
            response = requests.post(url, files=files)

        assert response.status_code == 200, f"敏感视频处理失败,状态码: {response.status_code}"
        
        # 假设服务在处理敏感内容后,会在JSON body或自定义Header中返回处理报告
        # 这里需要根据你服务的实际响应格式来调整断言
        # 例如,如果报告在JSON中:
        # response_json = response.json()
        # assert 'processed' in response_json and response_json['processed'] is True
        # assert 'detected_items' in response_json
        # print(f"检测到并处理了以下内容: {response_json['detected_items']}")
        
        # 同样,可以保存处理后的视频
        # ...

    def test_filter_corrupted_video(self, service_base_url, corrupted_video_path):
        """测试3:处理损坏视频,应返回明确的客户端错误"""
        url = f"{service_base_url}/api/filter"
        with open(corrupted_video_path, 'rb') as f:
            files = {'video': f}
            response = requests.post(url, files=files)

        # 断言:状态码应为4xx,表示因客户端请求问题导致的错误
        assert response.status_code >= 400 and response.status_code < 500, \
            f"损坏文件未返回客户端错误,状态码: {response.status_code}"
        
        # 断言:响应中应包含错误信息
        # 可能是JSON格式的 `{"error": "File is corrupted"}`
        # 也可能是纯文本
        assert len(response.content) > 0, "错误响应体为空"
        # 你可以进一步解析响应,断言包含特定错误关键词
        # assert 'corrupt' in response.text.lower() or 'invalid' in response.text.lower()

    @pytest.mark.parametrize("file_name", ["test.txt", "test.exe", "image.jpg"])
    def test_filter_unsupported_format(self, service_base_url, file_name):
        """测试4:处理不支持的文件格式,应返回错误"""
        # 这里我们创建一个临时的非视频文件来测试
        unsupported_file_path = f"/tmp/{file_name}"
        with open(unsupported_file_path, 'w') as f:
            f.write("This is not a video file.")
        
        url = f"{service_base_url}/api/filter"
        with open(unsupported_file_path, 'rb') as f:
            files = {'video': f}
            response = requests.post(url, files=files)
        
        os.remove(unsupported_file_path) # 清理临时文件
        
        assert response.status_code >= 400 and response.status_code < 500, \
            f"不支持格式未返回客户端错误,状态码: {response.status_code}"
        assert 'video' not in response.headers.get('Content-Type', ''), \
            "不支持格式错误地返回了视频内容"

    def test_batch_processing_stability(self, service_base_url, normal_video_path):
        """测试5:连续批量处理,测试服务稳定性"""
        url = f"{service_base_url}/api/filter"
        num_requests = 10  # 连续请求10次
        failures = 0
        
        for i in range(num_requests):
            with open(normal_video_path, 'rb') as f:
                files = {'video': f}
                try:
                    response = requests.post(url, files=files, timeout=30) # 设置超时
                    if response.status_code != 200:
                        failures += 1
                        print(f"第{i+1}次请求失败,状态码: {response.status_code}")
                except requests.exceptions.RequestException as e:
                    failures += 1
                    print(f"第{i+1}次请求发生异常: {e}")
            time.sleep(0.5)  # 每次请求间隔0.5秒,避免瞬时压力过大
        
        # 断言:失败率应低于某个阈值,例如10%
        max_allowed_failures = num_requests * 0.1  # 10%的失败率容忍度
        assert failures <= max_allowed_failures, f"批量处理稳定性测试失败,失败次数: {failures}/{num_requests}"

5. 运行测试与生成报告

脚本写好了,怎么运行呢?在项目根目录下,打开终端,执行一条简单的命令即可:

pytest tests/ -v --html=report.html --self-contained-html
  • -v:显示详细的测试执行过程。
  • --html=report.html:使用pytest-html插件生成HTML格式的测试报告。
  • --self-contained-html:将CSS样式内嵌到HTML中,生成一个独立的报告文件。

运行完成后,会生成一个 report.html 文件。用浏览器打开它,你能清晰地看到哪些测试通过了,哪些失败了,失败的原因是什么,以及每个测试的执行时间。这份报告就是每次回归测试的“成绩单”,非常直观。

6. 集成到CI/CD流水线

让自动化测试发挥最大价值的地方,是把它集成到持续集成/持续部署(CI/CD)流水线中。比如使用Jenkins、GitLab CI、GitHub Actions等工具。

基本思路是:每当有新的代码提交或合并到主分支时,CI/CD系统自动拉取代码,在一个干净的环境中安装依赖,然后运行我们的pytest测试套件。只有当所有测试都通过时,才允许进行后续的构建或部署操作。

以GitHub Actions为例,可以创建一个 .github/workflows/run-tests.yml 文件:

name: Run VideoAgent Tests

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.9'
    - name: Install dependencies
      run: |
        pip install pytest requests opencv-python pytest-html
        # 这里可能还需要安装你的VideoAgentTrek-ScreenFilter服务依赖
    - name: Start Service (Optional)
      # 如果你的测试需要先启动本地服务,可以在这里添加启动命令
      run: |
        echo "Starting service..."
        # nohup python your_service_app.py &
    - name: Run tests with pytest
      run: |
        pytest tests/ -v --html=report.html --self-contained-html
    - name: Upload test report
      uses: actions/upload-artifact@v3
      if: always() # 即使测试失败也上传报告
      with:
        name: pytest-html-report
        path: report.html

这样,每次代码变更都会自动触发测试,从源头保障了服务的质量。

7. 总结与建议

给VideoAgentTrek-ScreenFilter搭建这套Python自动化测试框架,花了一些时间,但非常值得。现在每次模型更新或者代码调整,我只需要点一下按钮(或者根本不用点,CI自动跑),就能在几分钟内得到一份全面的测试报告,心里踏实多了。

整个过程下来,有几点体会比较深。第一,测试用例的设计比写测试代码本身更重要,要多从“用户会怎么用”、“什么情况下会出问题”的角度去思考。第二,pytest框架确实灵活强大,夹具和参数化用好了能省很多事。第三,一定要把测试集成到CI/CD里,让它成为开发流程中不可或缺的一环,而不是想起来才跑一下的“课外活动”。

如果你也在维护类似的AI服务或API,强烈建议把自动化测试做起来。一开始可能觉得麻烦,但一旦跑起来,它就是你服务稳定性的“守门员”。从简单的几个用例开始,慢慢补充,逐渐形成覆盖核心功能、异常情况和性能边界的测试网,这绝对是提升开发效率和项目质量的利器。


获取更多AI镜像

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

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐