
简介一份以软件测试为核心的计算机专业实习报告记录了2022年在两家IT公司的完整实习经历。第一家公司从事餐饮管理软件开发报告结合黑盒测试、瀑布模型说明如何发现、记录并回归验证Bug第二家公司基于自研PHPMySQL框架做电子商务定制开发报告补充了MVC模式、Smarty模板、Ajax与jQuery自学以及dedecms模板嵌套、缓存机制与静态化处理等实战细节覆盖测试与开发两条技术路线。资源包内仅有1个docx文档大小13KB内容紧凑既梳理实习过程、项目场景和问题排查思路也提炼出客户至上、需求明确、持续学习、团队协作、规范化流程等心得适合计算机专业学生作为实习报告写作参考。目前已有54人学习/下载对即将走向软件测试或PHP开发岗位的读者有较强的借鉴意义。1. 在真实的软件公司里测试不是“点一点”那么简单2022 年我在两家公司实习第一份工作是餐饮管理软件的测试第二份是做 PHP 电商平台定制开发。回头再看这两段经历其实是同一件事的两面在瀑布模型下测试是交付前的最后一道闸门在定制开发中测试经验反过来逼着我写更稳的代码。很多刚入行的同学会把“软件测试”理解成“用鼠标到处点、发现问题就报 bug”但真正做过一次完整的测试任务就会明白它考验的是你对业务流程的理解、对边界条件的敏感度以及写测试文档时的表达能力。这篇文章不聊空泛的“测试重要性”直接拆解我在实习里实际用到的黑盒测试方法、Bug 流转流程以及后来在 PHP MySQL MVC 项目中测试思维是如何帮我少走弯路的。适合正在找测试或开发实习的在校生也适合刚接手定制项目、需要建立基础质量保障流程的初级工程师参考。2. 黑盒测试与瀑布模型定制软件测试的核心打法2.1 黑盒测试为什么是定制项目的首选第一次进公司项目经理交给我的任务是测试餐饮管理软件的新功能。这套软件每个客户都有定制需求功能版本差异大内部逻辑又互相牵连根本不可能在短期内把整个系统的代码读一遍再测。采用的方法就是黑盒测试把被测系统当成一个不透明的盒子只关心输入和输出不关心内部实现。黑盒测试的关键在于设计有效的测试用例。我在实习里常用的设计思路有三种。2.1.1 等价类划分把输入数据划分成若干个子集每个子集里取一个代表性数据来测。比如餐饮软件的“菜品单价”输入框有效等价类是大于 0 的数字无效等价类包括负数、0、非数字字符、超长字符串。对每个等价类各设计一条用例就能用最少的数据覆盖大部分场景。2.1.2 边界值分析经验表明最容易出错的地方往往在边界附近。比如库存数量限制在 0 到 999 之间那 -1、0、1、998、999、1000 这六个数必须全部测到。特别是 0 这个值很多开发在写判断条件时容易写成if ($stock)库存为 0 时直接走 false 分支逻辑上没问题但如果后续有$total $price * $stock的运算就要看有没有做除零或空值保护。2.1.3 错误推测法靠经验猜测哪里容易出问题。比如提交表单时快速双击提交按钮、在输入框粘贴带换行符的内容、在分页参数里传字符串等。在餐饮软件里我经常模拟“开台后长时间不点菜再结账”的操作这种场景最容易暴露出会话超时或金额计算的问题。以下是我在实习中实际使用的测试用例记录表结构用例编号测试模块前置条件输入数据操作步骤预期结果实际结果是否通过TC-001桌台管理已开台桌号“A01”选择桌台 → 点击“并台” → 选择“B02”并台成功菜品合并展示提示“桌台不存在”失败TC-002菜品录入已点菜数量“0”数量输入 0 → 点击“确认”提示“数量必须大于0”提示“数量必须大于0”通过提示测试用例的“操作步骤”一定要写到每个点击动作不要写“正常操作流程”这种模糊描述否则开发复现 bug 时要靠猜。2.2 瀑布模型里的测试阶段验证不是“最后再做一次”这家公司的开发流程严格遵循瀑布模型需求分析 → 设计 → 编码 → 测试 → 维护。测试被放在编码之后、交付之前但我在实际工作中发现测试并不是一个阶段性的动作而是和开发过程交织在一起的。新功能开发完成后项目经理会先让我做一轮“冒烟测试”——只测主流程能不能走通比如“点菜→下单→结账”如果主流程直接挂掉就直接打回给开发不做详细测试。通过之后才进入系统测试阶段这时候按测试用例逐条执行发现 bug 就记录到测试文档中。这里有一个非常容易被新手忽略的点回归测试不是只测修好的那个功能。开发修复了一个 bug可能影响到其他关联模块。比如修了“桌台并台时菜品丢失”的问题就要把正常的并台流程、换台流程、结账流程全部重新测一遍而不是只验证那个 bug 场景。瀑布模型的测试文档一般包含以下内容# 测试文档 - 餐饮管理软件 v2.3 ## 1. 测试环境 - 服务器Windows Server 2019 Apache 2.4 MySQL 5.7 - 客户端Chrome 90.0分辨率 1920x1080 ## 2. 测试范围 - 本次测试覆盖桌台管理、菜品录入、结账管理 - 不包含会员管理开发中未提测 ## 3. Bug 列表 ### BUG-001 【严重】并台操作后菜品丢失 - 环境v2.3 - 前置条件A01、B02 均已开台并点菜 - 操作步骤A01 点击并台→ 勾选 B02 → 确认合并 - 实际结果合并后 B02 的菜品消失 - 预期结果B02 的菜品合并到 A01 - 指派给张 XX - 状态已修复待验证提示测试文档必须写清楚“前置条件”。同一段代码在不同数据状态下表现完全不同不写前置条件开发很难定位问题。在写测试文档的过程中我逐渐意识到这还是沟通工具。测试人员通过文档把问题“翻译”给开发人员开发通过文档了解复现路径项目经理通过文档评估进度和风险。文档质量差等于测试白做。2.3 Bug 生命周期从发现到关闭的全过程管理在规范的软件测试流程里一个 Bug 从被发现到关闭要经过固定流转。这是我当时记录 Bug 状态变化的方式新建(New) → 打开(Open) → 修复(Fixed) → 验证(Verified) → 关闭(Closed) ↓ 拒绝(Rejected) ↓ 重新打开(Reopen)状态流转中要注意几个关键细节。第一Bug 被开发“拒绝”时不一定是开发逃避责任有可能是测试环境配置不一致也可能是需求文档本身没写清楚该场景应该如何处理。这时候要找项目经理确认预期行为而不是单方面跟开发争执。第二修复后一定要做“验证 回归”两步动作先按原路径复测确认修复有效再检查有无引入新问题两步都通过才能关闭 Bug。第三Bug 等级要合理划分。致命级是系统崩溃或核心功能不可用严重级是功能结果错误但可通过其他途径绕过一般级是界面错误或轻微逻辑问题建议级是文字错误或体验不佳。等级分得太高开发疲于应付分得太低真正重要的问题会被淹没。有一次我报了一个“致命”级的 bug原因是结账时金额显示多了一分钱。开发首先检查了四舍五入逻辑又排查了数据库字段精度最后发现是前端浮点数计算精度问题。这个排查过程花了一个多小时但问题本身只是显示层错误系统重启后自动恢复只能算“一般”级。从那以后我每次报 Bug 前都会先评估影响范围而不是只看表面现象。3. PHP 电商项目的开发视角MVC、Smarty 与 ADODB3.1 自研框架的 MVC 路由解析第二家公司做 PHP 电商定制框架全部自研。入职培训第一课就是 MVC 开发模式。在 MVC 里Controller 负责接收请求和调度逻辑Model 负责数据和业务规则View 负责展示。对于做定制开发的公司来说MVC 还有一层额外的好处给不同客户做功能差异时只需要改 Controller 和 ViewModel 层的数据结构尽量保持稳定这样同一个客户升级版本时不会被自己的定制改动影响。公司框架的 URL 路由设计大概长这样/index.php?madmincgoodsaeditid15四个参数的含义分别是m代表模块modulec代表控制器controllera代表方法action后面的id是业务参数。这种“模块-控制器-方法”的三层结构是 PHP 老项目最常见的路由方式和现在 Laravel 里的 RESTful 路由比确实原始但优点是直观——任何一个 URL 拿到手里你都能立刻在代码里找到对应的处理函数。我接到的第一个任务是开发一个简易论坛程序。核心代码是处理发帖请求的控制器方法?php // 控制层PostController.php框架自研Controller基类提供assign和display方法 class PostController extends Controller { public function add() { $title trim($_POST[title] ?? ); $content trim($_POST[content] ?? ); // 服务端二次校验不能只依赖前端表单验证 if ($title || mb_strlen($title) 80) { $this-assign(error, 标题不能为空且不超过80个字符); $this-display(post/add_error.tpl); return; } if ($content || mb_strlen($content) 5) { $this-assign(error, 内容不能少于5个字符); $this-display(post/add_error.tpl); return; } $postModel M(post); // M函数是框架自研的Model工厂方法 $newId $postModel-insert([ title $title, content $content, user_id $_SESSION[uid], created date(Y-m-d H:i:s) ]); if ($newId) { $this-redirect(index.php?mforumcpostadetailid . $newId); } else { $this-assign(error, 数据库写入失败); $this-display(post/add_error.tpl); } } }这段代码的逻辑说明$_POST[title] ?? 用来避免未定义索引的 PHP 警告mb_strlen用多字节字符串函数才能正确统计中文标题长度M(post)是框架自研的 Model 工厂传入表名返回模型实例insert方法返回自增主键 ID用于跳转。在写这段代码时我刻意加入了服务端校验因为做测试时见过太多绕过前端校验直接提交的数据——只要用浏览器开发者工具改一下 POST 参数就能发空内容如果服务端不拦截垃圾数据就会不断写入。3.2 Smarty 模板引擎的变量与循环渲染论坛程序的展示层用了 Smarty 模板。Smarty 的核心思路是把 PHP 变量赋值给模板模板内部只使用 Smarty 语法做展示不写原生 PHP 代码。这样做的好处是前端工程师可以独立维护模板但代价是调试时多一层编译和缓存。典型的 Smarty 模板片段是这样{* list.tpl - 论坛帖子列表模板 *} ul classpost-list {foreach $postList as $post} li a hrefindex.php?mforumcpostadetailid{$post.id} {$post.title|escape:html} /a span classmeta{$post.user_name|escape:html}/span span classmeta{$post.created}/span /li {foreachelse} li暂无帖子a hrefindex.php?mforumcpostaadd发布第一帖/a/li {/foreach} /ul这里有两处值得注意{$post.title|escape:html}是 Smarty 的转义修饰器作用是防止用户输入的标题中的 HTML 标签被浏览器解析这个转义在安全上非常重要{foreachelse}语法用于处理数组为空的情况——第一次写模板时我忽略了这一点结果是列表为空时页面直接空白用户体验很差。Smarty 还有一个让我栽过跟头的特性默认开启了编译缓存。修改了模板文件后浏览器刷新不一定能看到变化需要去templates_c目录手动清理编译文件或者等待编译缓存过期。在调试阶段我一般把代码里的force_compile设为 true 简化调试等调试完成后必须关掉它否则线上环境每次请求都会重新编译模板白白浪费性能。3.3 ADODB 数据库连接引擎与参数化查询公司框架没有用 PDO而是封装了 ADODB。这在国内老一批 PHP 框架里很常见它的主要价值是提供统一的数据库操作接口底层支持 MySQL、PostgreSQL 等多种数据库。实际使用时我们主要依赖它的两大能力GetOne/GetAll 等快捷方法和自动拼接的查询条件。在定制开发中经常要处理客户自定义的查询条件比如“按日期范围筛选订单”。最初我这样拼接 SQL$sql SELECT * FROM orders WHERE status 1; if ($startDate) { $sql . AND created . $startDate . ; } if ($endDate) { $sql . AND created . $endDate . ; } $res $db-GetAll($sql);这段代码对可预见的条件有效但存在一个安全隐患如果$startDate来自用户输入且没有做类型校验就会出现 SQL 注入。更好的做法是用 ADODB 的参数化查询能力$params []; $where status 1; if ($startDate) { $where . AND created ?; $params[] $startDate; } if ($endDate) { $where . AND created ?; $params[] $endDate; } $sql SELECT * FROM orders WHERE $where; $res $db-GetAll($sql, $params);参数说明第一个?对应$params[0]第二个?对应$params[1]ADODB 会在底层对传入值做转义。做过测试的人对这类问题特别敏感因为我在第一家公司测试时就专门用单引号、双引号、11这类字符去试探输入框只要数据结构有漏洞十有八九能触发异常或返回不该有的数据。3.4 DedeCMS 模板嵌套与缓存/静态化机制第三项工作是做 DedeCMS 模板嵌套建公司产品帮助网站。这里有个容易混淆的点DedeCMS 的“模板嵌套”不是 PHP 的 include 文件而是它自定义的标签语法。比如{dede:include filenameheader.htm /}用来引入公共头部{dede:channel typeson}用来输出子栏目列表。在做这个任务的过程中我接触到了 DedeCMS 的缓存机制。它的核心是生成 HTML 静态页面而不是每次请求都动态解析模板。生成规则大致如下// 伪代码DedeCMS 生成静态首页的核心逻辑 $content $this-parseTemplate($templateFile); // 解析模板标签 - 得到动态 HTML $this-writeHtml($content, $htmlFilePath); // 写入静态文件 // 后续用户请求直接由 Apache/Nginx 返回静态文件不再执行 PHP这个机制的启发意义很大电商系统的商品详情页、文章详情页如果全部动态渲染数据库压力会大很多。静态化处理就是拿空间换时间——生成一次 HTML 文件之后直接用静态文件响应真实的电商项目里还会配合缓存标签做局部动态化比如用户登录名、购物车数量这种必须实时的部分用 AJAX 或 JS 动态加载避免整页失效。4. 从测试到开发的“Bug 预防式编码”思维4.1 用测试用例的思维写需求文档在第二家公司做定制工作时我接触了不少客户需求。第一次跟客户沟通时客户说“要一个积分商城”听起来很简单但当我追问“积分抵扣的比例是多少”“抵扣是否包含运费”“积分过期如何处理”“退货时积分怎么退回”这些细节时客户也答不上来。后来项目经理带我和客户约了一次专项会议逐条确认需求形成需求文档开发过程才顺利推进。这段经历让我体会到写需求文档和设计测试用例是类似的思维方式。需求描述里的每一个功能点都可以转化成对应的验收条件也就是测试用例的预期结果。如果一个功能写不清楚验收条件说明需求还没有想清楚。比如“用户可以用积分抵扣订单金额”这条需求拆开来就变成了需求描述验收条件即测试预期结果积分抵扣订单金额订单金额 100 元使用 2000 积分抵扣 20 元实付 80 元抵扣金额不能超过订单金额订单金额 10 元使用 2000 积分抵扣最多抵扣 10 元退货时积分退还订单完成退款后积分原路退回用户账户积分不足使用 500 积分抵扣但账户只有 300 积分提示“积分不足”提示需求文档里每条功能描述旁边加上“验收标准”列就等于把测试用例前置到了需求阶段开发时目标更清楚测试时也不用临时拆解需求。4.2 代码规范让团队代码看起来像一个人写的这家公司有一条规定让我印象很深项目组每个人的代码风格必须一致不管是变量命名、缩进方式还是注释格式都要符合公司的编程标准。当时觉得约束多后来才发现这样做的好处——代码审查时不用先花时间适应别人的风格直接就能看逻辑问题。规范里最常规的几条变量名一律小写开头、驼峰式命名四格缩进函数和方法必须有注释说明参数和返回值数据库字段名统一用下划线命名。写代码时注意这些细节等于在编码阶段就把一部分“可读性 bug”消灭了。比如// 编程标准示例 class GoodsController extends Controller { /** * 修改商品库存 * * param int $goodsId 商品ID * param int $num 本次变动的数量正数增加负数减少 * return bool 是否成功 */ public function changeStock($goodsId, $num) { // 实现略 } }我在做测试时经常根据代码注释来推断功能预期行为注释写清楚的功能测试用例设计起来也快。相反如果开发换了人且代码没注释测试就得摸索着来。规范表面上是约束代码风格本质上是在投资未来的可维护性。4.3 定制开发中的回归测试策略定制开发里最典型的问题是不同客户使用同一套产品某个客户定制了新功能改动只对自己生效还是会影响所有客户这家公司的产品走的是“主干 分支”的路线同一个产品的公用代码保持统一客户定制功能尽量做到模块化穿插在留好的扩展位里。实际操作上每次改完代码后我会用几个固定的测试场景做快速回归# 初始化测试数据本地环境 mysql -u root -p test_db test_data.sql # 确认服务正常启动 php -S localhost:8080 -t public第一条命令把测试数据恢复到干净状态确保每次测试的输入数据完全一致第二条命令启动 PHP 内置开发服务器。这种快速回归非常适合没有完整自动化测试框架的老项目——手工执行固定场景验证公用功能没有被新改动破坏。4.4 边界值设计从测试用例到代码逻辑在第一家做测试时积累的边界值思维到写 PHP 代码时帮了大忙。比如写分页功能时每页显示 10 条数据、共 1 条数据、共 0 条数据、页码传 0、页码传负数、页码传字符串——这些情况我在设计代码时就提前做了处理$page isset($_GET[page]) ? intval($_GET[page]) : 1; if ($page 1) { $page 1; } $offset ($page - 1) * $pageSize; $totalPages max(1, ceil($totalCount / $pageSize)); if ($page $totalPages) { $page $totalPages; $offset ($page - 1) * $pageSize; }这段代码分别处理了未传 page 参数时默认第 1 页页码小于 1 时强制回位页码超过最大页数时回退到最后一页。做过测试的人会本能地多问一句“如果边界情况出现会发生什么”这个习惯移植到开发上就是写更稳的代码。很多 bug 其实不是“正常功能坏掉了”而是“异常场景没人管”这种 bug 在面试时也很常被问到属于软件测试面试题里必考的基础能力。5. 自制一个“交付前自查清单”验证最终质量实习结束后我把两周测试工作流的经验整理成一份自查清单之后在 PHP 开发中也一直用它。这个清单适用于任何没有自动化测试覆盖的中小项目重点是“站在用户视角把所有主流程走一遍”检查项具体操作说明空数据状态新建账号后直接浏览首页没有数据时页面是否报错列表是否有“暂无数据”文案边界输入输入 0、负数、超长文本、HTML 标签是否有校验提示是否安全转义重复操作快速双击提交按钮、连续两次提交是否产生重复订单或重复记录会话过期登录后长时间停留再操作是否跳转登录页已填写的表单是否丢失浏览器兼容Chrome、Edge 各测一遍布局错乱、JS 报错是最常出现的问题除了这个通用清单我还会在实现完一个功能后做一次“错路测试”故意用错误的方式操作比如不登录直接访问后台 URL、拿别人订单号的 URL 直接访问、在搜索框放通配符。做测试时习惯性地多试这些“不安全”的路径写开发代码时就会自觉加权限判断和参数过滤。尝试过一个很典型的问题客户反馈“订单页面打开很慢”排查后发现是一次性查询了全部订单数据并在页面循环渲染后来改成 MySQL 的 limit 分页才解决——这种问题在没数据时发现不了但在真实数据量浮现时十分棘手。最终建议是每次交付前留出 30 分钟按这张清单完整走一遍主流程如果有任何一条不通过的宁可推迟交付也要先修掉。软件测试的核心目的不是“找到所有 bug”而是“确认已知的核心场景都符合预期”保证产品能交付出去并稳定运行。我在实习结束时最大的收获就是建立了这种“先怀疑、后确认”的工作习惯。本文还有配套的精品资源点击获取