第二次结对作业之代码实现
📫一、先来了解了解我们吧!
结对同学:
学号 | 姓名 | 个人博客主页 |
---|---|---|
102201241 | 戴康怡 | 102201241 |
102201542 | 曾庆徽 | 102201542 |
Github项目地址
分工情况
一开始呢是打算我做前端,队友做后端的,但没想到队友太聪明了!就变成了全过程讨论协作!哇这里就不得不提一下这次的作业我们真的是呕心沥血完成!!可以说接近一周的课都没有听,早上一睡醒就是在想软工作业,一直做到深夜,吃饭也在做,睡觉梦里也在做哈哈哈哈!连续熬了好几个大夜,真的非常非常认真完成啦!!
戴康怡同学:
(1)调整项目框架给出具体方案,实现项目广场,项目创建,以及个人中心前端界面开发,修改提供的前端模板,实现各个页面之间交互按钮的设计与实现。
(2)主要后端开发,管理后端数据库存储与更新,调试代码修改bug。
(3)绘制数据结构图与流程图。
曾庆徽同学:
(1)负责项目框架搭建,提供基础的注册,登录以及聊天的前端模板。
(2)后端数据库集合设计以及部分后端开发。
(3)使用Jest工具编写单元测试。
PSP表格
PSP2.1 Personal Software Process Stages | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 20 |
Estimate | 估计这个任务需要多少时间 | 5 | 5 |
Development | 开发 | 180 | 220 |
Analysis | 需求分析 (包括学习新技术) | 180 | 300 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 20 | 5 |
Design | 具体设计 | 300 | 480 |
Coding | 具体编码 | 500 | 1800 |
Code Review | 代码复审 | 30 | 90 |
Test | 测试(自我测试,修改代码,提交修改) | 60 | 120 |
Size Measurement | 计算工作量 | 15 | 15 |
Postmortem | 事后总结 | 30 | 30 |
合计 | 1350 | 3085 |
🔭二、解题与设计
(1)解题思路描述与设计实现说明
我们完成的是一个“ProjectPartner"小程序。我们初步设计将涵盖用户创建项目、申请加入项目、实时聊天等功能。我们初步构建的信息架构图如下:
可以说我们在做的过程中是一个不断完善的过程,我来介绍一下实现过程吧~
首先呢,我们在前端使用微信开发者工具实现,同时使用云开发及相关函数实现后端操作。我们首先创建了数据库,数据库结构如下图:
同时这里贴出创建数据库projects集合代码:
wx.cloud.database().collection('projects').add({
data: {
title,
description,
nickname,
account_id, // 添加创建者昵称
likecount,
members: [account_id]
}
}).then(res => {
const newProject = {
_id: res._id,
title,
description,
nickname,
account_id, // 包含昵称
likecount,
members: [account_id]
};
首先!要进入小程序肯定要先注册与登录啦!我们设计一进入小程序便显示“欢迎来到ProjectPartner”以及slogan和一个很好看的图片~(来自素材网不侵权!)引导用户登录或注册。若用户还未有账号,可以点击注册,首先允许用户微信授权,接着设置账号密码。若用户已有账号,则可直接输入账号、密码进入。
登录后,我们的小程序设计导航栏有四个界面,分别为“项目广场”、“创建项目”、“通讯录“、”个人中心“。接着我就按照这个顺序分别介绍!
项目广场
(1)项目广场中有所有的项目,这些项目均来自于用户创建,按时间顺序呈现,我们使用到了reverse()函数,将最新的呈现在最上面。用户可以通过界面上方的搜索框进行搜索,会根据用户的输入与项目的名称、创建者昵称、项目描述相匹配,有匹配的字符串便可直接在下方显示。用户可以通过搜索定位到自己感兴趣的项目或创建者。
这里给出获取搜索到的项目的代码:
filterProjects() {
const { projects, searchTerm } = this.data;
if (!searchTerm) {
this.setData({ filteredProjects: projects.reverse() }); // 反转原始项目数组
return;
}
const filteredProjects = projects.filter(project =>
(project.title || '').toLowerCase().includes(searchTerm) ||
(project.description || '').toLowerCase().includes(searchTerm)||
(project.nickname || '').toLowerCase().includes(searchTerm)
);
this.setData({ filteredProjects: filteredProjects.reverse() }); // 反转过滤后的数组
},
(2)点击每一个项目,便可进入项目详情界面。项目详情界面包含项目名称、创建人、点赞数、项目描述、项目成员。如果喜欢这个项目,可以点击”点赞“按钮,点赞数会相应增加,点赞数都是实时更新的。
这里给出点赞更新代码:
increaseLikeCount: function() {
const newLikeCount = this.data.project.likecount + 1;
// 更新本地点赞数
this.setData({
'project.likecount': newLikeCount
});
// 直接更新数据库中的点赞数
const db = wx.cloud.database();
db.collection('projects').doc(this.data.project._id).update({
data: {
likecount: newLikeCount
},
success: (res) => {
console.log('点赞数更新成功', res);
},
fail: (err) => {
console.error('点赞数更新失败', err);
}
});
},
同时如果对这个项目感兴趣,可以点击”申请加入项目“按钮,进入申请界面,填写自己的申请理由和联系方式,提交申请后创建此项目的用户会收到该申请;或点击“进入聊天”按钮,进入通讯录界面,与项目中的成员进行联系。
创建项目
在创建项目界面中可输入创建者昵称、项目标题、项目描述,其中设计字数限制分别为10、15、200。当达到字数限制时,用户则不可以再继续输入,同时出现告知用户的提示。
这里给出昵称字数限制代码:
onnameChange(event:any) {
const inputValue = event.detail.value;
// 如果输入超出最大长度,截取并更新
if (inputValue.length > this.data.nicknameMaxLength) {
this.setData({
nickname: inputValue.slice(0, this.data.nicknameMaxLength) // 只保留前10个字符
});
wx.showToast({
title: '昵称不能超过10个字',
icon: 'none'
});
} else {
this.setData({
nickname: inputValue // 更新昵称
});
}
},
填写完毕后,点击“发布项目”按钮,即可成功发布。同时跳转到项目广场界面,用户可以在项目广场上看到自己刚刚创建的项目。
通讯录
(1)通讯录界面显示“新朋友”、“我的好友”、“其他用户”三栏。“新朋友”处会显示收到的好友申请,若暂无好友申请会有相关提示,若有则会显示申请用户的头像、昵称,以及“同意”的按钮。点击同意按钮后,该用户则会出现在“我的好友”处。“其他用户”处显示小程序上的所有用户,用户也可以通过搜索框对感兴趣的用户进行搜索,每个用户右侧都会显示“添加好友”按钮,点击即会发送好友申请。
这里给出加好友的部分代码:
addFriend(e) {
var index = e.currentTarget.dataset.index;
var that = this;
wx.cloud.database().collection('chat_record').add({
data: {
userA_id: that.data.userInfo._id,
userA_account_id: that.data.userInfo.account_id,
userA_avatarUrl: that.data.userInfo.avatarUrl,
userB_id: that.data.filteredUserList[index]._id,
userB_account_id: that.data.filteredUserList[index].account_id,
userB_avatarUrl: that.data.filteredUserList[index].avatarUrl,
record: [],
friend_status: false,
time: utils.formatTime(new Date()),
},
success(res) {
console.log(res);
wx.showToast({
title: '已发送好友申请',
});
},
});
},
(2)成为好友后,可以点击用户区域,进入聊天。聊天的记录会被实时记录下来。
个人中心
(1)个人中心会显示用户的头像、昵称、账号,以及该用户创建的项目。同时有修改头像的按钮,用户可以选择拍照或者选择相册的方式修改头像。还有一个“切换用户”按钮,点击进入登录界面。
(2)点击项目后会进入我的项目详情界面。这个界面会显示项目名称、项目描述、点赞数、项目成员信息。同时有一个收到的申请按钮。用户点击此按钮可以进入收到的申请界面,点击各个申请可以进入申请详情界面,可以看到申请人昵称、申请理由以及申请人的联系方式,同时有一个“同意”按钮,点击同意即会将该申请人加入为此项目成员中。
这里给出添加项目成员的项目代码:
applyForProject() {
wx.showToast({
icon: 'none',
title: '已同意',
})
const db = wx.cloud.database();
const projectId= String(this.data.application.project_id); // 替换为实际项目的 ID
console.log('申请列表:', this.data.application);
const newMemberAccountId = String(this.data.application.account_id); // 要添加的成员 account_id
db.collection('projects').doc(projectId).get()
.then(res => {
console.log("项目数据:", res.data);
})
.catch(err => {
console.error("获取项目数据失败:", err);
});
console.log("Updating project with ID:", projectId);
console.log("Adding member ID:", newMemberAccountId);
db.collection('projects').doc(projectId).update({
data: {
members: db.command.addToSet(newMemberAccountId) // 使用 addToSet 避免重复
}
}).then(res => {
console.log("成员添加成功", res);
wx.showToast({
title: '成员添加成功',
icon: 'success'
});
}).catch(err => {
console.error("添加成员失败", err);
wx.showToast({
title: '添加成员失败',
icon: 'none'
});
});
},
为什么要给出这个代码呢?因为我在这里花了很久很久很久的时间!也许你会说这有什么难的,怎么会花很久的时间!是因为我在wxml文件中通过data-project_id获取project_id数据的时候不小心在两个大括号里面按了个空格!!调试的时候因为是空格看不出来花了我非常非常久的时间!!
实现流程图:
(2)附加特点设计与展示
(1)在项目广场查看项目详情时,项目详情里可以为项目点赞并看到点赞数,一般来说点赞数最高的项目一般也能提现项目的火热度,想到一个冷冰冰的界面需要加上一点闪耀之处让大家感到大大的认可度,我们的点赞符号设计的也十分有新意。
点赞数也是实时更新的:
(2)在项目广场加入一个搜索栏,根据搜索的项目标题名称会显示出名称相匹配的项目。
(3)在个人中心中我们用户的昵称还有头像可以自行修改,修改后自动更新新的用户头像和昵称。
实现思路:
对于点赞数我们需要绑定一个点赞数变量跟随其他内容一起加入projects(项目)的数组里,并与点赞按钮互相绑定,当用户点赞时更新点赞数并保存进入projects(方案)数组。
对于搜索功能,我们需要创建一个搜索栏,支持输入项目标题。然后设计一个字符匹配函数,筛选项目标题相同。
对于昵称和头像的改动,昵称设置修改函数并将其保存,头像调用微信支持的的从手机相片里获取的API功能来实现。
最有价值代码片段:
选用的还是设置点赞数有关的代码
// projectDetail.ts
Page({
data: {
project: {
title: '',
nickname: '',
description: '',
likecount: 0,
_id: '',
members: [] // 新增字段用于存储项目成员
}
},
onLoad(options: any) {
const { title, nickname, description, likecount, _id } = options;
console.log('传入的项目 ID:', _id); // 检查传入的 ID
this.setData({
'project.title': title || '项目名称',
'project.nickname': nickname || '项目负责人昵称',
'project.description': description || '项目描述',
'project.likecount': Number(likecount) || 0,
'project._id': _id || '' // 确保 ID 被正确设置
});
// 获取项目成员
this.getProjectMembers(_id);
},
getProjectMembers(projectId:any) {
const db = wx.cloud.database();
db.collection('projects').doc(projectId).get().then(res => {
// 更新项目的成员数据
this.setData({
'project.members': res.data.members || [] // 获取成员数组
});
console.log("项目成员:", res.data.members);
}).catch(err => {
console.error("获取项目成员失败", err);
});
},
// 点赞按钮的点击事件处理函数
increaseLikeCount: function() {
const newLikeCount = this.data.project.likecount + 1;
// 更新本地点赞数
this.setData({
'project.likecount': newLikeCount
});
// 直接更新数据库中的点赞数
const db = wx.cloud.database();
db.collection('projects').doc(this.data.project._id).update({
data: {
likecount: newLikeCount
},
success: (res) => {
console.log('点赞数更新成功', res);
},
fail: (err) => {
console.error('点赞数更新失败', err);
}
});
},
applyForProject() {
const projectId = this.data.project._id; // 获取项目的 _id
wx.navigateTo({
url: `/pages/apply/apply?id=${projectId}` // 将 _id 作为参数传递
});
},
goToChat() {
wx.switchTab({
url: `/pages/friends/friends` // 将 _id 作为参数传递
});
}
});
理由是在这里是第一次添加点赞数到了我们的代码里,同时这段代码也包含了很多复杂的函数,包括获取项目成员,更新点赞数等,结构清晰,功能完善。
🌱三、目录说明和使用说明
(1)目录说明
miniprogram/
├── app.js # 全局逻辑文件,定义应用的生命周期回调函数
├── app.json # 全局配置文件,配置小程序的页面路径、窗口表现等
├── app.wxss # 全局样式文件,定义全局样式
├── sitemap.json # 网站地图文件,用于搜索引擎爬取的配置
├── pages/ # 小程序页面目录
│ ├── index/ # 首页目录
│ │ ├── index.js # 首页逻辑文件
│ │ ├── index.json # 首页配置文件
│ │ ├── index.wxml # 首页的页面结构文件
│ │ └── index.wxss # 首页的样式文件
│ ├── about/ # 关于页面
│ │ ├── about.js # 关于页面逻辑文件
│ │ ├── about.json # 关于页面配置文件
│ │ ├── about.wxml # 关于页面结构文件
│ │ └── about.wxss # 关于页面样式文件
│ ├── apply/ # 申请页面
│ │ ├── apply.js # 申请页面逻辑文件
│ │ ├── apply.json # 申请页面配置文件
│ │ ├── apply.wxml # 申请页面结构文件
│ │ └── apply.wxss # 申请页面样式文件
│ ├── applydetail/ # 申请详情页面
│ │ ├── applydetail.js #申请详情逻辑文件
│ │ ├── applydetail.json #申请详情配置文件
│ │ ├── applydetail.wxml #申请详情结构文件
│ │ └── applydetail.wxss #申请详情样式文件
│ ├── chat/ # 聊天页面
│ │ ├── chat.js # 聊天页面逻辑文件
│ │ ├── chat.json # 聊天页面配置文件
│ │ ├── chat.wxml # 聊天页面结构文件
│ │ └── chat.wxss # 聊天页面样式文件
│ ├── create/ # 创建项目页面
│ │ ├── create.js # 创建项目页面逻辑文件
│ │ ├── create.json # 创建项目页面配置文件
│ │ ├── create.wxml # 创建项目页面结构文件
│ │ └── create.wxss # 创建项目页面样式文件
│ ├── friends/ # 好友页面
│ │ ├── friends.js # 好友页面逻辑文件
│ │ ├── friends.json # 好友页面配置文件
│ │ ├── friends.wxml # 好友页面结构文件
│ │ └── friends.wxss # 好友页面样式文件
│ ├── login/ # 登录页面
│ │ ├── login.js # 登录页面逻辑文件
│ │ ├── login.json # 登录页面配置文件
│ │ ├── login.wxml # 登录页面结构文件
│ │ └── login.wxss # 登录页面样式文件
│ ├── myprojectDetail/ # 我的项目详情页面
│ │ ├── myprojectDetail.js # 我的项目详情页面逻辑文件
│ │ ├── myprojectDetail.json # 我的项目详情页面配置文件
│ │ ├── myprojectDetail.wxml # 我的项目详情页面结构文件
│ │ └── myprojectDetail.wxss # 我的项目详情页面样式文件
│ ├── project/ # 项目广场页面
│ │ ├── project.js # 项目广场页面逻辑文件
│ │ ├── project.json # 项目广场页面配置文件
│ │ ├── project.wxml # 项目广场页面结构文件
│ │ └── project.wxss # 项目广场页面样式文件
│ ├── projectDetail/ # 项目详情页面
│ │ ├── projectDetail.js # 项目详情页面逻辑文件
│ │ ├── projectDetail.json # 项目详情页面配置文件
│ │ ├── projectDetail.wxml # 项目详情页面结构文件
│ │ └── projectDetail.wxss # 项目详情页面样式文件
│ ├── receiveapply/ # 接收申请页面
│ │ ├── receiveapply.js # 接收申请页面逻辑文件
│ │ ├── receiveapply.json # 接收申请页面配置文件
│ │ ├── receiveapply.wxml # 接收申请页面结构文件
│ │ └── receiveapply.wxss # 接收申请页面样式文件
│ ├── register/ # 注册页面
│ │ ├── register.js # 注册页面逻辑文件
│ │ ├── register.json # 注册页面配置文件
│ │ ├── register.wxml # 注册页面结构文件
│ │ └── register.wxss # 注册页面样式文件
│ ├── user/ # 个人中心页面
│ │ ├── user.js # 个人中心页面逻辑文件
│ │ ├── user.json # 个人中心页面配置文件
│ │ ├── user.wxml # 个人中心页面结构文件
│ │ └── user.wxss # 个人中心页面样式文件
├── components/ # 自定义组件目录
│ ├── navigation-bar/ # 自定义顶部导航栏组件
│ │ ├── navigation-bar.ts # 自定义顶部导航栏组件逻辑文件
│ │ ├── navigation-bar.json # 自定义顶部导航栏组件配置文件
│ │ ├── navigation-bar.wxml # 自定义顶部导航栏组件结构文件
│ │ └── navigation-bar.wxss # 自定义顶部导航栏组件样式文件
├── utils/ # 工具库文件夹,包含公用的工具函数
│ ├── util.js # 常用工具函数
│ └── request.js # 网络请求封装
└── images/ # 图片资源文件夹
├── friends.png # 通讯录图片
├── index.png # 项目广场图片
├── message.png # 创建项目图片
├── unknown.png # 搜索图片
├── user.png # 个人中心图片
├── 1.png # 申请页面图片
├── 2.png # 创建项目背景图片
├── 3.png # 项目广场背景图片
└── no_message.png # 消息为空显示图片
(2)使用说明
哈喽哈喽!这里是102201241与102201542的小程序“ProjectPartner”!💟
这是我们小程序的二维码:
它是有一定的时效的!所以如果想要预览麻烦您先联系一下102201241小戴或102201542小曾获取一下最新的二维码与添加体验成员,谢谢!!
注册和登录🔒
首先!要进入小程序肯定要先注册与登录啦!我们设计一进入小程序便显示“欢迎来到ProjectPartner”和我们小程序的slogan“在这里找到志同道合的小伙伴吧”,以及一个很好看的图片(来自素材网不侵权!)若用户还未有账号,可以点击注册,首先允许用户微信授权,接着设置账号密码。若用户已有账号,则可直接输入账号、密码进入。为了让测试人员更好地体验我们的小程序,我们已向小程序中添加了部分用户、项目等等。测试人员可以通过以下账号、密码登录进行测试:
账号:123
密码:12345
登录后,我们的小程序设计导航栏有四个界面,分别为“项目广场”、“创建项目”、“通讯录“、”个人中心“。接着我就按照这个顺序分别介绍!
项目广场📦
(1)项目广场中有所有的项目,这些项目均来自于用户创建,按时间顺序呈现,最新的呈现在最上面。在项目广场界面可以浏览到所有的项目,同时可以通过界面上方的搜索框进行搜索,会根据用户的输入与项目的名称、创建者昵称、项目描述相匹配,有匹配的字符串便可直接在下方显示。用户可以通过搜索定位到自己感兴趣的项目或创建者。
(2)点击每一个项目,便可进入项目详情界面。项目详情界面包含项目名称、创建人、点赞数、项目描述、项目成员。如果喜欢这个项目,可以点击”点赞“按钮,点赞数会相应增加,点赞数都是实时更新的。同时如果对这个项目感兴趣,可以点击”申请加入项目“按钮,进入申请界面,填写自己的申请理由和联系方式,提交申请后创建此项目的用户会收到该申请;或点击“进入聊天”按钮,进入通讯录界面,与项目中的成员进行联系。
创建项目🖌️
在创建项目界面中可输入创建者昵称、项目标题、项目描述,其中设计字数限制分别为10、15、200。当达到字数限制时,用户则不可以再继续输入,同时出现告知用户的提示。填写完毕后,点击“发布项目”按钮,即可成功发布。同时跳转到项目广场界面,用户可以在项目广场上看到自己刚刚创建的项目。
通讯录☎️
(1)通讯录界面显示“新朋友”、“我的好友”、“其他用户”三栏。“新朋友”处会显示收到的好友申请,若暂无好友申请会有相关提示,若有则会显示申请用户的头像、昵称,以及“同意”的按钮。点击同意按钮后,该用户则会出现在“我的好友”处。“其他用户”处显示小程序上的所有用户,用户也可以通过搜索框对感兴趣的用户进行搜索,每个用户右侧都会显示“添加好友”按钮,点击即会发送好友申请。
(2)成为好友后,可以点击用户区域,进入聊天。聊天的记录会被实时记录下来。
个人中心👨
(1)个人中心会显示用户的头像、昵称、账号,以及该用户创建的项目。同时有修改头像的按钮,用户可以选择拍照或者选择相册的方式修改头像。还有一个“切换用户”按钮,点击进入登录界面。
(2)点击项目后会进入我的项目详情界面。这个界面会显示项目名称、项目描述、点赞数、项目成员信息。同时有一个收到的申请按钮。用户点击此按钮可以进入收到的申请界面,点击各个申请可以进入申请详情界面,可以看到申请人昵称、申请理由以及申请人的联系方式,同时有一个“同意”按钮,点击同意即会将该申请人加入为此项目成员中。
PS:若仍有任何不懂的地方或觉得奇怪的地方欢迎联系102201241小戴🐇、102201542小曾🐎
感谢观看💗
👋四、单元测试
1.选用工具:
目前有多种多种测试工具和框架来确保代码质量和功能的正确性。而我们微信小程序测试选用的是最普遍也是耐用的Jest工具。
Jest 是一个流行的 JavaScript 测试框架,支持单元测试和集成测试。它具有简单的语法和强大的功能,适用于测试小程序的逻辑层,同时还能验证 API 调用、数据库交互、不同模块的配合等。
2.学习过程:
我们先是浏览博客作业中发的邹欣老师的有关“单元测试和回归测试”的文章内容了解单元测试的基本知识。在选择Jest作为工具以后,我们通过浏览网上一些已经有的Jest进行单元测试的教程进行初步学习,有些基础的启蒙视频对我们的帮助还是非常大的,如B站上的这个视频:
在实际编程中,可以借助AI生成一个大致的测试模板,然后自己根据需求改写,我们在微信小程序的开发过程中会用到wx的各种API来存储数据或者获取数据到我们的云开发的后端数据库,所以我们的测试文件采用的是在一开始时就直接模拟wx的对象对数据库的调用。
3.简易教程:
我们首先得确保自己的电脑安装了Node.js,然后在Node js命令控制台打开我们的项目文件,初始化一下我们的package.json文件:
使用npm安装Jest,具体指令如下:
npm install --save-dev jest
安装好以后,在项目的根目录下创建jest.config.js文件,文件里的内容可以像我们一样定义:
然后就可以在开始编写我们的测试代码啦,你需要创建一个测试文件,例如 example.test.js。在该文件中,你可以编写自己的测试。
最后,在控制台运行npx jest example.test.js就可以查看测试结果了:
呈现结果大致是这样的:
成功通过,nice!
4.部分单元测试代码展示:
以下是跳转功能单元测试代码,实现的测试是在点击项目后是否会跳转到项目详情界面并显示,界面之间的跳转属于是最基础也是最重要的功能,对其测试十分必要!!
测试用到的大部分是基础的模拟函数。
// project.test.ts
const myapp = {
globalData: {}
};
// 模拟 wx 对象
global.wx = {
navigateTo: jest.fn(), // 只模拟 navigateTo 方法
};
// 模拟 getApp 函数
global.getApp = () => myapp;
// 模拟 Page 函数
function Page(obj) {
Object.assign(this, obj);
}
// 添加 goToDetail 方法
Page.prototype.goToDetail = function (event) {
const { title, description, account_id } = event.currentTarget.dataset;
wx.navigateTo({
url: /pages/projectDetail/projectDetail?title=${title}&description=${description}&account_id=${account_id}
});
};
// 测试用例
describe('Project Page Functionality', () => {
let pageInstance;
beforeEach(() => {
jest.clearAllMocks(); // 清除之前的模拟
pageInstance = new Page({}); // 创建 Page 实例
});
it('should navigate to project detail page with correct parameters', () => {
const event = {
currentTarget: {
dataset: {
title: '项目1',
description: '描述1',
account_id: '001'
}
}
};
pageInstance.goToDetail(event); // 调用 goToDetail 方法
// 断言
expect(wx.navigateTo).toHaveBeenCalledWith({
url: '/pages/projectDetail/projectDetail?title=项目1&description=描述1&account_id=001'
});
});
});
以下是注册功能单元测试代码:
// register.test.js
const app = {
globalData: {
userInfo: null // 初始化 userInfo
}
};
// 模拟 wx 对象
global.wx = {
cloud: {
database: jest.fn(() => ({
collection: jest.fn(() => ({
where: jest.fn(() => ({
get: jest.fn((callback) => {
// 默认模拟返回值为空
callback.success({ data: [] }); // 初始为空,模拟无重复账户
}),
})),
add: jest.fn((data) => ({
success: jest.fn(() => {
// 模拟添加成功
return { _id: 'user123' };
}),
})),
})),
})),
switchTab: jest.fn(),
showToast: jest.fn(),
getUserProfile: jest.fn((options) => {
// 模拟用户授权
options.success({ userInfo: { avatarUrl: 'test_url', nickName: 'test_user' } });
}),
},
};
// 模拟 getApp 函数
global.getApp = () => app;
// 模拟 Page 函数
function Page(obj) {
// 将 obj 绑定到 this
Object.assign(this, obj);
this.data = { account_id: '', ps1: '', ps2: '' }; // 初始化 data
}
// 在构造函数中绑定原型方法
Page.prototype.getUserProfile = function () {
var that = this;
wx.getUserProfile({
desc: '展示信息',
success: (res) => {
console.log(res);
that.setData({
userInfo: res.userInfo
});
wx.showToast({
title: '已授权',
duration: 500
});
// 加入全局变量
app.globalData.userInfo = res.userInfo;
console.log(app.globalData.userInfo);
}
});
};
Page.prototype.setData = function (data) {
Object.assign(this.data, data);
};
Page.prototype.registerCheck = function () {
if (this.data.ps1 !== this.data.ps2) {
wx.showToast({
icon: 'error',
title: '密码不相同',
});
return false;
} else if (this.data.ps1.length > 10) {
wx.showToast({
icon: 'error',
title: '密码过长',
});
return false;
} else if (this.data.account_id.length > 10) {
wx.showToast({
icon: 'error',
title: '昵称过长',
});
return false;
}
return true;
};
Page.prototype.register = function () {
var that = this;
if (!this.registerCheck()) return;
wx.cloud.database().collection('chat_user').where({
account_id: that.data.account_id
}).get({
success(res) {
console.log(res);
// 去除重复用户名
if (res.data.length > 0) {
wx.showToast({
icon: 'error',
title: '昵称重复',
});
return;
} else {
wx.cloud.database().collection('chat_user').add({
data: {
avatarUrl: that.data.userInfo.avatarUrl,
nickName: that.data.userInfo.nickName,
account_id: that.data.account_id,
password: that.data.ps2,
friends: [],
new_friends: []
},
success(res) {
console.log(res);
// 将用户名和密码保存到全局变量 app.globalData中
app.globalData.userInfo.account_id = that.data.account_id;
app.globalData.userInfo.password = that.data.ps2; // 修改为 ps2
wx.switchTab({
url: '/pages/message/message',
});
}
});
}
}
});
};
// 测试用例
describe('Register Functionality', () => {
beforeEach(() => {
jest.clearAllMocks(); // 清除之前的模拟
});
it('should authorize user profile and set userInfo', () => {
// 创建 Page 实例
const pageInstance = new Page({});
pageInstance.getUserProfile();
// 断言
expect(pageInstance.data.userInfo).toEqual({ avatarUrl: 'test_url', nickName: 'test_user' });
expect(app.globalData.userInfo).toEqual({ avatarUrl: 'test_url', nickName: 'test_user' });
expect(wx.showToast).toHaveBeenCalledWith({ title: '已授权', duration: 500 });
});
it('should register successfully with valid input', () => {
// 创建 Page 实例并设置用户信息
const pageInstance = new Page({});
pageInstance.setData({
account_id: 'test_user',
ps1: '123456',
ps2: '123456',
userInfo: { avatarUrl: 'test_url', nickName: 'test_user' }
});
// 调用注册函数
pageInstance.register();
// 断言
expect(wx.showToast).not.toHaveBeenCalledWith({ icon: 'error', title: '昵称重复' });
expect(wx.switchTab).toHaveBeenCalledWith({ url: '/pages/message/message' });
expect(app.globalData.userInfo).toEqual({
avatarUrl: 'test_url',
nickName: 'test_user',
account_id: 'test_user',
password: '123456'
});
});
it('should show error toast for password mismatch', () => {
// 创建 Page 实例并设置用户信息
const pageInstance = new Page({});
pageInstance.setData({
account_id: 'test_user',
ps1: '123456',
ps2: '654321', // 密码不匹配
});
// 调用注册函数
pageInstance.register();
// 断言
expect(wx.showToast).toHaveBeenCalledWith({ icon: 'error', title: '密码不相同' });
});
it('should show error toast for long password', () => {
// 创建 Page 实例并设置用户信息
const pageInstance = new Page({});
pageInstance.setData({
account_id: 'test_user',
ps1: '12345678901', // 密码过长
ps2: '12345678901',
});
// 调用注册函数
pageInstance.register();
// 断言
expect(wx.showToast).toHaveBeenCalledWith({ icon: 'error', title: '密码过长' });
});
it('should show error toast for long account_id', () => {
// 创建 Page 实例并设置用户信息
const pageInstance = new Page({});
pageInstance.setData({
account_id: 'test_user_test', // 昵称过长
ps1: '123456',
ps2: '123456',
});
// 调用注册函数
pageInstance.register();
// 断言
expect(wx.showToast).toHaveBeenCalledWith({ icon: 'error', title: '昵称过长' });
});
});
在这里面我们用的了很多测试功能,如:
(1)测试在调用 getUserProfile 方法后,用户信息(如头像和昵称)被正确设置到页面实例和全局数据中。
(2)测试用户在输入有效且唯一的账号信息时,能够成功注册。
(3)确保系统在注册时检查到重复的账号 ID,并给出相应的错误提示。
该单元测试使用的函数大部分为模拟函数,以及清楚状态并重置模拟函数beforeEach()。
5.构造数据主要思路:
我们的测试数据,主要是围绕一些违规情况进行构建的,比如说构建一些超过字数限制的字符,重复的id,还有空字符。老师和同学们会用各种数据为难我们,但我们小组也是测试了很多种情况来应对老师和同学们的测试,主打一个道高一尺,魔高一丈。
🤔五、总结与评价
Github代码签入记录:
遇到的困难及解决方法:
代码方面的问题:在尝试自己学习+写代码的过程中难免需要不断地debug和优化,这次作业中我花了非常大量的时间在这上面所以深有体会。学到了各种调试的方法,不断地进行调试,解决问题。
结对方面的问题:我觉得我性子是属于比较急的那种,所以在和队友沟通的时候有时候队友会有点get不到我的意思,我就会有点红温,然后就开始不断的揽活。通过这次作业,也在与队友沟通方面学到了很多!
评价队友:
上面有提到我是性子比较急的那种,所以就很需要一个和我互补的队友,而我队友恰恰好就是那种不慌不乱、情绪稳定的人。虽然有时候我比较急,但他都会比较冷静的处理,这是值得我学习的地方。但是我觉得队友还缺少很多硬实力,在前端和后端方面的能力都不是很强,我觉得他可以再多多学习一下。