软件工程第二次结对作业
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/fzu/SE2024 |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/fzu/SE2024/homework/13281 |
这个作业的目标 | 将上次结对作业的内容用代码实现 |
姓名及学号 | 陈家凯 102202126 |
结对成员及学号 | 许煊宇 102202113 |
Github 仓库地址 |
https://github.com/chenoojkk/102202126-102202113 |
具体分工
登录注册以及聊天功能:102202113
项目发布以及搜索功能:102202126
主要的几个功能分工完成,剩下的一些细节则合作完成。
PSP表格
Personal Software Process Stages | 预估耗时(小时) | 实际耗时(小时) |
---|---|---|
Planning 计划 | 2 | 1.5 |
Development 开发 | 2 | 2 |
Analysis 需求分析 (包括学习新技术) | 20 | 20 |
Design Spec 生成设计文档 | 1 | 1 |
Design Review 设计复审 | 1 | 1 |
Coding Standard 代码规范 | 5 | 5 |
Design 具体设计 | 10 | 10 |
Coding 具体编码 | 30 | 35 |
Code Review 代码复审 | 2 | 2 |
Test 测试(自我测试,修改代码,提交修改) | 1 | 1 |
Reporting 报告 | 1 | 1 |
Test Report 测试报告 | 1 | 1 |
Size Measurement 计算工作量 | 1 | 1 |
Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 | 0.5 | 0.5 |
合计 | 77.5 | 82 |
解题思路描述与设计实现说明
-
代码实现思路,文字描述(部分)
以下是实现聊天功能的代码思路:
1. 页面布局
在
chat.wxml
文件中定义聊天界面的布局,包括消息显示区域和输入框。使用scroll-view
组件实现消息的滚动显示,使用input
组件实现消息输入框,并添加发送按钮。2. 样式设计
在
chat.wxss
文件中定义页面的样式,使页面美观且易于使用。包括消息气泡的样式、头像的样式、输入框和发送按钮的样式等。3. 数据绑定
在
chat.js
文件中,通过onLoad
和onShow
方法从全局数据中获取用户信息和聊天记录,并绑定到页面上。使用setData
方法将数据绑定到页面的data
对象中。4. 数据库操作
在
chat.js
文件中,通过wx.cloud.database
方法从数据库中获取聊天记录和好友信息,并更新到页面上。使用watch
方法实时监听聊天记录的变化,并更新页面显示。5. 事件处理
在
chat.js
文件中定义输入框的输入事件和发送按钮的点击事件,并实现相应的处理函数。输入事件用于实时更新输入框的内容,发送按钮的点击事件用于将输入框的内容发送到数据库,并更新聊天记录。
以下是实现 publishsubject
功能的代码思路:
1.页面布局
在 publishsubject.wxml
文件中定义表单布局,包括项目名称、项目描述、项目合伙人和上传图片的输入 框, 以及提交按钮。
2.样式设计
在 publishsubject.wxss
文件中定义页面的样式,使页面美观且易于使用。包括输入框、按钮和图片预览 的样式。
3.数据绑定
在 publishsubject.js
文件中,通过 onLoad
方法初始化页面数据,并绑定到页面上。使用 setData
方 法将数据绑定到页面的 data 对象中。
4.数据处理
在 publishsubject.js
文件中定义输入框的输入事件、图片选择事件和提交按钮的点击事件,并实现相应 的处理函数。输入事件用于实时更新输入框的内容,图片选择事件用于选择并预览图片,提交按钮的点击事 件用于提交表单数据。
5.数据库操作
在 publishsubject.js
文件中,通过 wx.cloud.database
方法将表单数据存储到云数据库中,并在提交 后清空输入框内容和图片预览。
-
关键实现的流程图或数据流图
-
贴出你认为重要的/有价值的代码片段,并解释
projectintro
页面的主要功能是展示项目详情,并提供申请加入项目和联系项目发布人的功能。以下是对该页面的详细解释:1. 引入工具模块
const utils = require("../../utils/util");
引入工具模块,用于格式化时间等操作。
2. 页面初始化
Page({ data: { project: {} }, onLoad(options) { const eventChannel = this.getOpenerEventChannel(); eventChannel.on('sendProjectData', (data) => { this.setData({ project: data.project }); }); },
页面加载时,通过事件通道接收并设置项目数据。
3. 申请加入项目
applyToJoin() { const app = getApp(); const userInfo = app.globalData.userInfo; const project = this.data.project; if (!project._id) { wx.showToast({ icon: "none", title: '项目ID不存在', }); return; } const db = wx.cloud.database(); db.collection('chat_user').where({ account_id: project.user }).get({ success(res) { if (res.data.length > 0) { const userB = res.data[0]; const newMessage = { id: userInfo._id, text: `${userInfo.account_id} 申请加入项目 ${project.projectName}`, time: utils.formatTime(new Date()) }; db.collection('chat_record').where({ userA_id: userInfo._id, userB_id: userB._id, }).get({ success(res) { if (res.data.length > 0) { const recordId = res.data[0]._id; const record = res.data[0].record; record.push(newMessage); db.collection('chat_record').doc(recordId).update({ data: { record: record }, success(res) { wx.showToast({ title: '申请已发送!', icon: 'success', duration: 2000 }); }, fail(err) { wx.showToast({ title: '申请发送失败', icon: 'none', duration: 2000 }); } }); } else { db.collection('chat_record').where({ userA_id: userB._id, userB_id: userInfo._id, }).get({ success(res) { if (res.data.length > 0) { const recordId = res.data[0]._id; const record = res.data[0].record; record.push(newMessage); db.collection('chat_record').doc(recordId).update({ data: { record: record }, success(res) { wx.showToast({ title: '申请已发送!', icon: 'success', duration: 2000 }); }, fail(err) { wx.showToast({ title: '申请发送失败', icon: 'none', duration: 2000 }); } }); } else { const newRecord = { userA_id: userInfo._id, userA_account_id: userInfo.account_id, userA_avatarUrl: userInfo.avatarUrl, userB_id: userB._id, userB_account_id: userB.account_id, userB_avatarUrl: userB.avatarUrl, projectId: project._id, record: [newMessage], friend_status: false }; db.collection('chat_record').add({ data: newRecord, success(res) { wx.showToast({ title: '申请已发送!', icon: 'success', duration: 2000 }); }, fail(err) { wx.showToast({ title: '申请发送失败', icon: 'none', duration: 2000 }); } }); } }, fail(err) { wx.showToast({ title: '申请发送失败', icon: 'none', duration: 2000 }); } }); } }, fail(err) { wx.showToast({ title: '申请发送失败', icon: 'none', duration: 2000 }); } }); } else { wx.showToast({ title: '项目发布人信息未找到', icon: 'none', duration: 2000 }); } }, fail(err) { wx.showToast({ title: '获取项目发布人信息失败', icon: 'none', duration: 2000 }); } }); },
该函数用于申请加入项目。首先获取当前用户信息和项目数据,然后查询项目发布人的信息,并向其发送申请消息。如果聊天记录已存在,则更新记录;否则,创建新记录。
4. 联系项目发布人
contactUs() { const app = getApp(); const userInfo = app.globalData.userInfo; const project = this.data.project; const db = wx.cloud.database(); db.collection('chat_user').where({ account_id: project.user }).get({ success(res) { if (res.data.length > 0) { const userB = res.data[0]; db.collection('chat_record').where({ userA_id: userInfo._id, userB_id: userB._id, friend_status: true }).get({ success(res) { if (res.data.length > 0) { wx.navigateTo({ url: '/pages/chat/chat?id=' + res.data[0]._id }); } else { const newMessage = { id: userInfo._id, text: `${userInfo.account_id} 申请添加你为好友`, time: new Date() }; const newRecord = { userA_id: userInfo._id, userA_account_id: userInfo.account_id, userA_avatarUrl: userInfo.avatarUrl, userB_id: userB._id, userB_account_id: userB.account_id, userB_avatarUrl: userB.avatarUrl, projectId: project._id, record: [newMessage], friend_status: false }; db.collection('chat_record').add({ data: newRecord, success(res) { wx.showToast({ title: '好友申请已发送!', icon: 'success', duration: 2000 }); }, fail(err) { wx.showToast({ title: '好友申请发送失败', icon: 'none', duration: 2000 }); } }); } }, fail(err) { wx.showToast({ title: '检查好友状态失败', icon: 'none', duration: 2000 }); } }); } else { wx.showToast({ title: '项目发布人信息未找到', icon: 'none', duration: 2000 }); } }, fail(err) { wx.showToast({ title: '获取项目发布人信息失败', icon: 'none', duration: 2000 }); } }); } });
该函数用于联系项目发布人。首先获取当前用户信息和项目数据,然后查询项目发布人的信息。如果双方已是好友,则跳转到聊天页面;否则,发送好友申请。
附加特点设计与展示
homepage.js
文件中的 loadProjects()
函数实现了将项目按照新到旧的顺序排列。以下是对该函数的详细解释:
-
获取数据库实例:
const db = wx.cloud.database();
通过
wx.cloud.database()
方法获取云数据库实例。 -
查询项目并按上传时间降序排列:
db.collection('projects').orderBy('uploadTime', 'desc').get({
使用
orderBy('uploadTime', 'desc')
方法按上传时间降序排列项目。这样,最新上传的项目会排在最前面。 -
处理查询结果:
success: res => { this.setData({ results: res.data, noResults: false // 确保在加载项目时不显示“未找到相关项目”提示 }); wx.stopPullDownRefresh(); // 停止下拉刷新 },
在查询成功的回调函数中,将查询到的项目数据存储在页面的数据对象
results
中,并确保不显示“未找到相关项目”的提示。同时,调用wx.stopPullDownRefresh()
方法停止下拉刷新。 -
处理查询失败:
fail: err => { console.error('加载项目失败:', err); wx.stopPullDownRefresh(); // 停止下拉刷新 }
在查询失败的回调函数中,输出错误信息到控制台,并调用
wx.stopPullDownRefresh()
方法停止下拉刷新。
完整的 loadProjects()
函数如下:
loadProjects() {
const db = wx.cloud.database();
db.collection('projects').orderBy('uploadTime', 'desc').get({
success: res => {
this.setData({
results: res.data,
noResults: false // 确保在加载项目时不显示“未找到相关项目”提示
});
wx.stopPullDownRefresh(); // 停止下拉刷新
},
fail: err => {
console.error('加载项目失败:', err);
wx.stopPullDownRefresh(); // 停止下拉刷新
}
});
}
成果展示如下:
原本的首页:
发布新项目:
新发布的项目展示在项目列表的上方,且在搜索框中显示新发布的项目名称:
目录结构
# CDCP
## 目录结构
│ .eslintrc.js
│ app.js
│ app.json
│ app.wxss
│ LICENSE
│ package-lock.json
│ package.json
│ project.config.json
│ project.private.config.json
│ README.md
│ sitemap.json
│
├─.idea
│ │ .gitignore
│ │ CDCP.iml
│ │ misc.xml
│ │ modules.xml
│ │ workspace.xml
│ │
│ └─inspectionProfiles
│ profiles_settings.xml
│
├─images
│ │ 1.png
│ │ 2.png
│ │ 3.png
│ │ no_message.png
│ │
│ └─icon
│ friends.png
│ index.png
│ message.png
│ unknown.png
│ user.png
│
├─pages
│ ├─changeInformation
│ │ changeInformation.js
│ │ changeInformation.json
│ │ changeInformation.wxml
│ │ changeInformation.wxss
│ │ # 修改信息页面
│ │ # 用户可以在此页面修改个人信息
│ │
│ ├─chat
│ │ chat.js
│ │ chat.json
│ │ chat.wxml
│ │ chat.wxss
│ │ # 聊天页面
│ │ # 用户可以在此页面与其他用户进行聊天
│ │
│ ├─friends
│ │ friends.js
│ │ friends.json
│ │ friends.wxml
│ │ friends.wxss
│ │ # 好友页面
│ │ # 用户可以在此页面查看和管理好友列表
│ │
│ ├─homepage
│ │ homepage.js
│ │ homepage.json
│ │ homepage.wxml
│ │ homepage.wxss
│ │ # 首页
│ │ # 显示项目列表和导航到其他页面的入口
│ │
│ ├─IdentityAuthentication
│ │ │ IdentityAuthentication.js
│ │ │ IdentityAuthentication.json
│ │ │ IdentityAuthentication.wxml
│ │ │ IdentityAuthentication.wxss
│ │ │ # 身份认证页面
│ │ │ # 用户可以在此页面进行身份认证
│ │ │
│ │ ├─student
│ │ │ student.js
│ │ │ student.json
│ │ │ student.wxml
│ │ │ student.wxss
│ │ │ # 学生认证页面
│ │ │ # 学生用户可以在此页面进行身份认证
│ │ │
│ │ └─teacher
│ │ teacher.js
│ │ teacher.json
│ │ teacher.wxml
│ │ teacher.wxss
│ │ # 教师认证页面
│ │ # 教师用户可以在此页面进行身份认证
│ │
│ ├─login
│ │ login.js
│ │ login.json
│ │ login.wxml
│ │ login.wxss
│ │ # 登录页面
│ │ # 用户可以在此页面进行登录操作
│ │
│ ├─message
│ │ message.js
│ │ message.json
│ │ message.wxml
│ │ message.wxss
│ │ # 消息页面
│ │ # 用户可以在此页面查看和管理消息
│ │
│ ├─mp
│ │ mp.js
│ │ mp.json
│ │ mp.wxml
│ │ mp.wxss
│ │ # 用户发布的项目详情页面
│ │
│ │
│ ├─myprojects
│ │ myprojects.js
│ │ myprojects.json
│ │ myprojects.wxml
│ │ myprojects.wxss
│ │ # 我的项目页面
│ │ # 用户可以在此页面查看和管理自己的项目
│ │
│ ├─projectintro
│ │ projectintro.js
│ │ projectintro.json
│ │ projectintro.wxml
│ │ projectintro.wxss
│ │ # 项目介绍页面
│ │ # 显示项目的详细信息
│ │
│ ├─publishsubject
│ │ publishsubject.js
│ │ publishsubject.json
│ │ publishsubject.wxml
│ │ publishsubject.wxss
│ │ # 发布项目页面
│ │ # 用户可以在此页面发布新项目
│ │
│ ├─register
│ │ register.js
│ │ register.json
│ │ register.wxml
│ │ register.wxss
│ │ # 注册页面
│ │ # 用户可以在此页面进行注册操作
│ │
│ ├─searchinfo
│ │ searchinfo.js
│ │ searchinfo.json
│ │ searchinfo.wxml
│ │ searchinfo.wxss
│ │ # 搜索页面
│ │ # 用户可以在此页面搜索项目信息
│ │
│ └─user
│ user.js
│ user.json
│ user.wxml
│ user.wxss
│ # 用户页面
│ # 显示用户的个人信息和设置
│
└─utils
util.js
# 工具模块
# 包含一些通用的工具函数
单元测试
选用的测试工具:使用 jest 作为测试框架
使用步骤
-
在项目中安装了 jest
npm install --save-dev jest
-
在项目根目录下创建一个 tests 文件夹,并在其中创建一个
homepage.test.js
文件。 在homepage.test.js
文件中,编写以下测试代码// __tests__/homepage.test.js const wx = { cloud: { database: jest.fn().mockReturnValue({ collection: jest.fn().mockReturnValue({ orderBy: jest.fn().mockReturnThis(), limit: jest.fn().mockReturnThis(), get: jest.fn().mockImplementation(({ success, fail }) => { success({ data: [{ projectName: 'Test Project', uploadTime: new Date() }] }); }) }) }) }, stopPullDownRefresh: jest.fn(), navigateTo: jest.fn() }; global.wx = wx; const homepage = require('../pages/homepage/homepage.js'); describe('homepage.js', () => { beforeEach(() => { homepage.setData = jest.fn(); }); test('loadProjects should load projects and set data', () => { homepage.loadProjects(); expect(homepage.setData).toHaveBeenCalledWith({ results: [{ projectName: 'Test Project', uploadTime: new Date() }], noResults: false }); expect(wx.stopPullDownRefresh).toHaveBeenCalled(); }); test('loadLatestProject should load the latest project and set data', () => { homepage.loadLatestProject(); expect(homepage.setData).toHaveBeenCalledWith({ latestProjectName: 'Test Project' }); }); });
-
在
package.json
文件中添加以下脚本"scripts": { "test": "jest" }
-
运行测试
npm test
构造测试数据的思路以及未来应对策略
-
覆盖常见情况:
- 正常情况:测试函数在正常情况下的行为,例如数据库查询成功并返回预期数据。
- 边界情况:测试函数在边界条件下的行为,例如数据库查询返回空数据或只有一条数据。
- 异常情况:测试函数在异常情况下的行为,例如数据库查询失败或网络错误。
-
模拟外部依赖:
- 使用
jest.fn()
模拟外部依赖(如wx
对象及其方法),确保测试环境与实际运行环境一致。
- 使用
-
验证函数行为:
- 验证函数是否正确处理数据并更新页面状态。
- 验证函数是否正确处理错误并输出错误信息。
-
考虑未来扩展:
- 编写灵活的测试代码,便于将来添加新的测试用例。
- 使用描述性强的测试名称,便于测试人员理解测试目的。
给出Github
的代码签入记录截图
遇到的代码模块异常或结对困难及解决方法
模块异常:homepage首页的搜素框功能无法响应搜索 ,可以在数据库进行projectName
的对比,但无法在首页显示搜索结果
解决方法:发现在homepage存在多个组件重叠,故无法正常显示搜索结果。在主页的搜索框功能中,新建跳转页面Searchifo
,在此页面重新运行搜索功能代码,解决问题。
评价你的队友
结伴队友:102202126陈家凯
值得学习的地方:
- 责任心:他对项目的每一个细节都非常关注,确保每一项任务都能按时高质量地完成。
- 创新能力:他总是能够提出一些创新的想法和解决方案,使我们的项目更加出色
需要改进的地方:
- 时间管理:他能够合理安排时间,高效完成任务。我需要向他学习如何更好地管理时间,提高工作效率。
- 持续学习:他总是保持对新技术的热情,不断学习和应用新的知识。我需要向他学习这种持续学习的精神,不断提升自己的技术水平。