ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Python自动化测试系统设计与实现:分层架构与pytest实战

Python自动化测试系统设计与实现:分层架构与pytest实战 简介这是一篇基于Python语言的自动化测试系统设计与实现毕业论文面向专科和本科毕业生提供写作参照。全文按绪论、系统设计、系统实现、系统测试与评价、结果分析与讨论、总结与展望六章组织既阐述自动化测试的研究背景与国内外现状也给出了由前端界面、后端服务器和数据库组成的总体架构并细化测试用例管理、测试执行、结果分析和报告生成等核心模块的设计。实现层面基于Django搭建后台服务使用requests库模拟用户操作利用Scrapy框架完成数据爬取通过Tesseract OCR识别图像文本并借助Face_recognition库实现人脸识别功能覆盖了从理论到落地的完整技术链路。压缩包内含1份docx格式文档体积仅30KB结构紧凑、便于阅读。当前已有261人学习浏览适合正在撰写相关选题论文或入门自动化测试的开发者直接参考和借鉴。1. 自动化测试系统先搭骨架再写脚本一个测试系统如果接手三个月后还需要靠全局搜索找用例那说明设计没跟上规模。很多自动化项目失败不在脚本本身而在于把“自动化测试”理解成“把手工点过的路径写成代码”结果用例越堆越多、互相牵连改动一个公共方法要牵着一串用例返工。标题里“设计与实现”这四个字重点在于先用 Python 把骨架立住用例归用例、驱动归驱动、数据归数据。Python 语法表达力强、第三方库齐全做系统骨架时最省力气的是文件结构天然就能表达模块边界。适合手里已经有一些零散脚本、准备整合成可运行可交付的测试系统的读者也适合想绕开“脚本堆成山”的入门者。2. 把自动化测试系统拆成用例层、驱动层与公共层2.1 三层划分依赖只能往下走做自动化测试系统我一般先画依赖关系。不管被测对象是 Web、移动端还是纯接口代码目录都要先分成三层。最底层是公共层放请求会话、浏览器驱动、数据库连接、日志和断言基类中间层是驱动层对应业务操作比如“登录”“创建订单”“查询列表”它只调用公共层最上层是用例层只组合驱动层动作并断言结果。依赖方向单向向下用例层不允许直接 import 公共层里的数据库连接池驱动层不允许反向调用用例层。好处在改需求时能看出来。接口字段变了只改驱动层和公共层的数据结构用例层里的业务描述保持稳定。UI 元素定位变了只改驱动层的页面对象用例层不用动。如果某天发现一个改动能波及所有测试脚本说明分层没切干净某个模块里混了多层职责。系统设计阶段我不写一行业务逻辑先把每个目录的依赖方向钉死。2.2 先跑通最小骨架一个能执行的 pytest 工程设计落地第一步不是写用例是搭一个能跑通的空工程。用 pytest 作为执行器比 unittest 更省事因为 fixture、参数化、插件体系都是现成的。目录结构参考下面这个骨架auto_test/ ├── config/ │ ├── __init__.py │ ├── settings.py # 环境变量与全局参数 │ └── env.yaml # 不同环境的基础地址 ├── common/ │ ├── __init__.py │ ├── client.py # 请求会话与浏览器驱动 │ ├── logger.py # 统一的日志封装 │ └── assertions.py # 断言基类 ├── drivers/ │ ├── __init__.py │ ├── login_action.py # 登录业务操作 │ └── order_action.py # 订单业务操作 ├── testcases/ │ ├── __init__.py │ ├── conftest.py │ ├── test_login.py │ └── test_order.py ├── reports/ # 测试报告输出 ├── pytest.ini # pytest 配置 └── requirements.txt这个结构里common与drivers是纯代码包testcases里只放测试函数。pytest.ini里我通常会关掉二进制缓存、指定默认执行目录、设好日志格式[pytest] testpaths testcases addopts -ra --strict-markers log_cli true log_cli_level INFO-ra会在结束时汇总所有失败与跳过的原因--strict-markers让未注册的标记直接报错避免团队里有人随手写pytest.mark.xxx造成标记混乱。这个配置本身也是系统设计的一部分它规定了“用例必须放在哪个目录、标记必须先注册”。2.3 数据与配置分离环境切换不碰代码自动化测试系统最常见的维护成本来自配置散落。有人把测试账号写在 module 顶部有人把环境地址硬编码在 request 里换一套环境就要动十几个文件。我一般把环境和业务参数都收进settings.py由环境变量优先读取顺序是环境变量 本地env.yaml 默认值。# config/settings.py import os import yaml def load_env_config(): config_file os.getenv(TEST_ENV_FILE, config/env.yaml) with open(config_file, encodingutf-8) as f: env_data yaml.safe_load(f) return env_data ENV_CONFIG load_env_config() BASE_URL os.getenv(TEST_BASE_URL, ENV_CONFIG.get(base_url)) DB_DSN os.getenv(TEST_DB_DSN, ENV_CONFIG.get(db_dsn))这里的关键是环境变量优先级高于配置文件。CI 里只需注入TEST_BASE_URL就能指向测试环境无需 git 提交任何环境差异。新手常犯的错误是把读取逻辑写进每个测试模块里、每个用例自己读一次配置文件。测试系统里“一次性加载、全局引用”比“随用随读”更可控因为启动时配置错立刻能暴露而不是跑到第 30 条用例才中断。3. 用 pytest 把用例组织成可维护的自动化测试系统3.1 fixture 与 conftest.py公共逻辑该住在哪里pytest 的 fixture 机制比 unittest 的 setUp/tearDown 更适合系统化设计因为 fixture 可以声明作用域、自动依赖注入、按需组装。scop 的取舍直接决定测试执行效率。scope生命周期适用场景function每个用例执行一次临时数据、浏览器页面对象class每个测试类执行一次类内共享的会话module每个模块执行一次只读配置、公共连接session整个测试过程一次数据库连接池、全局日志公共的 fixture 统一写在testcases/conftest.py只有单个模块要用的写在模块内部。下面是一个典型的会话级 fixture返回一个已经登录的客户端供多个模块复用# testcases/conftest.py import pytest from common.client import ApiClient pytest.fixture(scopesession) def client(): client ApiClient() client.login() yield client client.close()这个 fixture 因为声明了scopesession整个测试会话只创建一个登录态。如果不加 scope默认是 function每进一个用例就重新 login 一次系统里只要有几个依赖登录态的模块执行时间立刻翻倍。这里还要注意yield后面的清理逻辑必须写在 yield 之后如果登录成功但清理失败pytest 会在 teardown 阶段报错而不是吞掉。3.2 参数化与数据驱动一个用例跑多组数据自动化测试系统能不能规模化看参数化用得好不好。pytest 的pytest.mark.parametrize可以直接把一组组数据变成多条用例数据来源可以是列表、元组也可以是从文件读出来的外部数据。接口测试里最常见的做法是让参数来自 extern 文件避免改数据要动代码。# testcases/test_login.py import pytest from common.client import ApiClient login_cases [ {username: valid_user, password: valid_pwd, code: 0}, {username: , password: any_pwd, code: 1001}, {username: valid_user, password: wrong_pwd, code: 1002}, ] pytest.mark.parametrize(case, login_cases, ids[正常登录, 用户名为空, 密码错误]) def test_login_with_multi_data(client, case): resp client.login(case[username], case[password]) assert resp.status_code 200 assert resp.json()[code] case[code]ids参数会在收集阶段给每条用例一个可读的名字报告里能看到“正常登录”“密码错误”而不是login_cases[2]。参数化最容易被忽略的点是数据本身的语义比数量重要。一组只覆盖“正、反、边界”三条的数据比二十条重复流程的数据更能揪出问题。这里的client是 conftest 里定义的 session 级 fixturepytest 会按函数参数名自动注入不需要手动实例化。3.3 断言与失败上下文让报错能直接定位系统化的关键之一是每个失败都能独立定位。很多人写断言只写assert resp.json()[code] 0一旦失败只能看到 java 风格的堆栈不知道请求参数是什么、返回整体是什么。我一般把断言封装成带上下文的断言方法。# common/assertions.py class AssertionEngine: staticmethod def assert_code(actual, expect_code, message): if actual ! expect_code: err_msg fcode 断言失败: expect{expect_code}, actual{actual} err_msg f, 补充信息{message} raise AssertionError(err_msg)用例里这样调用def test_order_create(client): resp client.create_order(goods_id1001) AssertionEngine.assert_code( resp.json().get(code), 0, messagef接口响应: {resp.text} )失败时日志里能直接看到完整响应体不用再回头加 print 重跑。还要注意断言基类只放通用断言不放业务断言。业务范围内“金额必须大于零”这类规则属于驱动层不能混进公共断言里否则驱动层要改规则时反而要去改公共包。4. 动手实现测试主链路驱动封装、执行调度与报告4.1 用页面对象封装驱动动作UI 自动化测试系统里驱动层最常用的是页面对象模式。核心思想是把一个页面的定位和操作收拢成一个类用例层只调用方法不直接接触find_element。写页面对象时我习惯把每个方法的返回结构定死成功返回预期数据失败抛出业务异常不让定位异常弹出到用例层。# drivers/login_action.py from selenium.webdriver.remote.webdriver import WebDriver from common.logger import logger class LoginAction: def __init__(self, driver: WebDriver): self.driver driver def login(self, username: str, password: str) - bool: self.driver.find_element(id, username).send_keys(username) self.driver.find_element(id, password).send_keys(password) self.driver.find_element(id, login_btn).click() logger.info(登录操作执行完成, user%s, username) return self.driver.current_url.endswith(/dashboard)这里故意不写 sleep而是依赖显式等待。显式等待的写法可以封装成公共层里的wait_until否则每个方法后面都挂一个固定time.sleep(2)测试系统跑起来慢而且容易偶发失败。元素定位失败时日志里要打印的是“当前页面标题 当前 URL”这类上下文不是只抛一个 NoSuchElementException。4.2 多进程与多线程并行执行调度是系统的隐藏能力到用例超过几百条后串行执行会慢到影响迭代节奏。pytest 本身支持pytest-xdist插件做多进程分发配置方式很直接pytest -n 4 --dist loadscope-n 4表示起 4 个 worker--dist loadscope让同一模块的用例尽量分到同一个 worker。这样做的原因是部分 fixture 是 module/session 级如果把同一个模块的用例拆到不同进程每个进程都要重新建会话等于并行红利被初始化成本吃掉。接口测试系统里我一般先按模块分组再分配UI 测试则严格限制并行因为浏览器实例的资源占用远高于 session。如果不想引入插件也可以用 Python 标准库里的concurrent.futures.ThreadPoolExecutor手动分发独立用例from concurrent.futures import ThreadPoolExecutor, as_completed def run_case(case_func): return case_func() with ThreadPoolExecutor(max_workers4) as executor: futures [executor.submit(run_case, case) for case in independent_cases] for future in as_completed(futures): result future.result()手动并行的前提是这些用例之间没有数据依赖比如各自创建独立订单、互不引用 ID。系统里凡是依赖共享数据的用例必须显式标记为串行这比“全部并行”更可靠。4.3 测试报告与数据可视化执行完后没有报告再好的系统也使不上劲。pytest 配合pytest-html是最快的落地方式pytest --htmlreports/report.html --self-contained-html--self-contained-html会把 CSS/JS 内嵌进单个文件方便直接发给别人看和存档。内部系统里如果连了数据库我还会把执行结果写回一张表用 Python 的数据分析脚本定期出趋势。可视化方式不复杂读结果表里的日期、通过率、耗时列落在折线图里。这一步其实是在回答“自动化测试是否让质量变好”这个问题没有数据支撑的系统只是脚本集合。5. 设计与回归的收口技巧用 collect-only 验证系统边界5.1 执行前先体检系统结构自动化测试系统改完目录、动了 fixture 之后直接全量跑一遍成本很高。pytest --collect-only可以只收集用例而不执行快速检查用例是否按预期被识别pytest --collect-only -q输出会列出所有待执行的用例路径我一般看三个点用例数量是否正确、参数化用例是否展开成预期的多条、有没有用例被重复收集或者被错误跳过。如果收集结果里有不属于本模块的用例说明testpaths或目录命名有问题。这个命令跑完再决定动代码比闷头全量执行省时间得多。5.2 失败重跑时的设计判断自动化测试系统跑起来之后最干扰判断的是“偶发失败”。pytest-rerunfailures插件可以按次数重跑但重跑不等于掩盖问题。我一般只对网络抖动类用例开重跑比如请求超时后重试一次而不是对断言失败开重跑。断言失败是真实缺陷的信号重跑它会延迟问题暴露。系统里最好在 conftest 里约定重试次数而不是让每个用例自己乱传参数。pytest --reruns 2 --reruns-delay 1--reruns-delay 1是两次重跑之间间隔 1 秒避免对被测服务产生瞬时压力。5.3 一个控制并发的参数化技巧参数化数据如果存在执行顺序依赖可以在数据里加一个分组字段再用pytest.mark.group标记最后在插件或 conftest 里按分组排队执行pytest.mark.group(order) pytest.mark.parametrize(case, order_cases, ids[创建, 取消]) def test_order_flow(case): ...执行时先跑所有order组再跑其他组可以在 conftest 里通过 pytest hook 收集用例顺序但更简单可靠的做法是官方工具分组本身不改变执行顺序只提供分类能力真正的排队由执行脚本里的-m筛选完成pytest -m order这类用例不要和普通用例混在同一个-n并行池里把所有分组依赖留在设计层面说清楚。回归时先跑分组的链路再跑独立用例执行顺序、资源占用、失败定位才都在可控范围里。本文还有配套的精品资源点击获取
返回列表