简介:这是一份计算机相关专业测试开发岗位的实习报告,完整记录作者参与新疆泰运集团运输网站安全项目上线前测试的经历,适合准备软件测试实习、需要撰写实习报告的学生参考。压缩包共1个doc文件,大小24KB,正文按实习目的、实习内容、测试方法、BUG提交、总结展开,结构清晰。报告覆盖需求评审、需求分析、测试计划、用例设计、测试环境、执行测试、BUG跟踪、测试报告完整流程,并对白盒/黑盒/灰盒、静态/动态、单元/集成/系统等测试方法做了分类梳理;BUG提交部分还说明了错误类型、重现概率、严重级别、标题和截图附件等要素。尤其值得一看的是作者亲历的TCP/IP与C/S架构压力测试,涉及Linux文件描述符限制排查与调优,能够帮助读者理解真实测试环节中的细节。目前已有442人学习浏览,适合作为测试开发实习总结和岗位认知的参考资料。
1. 测试开发实习:你以为的点工,其实是质量工程
打开实习报告.doc的时候,你可能只打算写三个月的手工测试。但进了项目组你会发现,测试开发实习的日常是:上午写用例、下午写脚本、晚上修CI流程,周末还要响应开发临时提的“帮我造个数据”。这不是简单的点点点,而是参与整个质量流水线——从需求分析到测试设计,从接口验证到性能调优,每一个环节都需要用工程手段去解决“质量怎么保证”的问题。
这篇内容我不讲抽象的测试理论,只讲你在测试开发实习里会碰到的真实任务:怎么把手工用例转成自动化脚本,怎么在后端没就绪时用Mock继续测,怎么在IDEA里调JVM参数防止OOM打断调试,以及实习报告怎么写出交付感。每段都会给出能直接抄的代码和参数,适合正在准备测试开发实习或刚入职三个月内的同学。
2. 测试用例到自动化脚本:先设计,再让pytest跑起来
很多实习生的误区是一上来就学Selenium。其实测试开发的核心是先有测试设计,再有自动化执行。用例设计得不好,脚本写得再漂亮也只是把低覆盖率的问题重复执行一万遍。
2.1 等价类和边界值:用例不是越细越好,而是越准越好
你被分到的第一个任务通常是“给登录功能写测试用例”。如果按“正确密码、错误密码、空密码”三个用例去写,交付给评审看一眼就会被打回。正规的用例设计至少要做等价类划分和边界值分析。
以登录页的密码框为例,假设需求是“6到20位字符,包含字母和数字”。常见的用例设计如下表:
| 用例编号 | 输入数据 | 预期结果 | 设计方法 |
|---|---|---|---|
| TC-01 | 6位纯字母 | 不通过 | 等价类-无效 |
| TC-02 | 6位字母+数字,如abc123 | 通过 | 等价类-有效 |
| TC-03 | 5位字符 | 提示“最少6位” | 边界值-下限-1 |
| TC-04 | 6位字符 | 通过 | 边界值-下限 |
| TC-05 | 20位字符 | 通过 | 边界值-上限 |
| TC-06 | 21位字符 | 提示“最多20位” | 边界值-上限+1 |
提示:边界值法建议单独建一个用例模块,不要在自动化脚本里用if硬编码,否则后续需求变更时会改到怀疑人生。
2.2 pytest最小用例与断言:从能跑到跑好
用例设计完,接下来就是落地。测试开发最常见的落地框架是pytest,因为它不需要像unittest那样写一堆类包装,用函数、fixture、参数化就可以覆盖大部分场景。
import pytest def test_login_success(): # 模拟登录接口响应 resp = {"code": 0, "msg": "登录成功"} assert resp["code"] == 0 assert "登录成功" in resp["msg"] def test_login_wrong_password(): resp = {"code": 1001, "msg": "密码错误"} assert resp["code"] == 1001 assert resp["msg"] == "密码错误"这里两个测试函数分别对应有效等价类和无效等价类。断言只检查code和msg两个关键字段,而不是把整个响应体都断言一遍——响应体里哪怕多一个时间戳字段也会导致用例不稳定,这在测试开发里叫“脆弱断言”,是要避免的。
pytest的命名必须以test_开头或_test结尾,否则不会被执行。运行只需要在项目根目录执行:
pytest -v --tb=short-v显示每条用例的结果,--tb=short让失败信息只打印关键行,方便在连篇的运行日志里快速定位。刚上手时不建议直接加-q,虽然安静,但你看不到失败用例的细节。
2.3 项目目录与数据驱动:让测试数据不再散落在代码里
一个实习项目至少要有这四层目录:
test_proj/ ├── cases/ # 测试用例函数 │ └── test_login.py ├── data/ # 外部测试数据,推荐yaml或json │ └── login_data.yaml ├── common/ # 封装请求、断言、日志 │ └── request_util.py └── conftest.py # pytest的fixture和插件配置数据驱动的意思就是,把用例中的输入和预期抽到数据文件里,代码只留执行逻辑。例如login_data.yaml:
login_cases: - {username: "abc123", password: "abc123", expect_code: 0} - {username: "abc", password: "abc123", expect_code: 1001}然后在测试函数里用pytest.mark.parametrize参数化:
import pytest, yaml with open("data/login_data.yaml", encoding="utf-8") as f: login_cases = yaml.safe_load(f)["login_cases"] @pytest.mark.parametrize("case", login_cases, ids=lambda c: c["username"]) def test_login_from_yaml(case, request_util): resp = request_util.post("/api/login", json=case) assert resp["code"] == case["expect_code"]参数化后,每一条数据会生成一个独立的测试用例,ids参数控制展示的名字,这样跑挂了你能一眼看出是哪条数据出的问题。这里有一个实习中常见的坑:yaml文件里的中文如果被代码读取时编码不一致,会抛错。解决办法是在打开文件时强制指定encoding="utf-8",而不是靠系统默认编码。
3. 接口测试与Mock:后端没就绪,测试也不能停
测试开发实习中有很大一部分时间是围绕接口展开的。你不需要等前端页面做好,也不需要等后端每个接口都联调通过,接口测试可以把质量检查点提前到后端开发的中后期。
3.1 用requests写第一个接口冒烟测试
手工用Postman点一遍接口不算本事,能写脚本在CI里自动跑才是测试开发的价值。
import requests def test_get_user_info(): url = "http://127.0.0.1:8080/api/user/1001" resp = requests.get(url, timeout=5) assert resp.status_code == 200 data = resp.json() assert data["userId"] == 1001 assert data["role"] == "admin"timeout=5这个参数很多人会忽略,但它是必须的。不加timeout时,如果服务端一直不返回,你的测试进程会挂起直到整个压测任务超时。接口测试还要注意,不要直接断言整个JSON结构,而是断言业务的三个关键点:状态码、核心业务字段、错误码。如果接口返回的是一个列表,还要额外的长度断言:
resp = requests.get("http://127.0.0.1:8080/api/user/list", params={"page": 1, "size": 10}) items = resp.json()["items"] assert len(items) <= 10params参数用来传URL里的查询字符串,requests会自动帮你拼成?page=1&size=10,不用自己手拼字符串去转义。
3.2 Mock后端:用unittest.mock挡住外部依赖
后端接口常常不是一次性全部就绪的。你负责的模块可能依赖登录中心、支付网关,而对方还在开发中。这时候Mock的价值就来了。一种常见做法是在写单元测试时用unittest.mock替换掉依赖的响应:
from unittest.mock import patch import requests def get_order_status(order_id): resp = requests.get(f"http://gateway/api/order/{order_id}") return resp.json()["status"] @patch("requests.get") def test_order_status_mock(mock_get): mock_get.return_value.json.return_value = {"status": "PAID"} assert get_order_status("12345") == "PAID"@patch装饰器会把测试函数里的requests.get整体替换成一个假方法。这样你的测试就不再依赖真实网关,运行速度快,而且稳定。注意@patch的路径是"requests.get",这是被测试代码中模块的引用路径,不是测试文件里的引用路径。
另一种更接近真实场景的做法是起一个本地Mock服务,用Flask写一个假接口:
from flask import Flask, jsonify app = Flask(__name__) @app.route("/api/user/<user_id>") def mock_user(user_id): return jsonify(userId=user_id, role="admin") if __name__ == "__main__": app.run(port=8081)本地Mock服务适合联调场景,你可以把前端、测试脚本的base_url临时指向8081端口,等后端开发完再切回来。这个过程中,接口的参数约束和返回结构全由你定义,相当于测试开发在反向推动接口设计。
3.3 测试数据工厂与清理策略:不能只造数不回收
接口测试跑多了,测试库里的脏数据会越来越多。实习团队里最常见的做法是每次跑完测试后清理数据,或者在造数时使用带标记的数据。
| 数据项 | 造数规则 | 清理策略 |
|---|---|---|
| user_id | 使用固定前缀如t_加时间戳 | 跑完删除WHERE user_id LIKE 't_%' |
| order_no | 由UUID前8位生成 | 在teardown中删除对应订单 |
| 测试手机号 | 使用190开头的专用号段 | 定期脚本清理,不污染正式数据 |
在pytest里,清理逻辑一般放在fixture的yield之后:
@pytest.fixture def clean_user(): user_id = "t_" + str(int(time.time())) yield user_id requests.delete(f"http://127.0.0.1:8080/api/user/{user_id}")3.4 回调接口测试:用flask临时端口接收通知
这里再补充一个接口测试里很容易被忽略的场景:回调。很多系统的业务逻辑是异步的,下单后通过回调通知商户。测试回调时如果直接去连真实回调地址,往往需要等很久或者根本无法触发。我一般会在测试脚本里启动一个监听在随机端口的Flask服务,把测试目标配置成这个临时地址,然后回调来的时候断言参数。
from flask import Flask, request import threading, pytest received = {} @app.route("/callback", methods=["POST"]) def callback(): received["data"] = request.json return "OK" @pytest.fixture(scope="module") def callback_server(): thread = threading.Thread(target=lambda: app.run(port=8099)) thread.daemon = True thread.start() yield # 等待异步回调触发 assert received.get("data", {}).get("status") == "SUCCESS"这种模式的价值在于,不需要依赖外部的回调平台,你可以在十几秒内验证整个异步链路。临时端口不要写死,否则CI上有多个项目同时跑时会冲突,改成动态端口更稳妥。
4. 性能测试与JVM调优:防止开发测试时出现OOM
测试开发实习里除了功能,也会碰性能。但很多同学一开始就上压测工具,反而忽略了最基础的开发环境调优。热词里提到的“设置IDEA的JVM运行内存的大小,防止开发测试时出现OOM”其实就是这项工作的一部分。开发环境不稳,测试脚本跑得再快也是白费。
4.1 为什么测试环境经常OOM:并不总是堆内存太小
OOM全称OutOfMemoryError,最常见的原因是堆内存耗尽。但测试环境有三个特殊因素:一是测试项目本身基于Spring Boot,启动时默认占用较大;二是同时开了接口自动化、性能压测、数据库导入导出多个进程;三是IDEA自身如果分配的内存不足,会先卡死而不是优雅地报错。
看OOM日志时先分清楚是哪一步:JVM启动时OOM还是运行中OOM。启动时OOM通常是堆太小,运行中OOM多半有内存泄漏或一次性加载了过多数据。比如循环里不断拼接字符串并缓存到List,就很容易触发。
4.2 设置IDEA的JVM运行内存的大小,防止开发测试时出现OOM
IDEA本身是Java应用,它跑不动时会拖累你打开的测试项目。常见的做法是修改IDEA的虚拟机配置。
找到安装目录下的idea.vmoptions文件(macOS在/Applications/IntelliJ IDEA.app/Contents/bin下,Windows在安装目录的bin下)。修改关键参数:
-Xms1024m -Xmx2048m -XX:ReservedCodeCacheSize=512m参数含义如下:
| 参数 | 推荐值 | 作用 |
|---|---|---|
| -Xms | 1024m | 初始堆内存,IDEA启动时直接分配 |
| -Xmx | 2048m | 最大堆内存,超过这个值会抛OOM |
| -XX:ReservedCodeCacheSize | 512m | JIT编译缓存,设太小会导致频繁编译 |
注意:
-Xmx不建议超过本机物理内存的1/4,比如16G内存的机器设4G。设太大会导致系统其他程序换页,反而更卡。
如果你改完配置后IDEA还是卡,可以查看当前JVM实际内存使用。在IDEA的Help -> Diagnostic Tools -> JVM Metrics里能看到Heap Used曲线。如果曲线一直在缓慢上升后突然跌到老年代GC,那就是堆内存不够;如果曲线平稳但IDEA变慢,可能是GC时间太长,需要在vmoptions里加上-XX:+UseG1GC,G1会比默认的Parallel GC更适合IDEA这种响应型应用。
4.3 用jstat看堆内存:压测过程中的实时监控
压测时,测试开发需要时刻关注被测服务的JVM状态,不能等压测完了再翻日志。jstat是JDK自带的小工具,命令是这样的:
jstat -gcutil 18234 1000 1018234是被测Java进程的PID,需要先用jps -l查出来。1000表示每1000毫秒采样一次,10表示采样10次。输出结果里的E(Eden区)如果一直在高位,说明对象频繁创建;O(Old区)持续增长且FGC(Full GC)次数变多,说明很可能有泄漏。
如果是自己写压测脚本,可以在脚本里捕获OOM时的堆转储:
java -Xms512m -Xmx512m -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/tmp/oom.hprof -jar app.jar-XX:+HeapDumpOnOutOfMemoryError会在OOM时自动生成.hprof文件,这个文件用MAT(Memory Analyzer)打开,能看到是哪个类的实例占用了最多内存。对于一个刚接触测试开发的实习生来说,能跑到这一步,已经超过大多数只会对着日志发愁的同级了。
5. 测试开发学习路线与实习报告量化技巧
作为测试开发实习生,实习报告不能写成流水账,而要写出“你带来了什么改变”。这一章直接拆解学习路线和量化报告的方法,两个都能落地。
5.1 三个月学习路线表
| 阶段 | 时间 | 目标 | 必学内容 |
|---|---|---|---|
| 打底 | 第1-2周 | 会写用例、会抓接口 | 等价类/边界值,HTTP状态码,fiddler |
| 自动化 | 第3-6周 | 能用pytest跑脚本 | pytest、requests、yaml数据驱动 |
| 服务与Mock | 第7-9周 | 独立搭接口测试方案 | flask、unittest.mock、数据库增删查 |
| 性能与调优 | 第10-12周 | 会压测并看懂JVM指标 | jmeter、jstat、visualvm、IDEA内存设置 |
5.2 实习报告怎么写:事件 → 动作 → 结果数据
很多人的实习报告写的是“参与XX项目测试,负责XX模块”,这种描述在简历筛选阶段就被划掉了。测试开发岗位真正看的不是经历,而是验证结果。我建议按“事件背景、我的动作、量化结果”三段式填充。
例如:不要让“写了登录模块测试”单独存在,改成“登录模块在开发自测阶段漏了一个边界值场景,我补充了6个边界用例并在回归阶段跑出1个密码长度误判Bug,开发当天修复”。这个描述里包含了背景、动作和结果,还暗示了你会等价类划分和自动化执行。
5.3 一个能写进报告的数据校验脚本
测试开发实习另一个常被问到的问题是“你如何处理大量测试结果的比对”。如果报告里写“用Excel肉眼比”,面试官会觉得语气不足。可以放一个我用过的pandas对比脚本:
import pandas as pd old = pd.read_csv("baseline.csv") new = pd.read_csv("actual.csv") merged = pd.merge(old, new, on="id", suffixes=("_旧", "_新")) bad = merged[merged["value_旧"] != merged["value_新"]] print(f"共 {len(bad)} 条数据不一致")pd.merge按id字段关联新旧两份快照,suffixes区分两边的同名列,最后筛选出value不一致的行。这个脚本对列顺序不敏感,也不需要数据库连接,适合放在实习报告的工具章节里展示你对数据校验的自动化处理思路。
本文还有配套的精品资源,点击获取