Luminance

导航

AIGC标识 2026秋软件工程结对作业(第二次)——“校园拾光”程序实现

2026 秋软件工程结对作业(第二次)——“校园拾光”程序实现

项目 内容
这个作业属于哪个课程 H202601 软件工程与软件工程实践
这个作业要求在哪里 2026 秋软件工程结对作业(第二次之程序实现)
我的博客/本作业链接 章玲102402101 / 本次作业
结对队友博客/本作业链接 吴雅晴102402103 / 本次作业
GitHub 仓库 102402101-102402103

一、项目简介

“校园拾光”是一个无需后台即可运行的校园失物招领 Web 应用。我们基于第一次结对作业的原型,把“发布信息—浏览或搜索—查看详情—联系发布者—更新状态”实现为可操作的完整流程。用户可以发布寻物或招领信息,通过关键词、类型、类别和状态筛选内容,在详情中查看并复制联系方式;发布者可在“我的发布”中把信息更新为“已找到”或“已归还”。

为保证助教下载后可以直接复现,项目使用原生 HTML、CSS、JavaScript 和浏览器 localStorage,不依赖服务器、数据库或第三方框架,Chrome 直接打开 index.html 即可运行。

二、结对分工

成员 主要工作
章玲 需求边界、数据模型、store.js 业务逻辑、本地存储、单元测试、测试报告与博客整合
吴雅晴 页面结构、视觉样式、响应式布局、发布/详情/我的发布交互、人工走查与 README 完善
共同完成 功能复审、异常场景补充、Chrome 兼容性测试、Git 提交与最终验收

我们的合作方式是“模块主责 + 交叉复审”:完成一个可运行模块后先提交,再由另一位成员按真实用户流程检查,发现问题后通过独立提交修正。

三、PSP

PSP2.1 Personal Software Process Stages 章玲预估/实际(分钟) 吴雅晴预估/实际(分钟)
Planning 计划(小计) 20 / 20 20 / 20
Estimate 估计任务时间 20 / 20 20 / 20
Development 开发(小计) 660 / 820 635 / 770
Analysis 需求分析与技术学习 70 / 85 65 / 75
Design Spec 设计文档 45 / 55 50 / 60
Design Review 设计复审 35 / 45 35 / 50
Coding Standard 制定代码规范 25 / 25 25 / 25
Design 具体设计 70 / 85 90 / 105
Coding 具体编码 230 / 285 210 / 255
Code Review 代码复审 55 / 70 50 / 65
Test 自测、修改与提交 130 / 170 110 / 135
Reporting 报告(小计) 130 / 155 120 / 145
Test Report 测试报告 60 / 70 45 / 55
Size Measurement 工作量统计 20 / 20 20 / 20
Postmortem 总结与改进计划 50 / 65 55 / 70
合计 仅累加计划、开发、报告三个小计 810 / 995 775 / 935

时间核实说明:上述数值为当前整理稿,需要两位成员根据各自实际记录核实后作为最终 PSP;小计与分项不重复相加。分工和过程描述也应由双方确认。

实际时间高于预估,主要原因是我们低估了表单异常输入、组合筛选和窄屏布局的复测时间。后续会把“异常分支测试”和“多尺寸界面检查”单独列入计划。

四、解题思路与设计实现

4.1 代码实现思路

项目采用简单的三层划分:

  1. store.js 是与界面无关的业务层,负责校验、创建、搜索、排序、状态更新、删除与统计。
  2. app.js 是交互层,负责页面切换、DOM 渲染、表单提交、详情弹窗和 localStorage 持久化。
  3. styles.css 是表现层,统一颜色、卡片、表单和响应式断点。

把纯业务函数从 DOM 中分离后,既方便使用 Node.js 自动测试,也降低了修改界面时破坏核心规则的风险。数据模型统一包含 id、type、name、category、location、time、description、publisher、contact、status、owner、createdAt 字段;寻物默认状态为“寻找中”,招领默认状态为“待认领”。

4.2 关键流程图

校园拾光程序流程图

流程从首页分成浏览/搜索、发布、我的发布三个分支,并把“无搜索结果”“必填项不完整”“提交失败”“状态修改”纳入异常与闭环处理。

4.3 有价值的代码片段

以下搜索函数支持关键词与三种条件组合,并确保结果按发布时间倒序排列:

function searchItems(items, filters = {}) {
  const keyword = cleanText(filters.keyword).toLowerCase();
  return [...items].filter(item => {
    const text = [item.name, item.location, item.description, item.category]
      .join(' ').toLowerCase();
    return (!keyword || text.includes(keyword))
      && (!filters.type || filters.type === 'all' || item.type === filters.type)
      && (!filters.category || filters.category === 'all' || item.category === filters.category)
      && (!filters.status || filters.status === 'all' || item.status === filters.status);
  }).sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
}

状态更新采用不可变数组,便于测试,也避免原数据被意外修改:

function updateStatus(items, id, status) {
  if (!VALID_STATUS.includes(status))
    return { ok: false, error: '无效状态', items };
  if (!getItem(items, id))
    return { ok: false, error: '信息不存在', items };
  return {
    ok: true,
    items: items.map(item => item.id === id ? { ...item, status } : item)
  };
}

五、附加特点设计与展示

5.1 多条件组合筛选

除了题目要求的关键词搜索,我们增加了信息类型、物品类别和处理状态筛选。它能让寻找校园卡的用户排除耳机、书本等无关信息,也能隐藏已经解决的记录,减少查找成本。实现上由统一的 searchItems 函数逐项判断,各条件既可单独使用,也可任意组合。

5.2 一键复制联系方式

详情页不会引入超出作业范围的即时聊天,而是使用浏览器 Clipboard API 一键复制发布者提供的联系方式;若浏览器禁止剪贴板权限,会直接显示可手动复制的内容。这既保留了最核心的联系能力,也避免增加后台服务。

实现时先调用 Clipboard API,失败时通过提示框给出可手动复制的原文,不会因为浏览器权限不同而中断流程:

document.querySelector('#copy-contact').addEventListener('click', async () => {
  try {
    await navigator.clipboard.writeText(item.contact);
    toast('联系方式已复制');
  } catch {
    toast(`请手动复制:${item.contact}`);
  }
});

5.3 本地持久化与隐私提示

发布和状态修改都会写入 localStorage,刷新页面后数据仍然存在;发布页明确提醒用户不要填写证件号、支付密码等敏感信息。该设计不需要服务器,便于评分者复现,也符合本次“不做复杂后台”的范围。

5.4 实现成果

首页与信息浏览

首页与信息浏览

搜索与组合筛选

搜索与组合筛选

发布信息

填写真实示例数据的发布信息页面

物品详情与联系方式

真实示例物品详情与一键复制

我的发布与状态闭环

我的发布与状态更新

六、目录与使用说明

102402101-102402103/
├─ index.html              # 应用入口
├─ css/styles.css          # 样式与响应式布局
├─ js/store.js             # 业务逻辑
├─ js/data.js              # 演示数据
├─ js/app.js               # 页面交互与本地存储
├─ tests/store.test.js     # 16 个自动化测试
└─ docs/                   # 博客、测试报告、流程图和截图

测试人员下载全部文件后,用 Google Chrome 打开 index.html 即可。若需要通过本地服务器运行,可在项目目录执行 python -m http.server 8080,然后访问 http://localhost:8080。单元测试使用 Node.js 18+:

具体操作:打开顶部 GitHub 仓库,点击 Code → Download ZIP,解压完整文件夹,在文件夹中找到 index.html,右键选择“打开方式 → Google Chrome”。不要只下载 HTML 文件,CSS 与 JavaScript 必须保留原有相对路径。浏览网页不需要安装 Node.js;只有运行下面的自动化测试时才需要 Node.js,并在包含 index.html 的文件夹打开终端执行命令。

人工验收顺序:进入信息大厅浏览卡片;输入关键词并组合筛选;打开详情核对描述并复制联系方式;发布一条填写完整的寻物或招领信息;进入“我的发布”修改状态;回到信息大厅确认状态同步,刷新后再次确认记录保留。补充检查空名称、短描述、搜索无结果和剪贴板不可用等分支。

数据保存在当前浏览器的 localStorage 中,不同电脑不会自动共享发布内容;本次交付用于本地演示和测试。示例中的学号式联系方式为演示数据,发布页与详情页使用同一条“白色保温杯”记录,便于核对字段传递。

node --test tests/store.test.js

七、单元测试

7.1 工具与简易教程

我们选用 Node.js 内置的 node:test 与 node:assert/strict。学习步骤如下:

  1. 在业务文件末尾通过 CommonJS 导出纯函数。
  2. 在测试文件中 require('../js/store.js')。
  3. 使用 test('说明', () => { ... }) 定义用例。
  4. 使用 assert.equal、assert.deepEqual 或 assert.ok 判断实际结果。
  5. 执行 node --test tests/store.test.js;退出码为 0 且全部显示通过即成功。

7.2 测试代码示例

test('合法状态更新成功且不改变原数组', () => {
  const result = core.updateStatus(items, '1', '已找到');
  assert.equal(result.ok, true);
  assert.equal(result.items[0].status, '已找到');
  assert.equal(items[0].status, '寻找中');
});

test('非法状态不会写入', () => {
  assert.equal(core.updateStatus(items, '1', '已删除').ok, false);
});

7.3 测试数据设计与“刁难”场景

我们采用白盒分支覆盖,同时结合等价类与边界值:完整表单/空名称、描述恰好不足 5 字、有效/无效时间、存在/不存在 ID、合法/非法状态、名称/地点关键词、有/无结果、单条件/组合条件。测试人员可能输入空格、搜索不存在的物品、反复更新状态或传入非法状态,因此核心函数会先清理文本并校验枚举值。当前共有 16 个测试,全部通过。

示例中的 updateStatus 测试检查合法状态更新、原数组不被修改,以及非法状态被拒绝。16 个用例还覆盖 validateItem、创建信息、searchItems、删除和统计等纯业务函数。

7.4 覆盖范围与评价

正文列出以下 5 个代表性用例,完整 16 个用例见文末附录,与仓库 tests/store.test.js 一一对应:

用例 输入/操作 预期结果
T01 正常校验 完整合法表单 valid === true
T02 空白名称 名称为两个空格 valid === false
T03 描述边界 描述为“太短”(不足 5 字) 返回描述错误
T10 组合筛选 数码产品且已归还 仅返回 ID 3
T12 状态更新 ID 1 改为已找到 更新成功,原数组仍为寻找中

白盒测试根据业务函数的判断分支设计:校验成功/失败、ID 存在/不存在、状态合法/非法,以及筛选条件匹配/不匹配。16 个自动化用例能验证这些核心业务规则,但没有覆盖全部分支,也没有覆盖 DOM 点击、图片选择、浏览器存储配额或剪贴板权限,不能仅凭单元测试通过认定界面全部正常。上述浏览器行为应结合人工验收步骤检查,后续可增加自动化浏览器测试。

八、GitHub 签入记录

项目仓库:Luminance-l/102402101-102402103,完整提交记录可在 Commits 页面 查看。我们按功能拆分提交,保证每次提交只对应一类可验证的增量:

提交 主要内容
f569c4b 建立数据模型与核心业务规则
402d84f 完成响应式页面和主要用户流程
f57895e 增加 16 个自动化测试与使用说明
465d475 补充博客、测试说明和程序流程图
3c068ac 整理运行截图与验收清单
a1acd10 优化博客证据、图片尺寸和真实案例展示

队友需要使用自己的 GitHub 账号 fork 本仓库,在独立分支完成一次真实复审修改并向主仓库提交 Pull Request;合并后,本节补充 commits 列表与 Pull Request 截图。这样 GitHub 记录能够真实体现两人的独立操作和代码交互。

待补证据:GitHub commits 页面截图、吴雅晴提交的 Pull Request 截图及链接。

九、异常、结对困难及解决方法

9.1 界面与业务逻辑耦合

  • 问题:一开始把输入检查和数据修改都放在按钮的点击事件里,想单独测试某条规则时不方便。
  • 尝试:先通过页面操作逐项检查,但每改一次都要重新点击验证,不便于重复测试。
  • 解决:把校验、搜索和状态更新抽成 store.js 纯函数,界面只调用结果。
  • 收获:可测试性应在设计阶段考虑,而不是编码结束后补测试。

9.2 筛选条件相互覆盖

  • 问题:只输入关键词时结果正常,同时选择类别和状态后,结果有时与预期不一致。
  • 尝试:分别检查搜索框和下拉框的处理逻辑,发现它们各自过滤数据,后一次可能只在上一次的结果中继续查找。
  • 解决:每次都从完整数据出发,由一个函数同时应用全部条件,并增加组合筛选测试。
  • 收获:多个 UI 条件应对应一个确定的数据入口,避免隐藏状态。

9.3 状态闭环容易遗漏

  • 问题:原型重点放在发布和详情,“联系之后如何结束信息”不够明确。
  • 尝试:对照完整用户流程,从“联系完成”反向检查首页、详情和“我的发布”中是否能辨认信息的处理状态。
  • 解决:在“我的发布”中按寻物/招领分别提供“已找到/已归还”,更新后首页状态同步改变。
  • 收获:完成页面不等于完成业务,必须从用户任务的终点反查数据状态。

十、队友评价

吴雅晴在界面层级、卡片布局和交互一致性方面很细致,能够从普通用户视角发现信息密度和提示语问题,也会主动复查不同宽度下的显示效果。值得我学习的是她愿意反复走查页面而不是只看代码。可以改进的是编码任务开始前应更早确定组件命名和样式变量,减少后期统一样式的返工。

十一、总结与改进

本次结对让我们从“能画出原型”走到“能运行、能测试、能复现”。后续若继续开发,会增加图片压缩、编辑已发布内容和更完善的数据导入导出,但仍会优先维护当前核心流程的稳定性,不盲目加入地图、聊天或实名认证等超出范围的模块。

AI 使用说明:本项目在需求整理、代码检查和博客 Markdown 排版阶段使用了 AI 辅助;功能取舍、运行验证、测试数据与结对过程均由成员核对,最终代码需由两位成员共同理解并维护。

十二、提交前待核实事项

  • 过程描述已根据本次协作情况整理;两位成员仍需核实 PSP 数字是否符合实际投入。
  • 补充 GitHub 签入记录截图以及真实 fork/PR 证据。
  • 在班级群结对统计表填写两人信息和仓库地址,并由两人分别在作业页面提交各自博客链接。正常截止时间为 2026 年 10 月 10 日 23:59:59。

附录:完整自动化测试用例

测试夹具包含校园卡(寻物、寻找中)、钥匙(招领、待认领)、耳机(招领、已归还)三条记录。测试以业务函数内部判断为依据,兼顾正常、异常与边界输入。

编号 测试内容/输入 预期结果
T01 合法完整表单 校验通过
T02 名称为两个空格 校验失败
T03 描述“太短” 描述错误
T04 时间 not-a-date 时间错误
T05 创建寻物信息 初始状态寻找中
T06 创建招领信息 初始状态待认领
T07 关键词“校园” 返回 ID 1
T08 关键词“ 图书馆 ” 去除首尾空白,返回 ID 2
T09 类型 found 返回两条招领记录
T10 类别数码产品、状态已归还 返回 ID 3
T11 无筛选条件 按时间倒序返回 2、1、3
T12 更新 ID 1 为已找到 成功且不改变原数组
T13 更新不存在的 ID 404 返回失败
T14 更新为非法状态已删除 返回失败
T15 删除已有 ID 2 成功,剩余两条
T16 统计三条夹具记录 总数 3、未解决 2、已解决 1

完整可执行测试代码:tests/store.test.js。使用第七节命令一次运行全部 16 个用例,断言失败会显示对应名称并返回非零退出码。

posted on 2026-09-29 23:25  Luminance  阅读(13)  评论(0)    收藏  举报