本文通过一个真实的"三个会议交叉重叠"场景,完整展示测试驱动开发(TDD)的"红-绿-重构"循环。你将从零开始,亲眼见证测试如何驱动代码设计、暴露边缘情况、最终产出健壮的实现。
第一步:需求分析
业务场景
会议室预约系统允许用户将多个会议分配到同一间会议室。当会议时间发生重叠时,系统需要自动检测冲突并标红。本次要实现的策略是冲突均标红:只要两个会议的时间有重叠,两者都标记为冲突。
聚焦一个场景:三个会议交叉重叠
假设有三个会议,时间如下:
| 会议 | 开始时间 | 结束时间 |
|---|---|---|
| Meeting 1 | 08:00 | 09:00 |
| Meeting 2 | 08:30 | 09:30 |
| Meeting 3 | 09:00 | 10:00 |
时间关系:Meeting 1 与 Meeting 2 重叠,Meeting 2 与 Meeting 3 重叠,但 Meeting 1 和 Meeting 3 不重叠------它们首尾相接(09:00 = 09:00)。
用时间轴线表示如下:
markdown
Meeting 1 ████████░░░░░░░░░░░░
Meeting 2 ░░░░████████░░░░░░░░
Meeting 3 ░░░░░░░░░███████████
08:00 09:00 10:00
测试用例
对应该场景,我们列出了 7 个测试用例,覆盖三种操作类型:
操作类型一:添加会议
| 用例名称 | 操作 | 预期结果 |
|---|---|---|
| 1A 2A 3A => 1x 2x 3x | 三个会议依次分配到 A | 全部标红 |
操作类型二:移出会议
| 用例名称 | 操作 | 预期结果 |
|---|---|---|
| -> 1 null => 2x 3x | 移出 1 | 2、3 冲突,1 正常 |
| -> 2 null => | 移出 2 | 全部正常 |
| -> 3 null => 1x 2x | 移出 3 | 1、2 冲突,3 正常 |
操作类型三:移动到其他会议室
| 用例名称 | 操作 | 预期结果 |
|---|---|---|
| -> 1 B => 2x 3x | 1 移到 B | 2、3 冲突,1 正常 |
| -> 2 B => | 2 移到 B | 全部正常 |
| -> 3 B => 1x 2x | 3 移到 B | 1、2 冲突,3 正常 |
关键洞察: "移出 2"这个用例------当"桥梁"会议被移除后,1 和 3 不重叠,冲突全部消失。这是整个场景中最关键的设计洞察。
第二步:搭建测试环境
首先安装 Vitest:
bash
pnpm add -D vitest
在 package.json 中添加测试脚本:
json
{
"scripts": {
"test": "vitest",
}
}
第三步:红------编写第一个失败的测试
TDD 的第一步是:先写一个会失败的测试,明确描述你期望的最小行为。
我们在 test/three-cross.spec.js 中开始:
javascript
import { describe, it, expect } from 'vitest'
import { handleRoomChange } from '../index'
describe('三个会议交叉重叠 - 冲突均标红', () => {
it('无会议时,添加第一个会议应该无冲突', () => {
const meeting1 = {
id: 1, name: 'Meeting 1',
start: '2021-01-01 08:00:00', end: '2021-01-01 09:00:00',
date: '2021-01-01', isConflict: false, roomId: null
}
meeting1.roomId = 'A'
handleRoomChange(meeting1)
expect(meeting1.isConflict).toBe(false)
})
})
运行测试:
bash
pnpm test
输出:
bash
FAIL test/three-cross.spec.js
TypeError: handleRoomChange is not a function
测试失败,符合预期------因为 handleRoomChange 还不存在。这正是"红"阶段:我们需要创建这个模块。
第四步:绿------编写最小化代码通过测试
创建一个最简的方法,让它刚好通过当前测试:
javascript
// index.js
handleRoomChange(meeting: Meeting): void {
meeting.isConflict = false
}
这个实现很简单------它无条件地把 isConflict 设为 false。最小化代码意味着用最少的工作让当前测试变绿。
运行测试:
✓ 三个会议交叉重叠 - 冲突均标红
✓ 无会议时,添加第一个会议应该无冲突
绿了。
第五步:红------添加第二个测试,暴露新行为
现在写第二个测试------添加两个时间重叠的会议,两者都应被标记为冲突:
javascript
// 在同一个 describe 块中添加
it('两个重叠的会议应都被标记为冲突', () => {
const meeting1 = {
id: 1, name: 'Meeting 1',
start: '2021-01-01 08:00:00', end: '2021-01-01 09:00:00',
date: '2021-01-01', isConflict: false, roomId: null
}
const meeting2 = {
id: 2, name: 'Meeting 2',
start: '2021-01-01 08:30:00', end: '2021-01-01 09:30:00',
date: '2021-01-01', isConflict: false, roomId: null
}
meeting1.roomId = 'A'
handleRoomChange(meeting1)
meeting2.roomId = 'A'
handleRoomChange(meeting2)
expect(meeting1.isConflict).toBe(true)
expect(meeting2.isConflict).toBe(true)
})
运行:
bash
FAIL test/three-cross.spec.ts
✓ 无会议时,添加第一个会议应该无冲突
✗ 两个重叠的会议应都被标记为冲突
AssertionError: expected false to be true
红。 现在的代码把所有会议都设为 false,显然不对。
第六步:绿------让第二个测试通过
现在需要真正实现冲突检测逻辑了。但我们仍然只做刚好够用的代码:
javascript
import dayjs from 'dayjs'
let meetingsMap = {}
function hasConflict(m1, m2) {
return dayjs(m1.end).isAfter(dayjs(m2.start))
&& dayjs(m2.end).isAfter(dayjs(m1.start))
}
export function handleRoomChange(meeting) {
const { date, roomId } = meeting;
if (!meetingsMap[date]) meetingsMap[date] = {}
if (!meetingsMap[date][roomId]) meetingsMap[date][roomId] = []
const roomMeetings = meetingsMap[date][roomId]
roomMeetings.push(meeting)
// 暴力检测所有会议两两之间的冲突
for (let i = 0; i < roomMeetings.length; i++) {
for (let j = i + 1; j < roomMeetings.length; j++) {
if (hasConflict(roomMeetings[i], roomMeetings[j])) {
roomMeetings[i].isConflict = true
roomMeetings[j].isConflict = true
}
}
}
}
关键设计决策分析:
- 为什么用
meetingsMap? 因为需要按日期和会议室维度持久化会议列表,才能做两两比较。 - 为什么用
dayjs? 时间比较需要精确到秒,用字符串比较不可靠,dayjs 是项目已有的日期库。 - 冲突条件 :
m1 未结束 && m2 已开始------经典的区间重叠判断。 - 嵌套循环:最简单直观的两两比较方式,O(n²) 复杂度,但对于一个会议室通常只有几个会议的场景完全够用。
运行测试:
✓ 无会议时,添加第一个会议应该无冲突
✓ 两个重叠的会议应都被标记为冲突
两个测试都通过了。
第七步:红------用核心场景驱动设计
现在到了我们真正关心的场景:三个会议交叉重叠。先标记所有会议:
javascript
it('1 A 2 A 3 A => 1 x 2 x 3 x', () => {
const meetings = [
{ id: 1, name: 'Meeting 1', start: '2021-01-01 08:00', end: '2021-01-01 09:00',
date: '2021-01-01', isConflict: false, roomId: null },
{ id: 2, name: 'Meeting 2', start: '2021-01-01 08:30', end: '2021-01-01 09:30',
date: '2021-01-01', isConflict: false, roomId: null },
{ id: 3, name: 'Meeting 3', start: '2021-01-01 09:00', end: '2021-01-01 10:00',
date: '2021-01-01', isConflict: false, roomId: null },
]
meetings.forEach(m => { m.roomId = 'A';
handleRoomChange(m)
})
expect(meetings[0].isConflict).toBe(true) // 1-2 重叠
expect(meetings[1].isConflict).toBe(true) // 2-3 重叠
expect(meetings[2].isConflict).toBe(true) // 2-3 重叠
})
运行测试,通过了!因为嵌套循环已经处理了所有两两关系。
现在加上关键用例------移出会议:
javascript
it('1 A 2 A 3 A -> 2 null =>', () => {
const meetings = [
{
id: 1, name: 'Meeting 1', start: '2021-01-01 08:00', end: '2021-01-01 09:00',
date: '2021-01-01', isConflict: false, roomId: null
},
{
id: 2, name: 'Meeting 2', start: '2021-01-01 08:30', end: '2021-01-01 09:30',
date: '2021-01-01', isConflict: false, roomId: null
},
{
id: 3, name: 'Meeting 3', start: '2021-01-01 09:00', end: '2021-01-01 10:00',
date: '2021-01-01', isConflict: false, roomId: null
},
]
meetings.forEach(m => { m.roomId = 'A'; handleRoomChange(m) })
// 移出 Meeting 2
meetings[1].prevRoomId = meetings[1].roomId
meetings[1].roomId = null
handleRoomChange(meetings[1])
expect(meetings[0].isConflict).toBe(false) // 1 和 3 不重叠
expect(meetings[1].isConflict).toBe(false) // 已移出
expect(meetings[2].isConflict).toBe(false) // 3 和 1 不重叠
})
运行:
php
FAIL ✗ 1 A 2 A 3 A -> 2 null =>
AssertionError: expected true to be false
红! 问题在于:当我们移出 Meeting 2 时,之前的 handleRoomChange 只会往列表里加会议,从没考虑过移除。Meeting 1 的 isConflict 仍然停留在 true,没有被重新计算。
现在,移出需求驱动我们设计一个新的能力。
第八步:绿------实现移除和重算
我们需要区分"添加"和"移除"两种情况。设计 handleRoomChange 的分支逻辑:
javascript
export function clearMap() {
meetingsMap = {}
}
export function handleRoomChange(meeting) {
const { prevRoomId, roomId, date } = meeting
// 先从旧会议室移除
if (prevRoomId) {
if (!meetingsMap[date]) meetingsMap[date] = {}
if (!meetingsMap[date][prevRoomId]) meetingsMap[date][prevRoomId] = []
const roomMeetings = meetingsMap[date][prevRoomId]
const index = roomMeetings.findIndex(m => m.id === meeting.id)
if (index !== -1) roomMeetings.splice(index, 1)
const conflictIds = new Set()
for (let i = 0; i < roomMeetings.length; i++) {
for (let j = i + 1; j < roomMeetings.length; j++) {
if (hasConflict(roomMeetings[i], roomMeetings[j])) {
conflictIds.add(roomMeetings[i].id)
conflictIds.add(roomMeetings[j].id)
}
}
}
roomMeetings.forEach(m => {
m.isConflict = conflictIds.has(m.id)
})
}
// 再添加到新会议室
if (roomId) {
if (!meetingsMap[date]) meetingsMap[date] = {}
if (!meetingsMap[date][roomId]) meetingsMap[date][roomId] = []
const roomMeetings = meetingsMap[date][roomId]
roomMeetings.push(meeting)
const conflictIds = new Set()
for (let i = 0; i < roomMeetings.length; i++) {
for (let j = i + 1; j < roomMeetings.length; j++) {
if (hasConflict(roomMeetings[i], roomMeetings[j])) {
conflictIds.add(roomMeetings[i].id)
conflictIds.add(roomMeetings[j].id)
}
}
}
roomMeetings.forEach(m => {
m.isConflict = conflictIds.has(m.id)
})
} else {
meeting.isConflict = false
}
}
这个步骤:
- 每个测试用例都需要将
meetingsMap重置。 - 每次添加或移除会议后,都重新计算整个会议室的状态。虽然看起来做了重复计算,但胜在简单且正确------会议室里的会议数量极少,性能不是问题,正确性才是。
运行测试:
css
✓ 两个重叠的会议应都被标记为冲突
✓ 1 A 2 A 3 A => 1 x 2 x 3 x
✓ 1 A 2 A 3 A -> 2 null =>
所有测试通过。
第九步:红------继续添加剩余场景
测完核心逻辑,快速补全剩余用例:
typescript
it('1 A 2 A 3 A -> 1 null => 2 x 3 x', () => {
// 移出 1 → 2 和 3 仍然冲突
...
expect(meetings[0].isConflict).toBe(false)
expect(meetings[1].isConflict).toBe(true)
expect(meetings[2].isConflict).toBe(true)
})
it('1 A 2 A 3 A -> 3 null => 1 x 2 x', () => {
// 移出 3 → 1 和 2 仍然冲突
...
expect(meetings[0].isConflict).toBe(true)
expect(meetings[1].isConflict).toBe(true)
expect(meetings[2].isConflict).toBe(false)
})
it('1 A 2 A 3 A -> 1 B => 2 x 3 x', () => {
// 1 移到 B → A 会议室只剩 2 和 3,它们冲突
// 1 在 B 会议室只有自己,无冲突
...
expect(meetings[0].isConflict).toBe(false)
expect(meetings[1].isConflict).toBe(true)
expect(meetings[2].isConflict).toBe(true)
})
由于 handleRoomChange 已经同时处理了移除和添加,这些用例都直接通过:
css
✓ 1 A 2 A 3 A -> 1 null => 2 x 3 x
✓ 1 A 2 A 3 A -> 2 null =>
✓ 1 A 2 A 3 A -> 3 null => 1 x 2 x
✓ 1 A 2 A 3 A -> 1 B => 2 x 3 x
✓ 1 A 2 A 3 A -> 2 B =>
✓ 1 A 2 A 3 A -> 3 B => 1 x 2 x
7 个测试,全部通过。
第十步:重构------在安全网下优化代码
测试全部通过后,我们站在一个安全的位置审视代码。
问题 1:面向过程的代码重构
javascript
import dayjs from 'dayjs'
export default class AllConflictManager {
meetingsMap = {}
clear() {
this.meetingsMap = {}
}
handleRoomChange(meeting) {
const { prevRoomId, roomId } = meeting
if (prevRoomId) {
this.removeMeetingFromRoom(meeting, prevRoomId)
}
if (roomId) {
this.addMeetingToRoom(meeting, roomId)
} else {
meeting.isConflict = false
}
}
addMeetingToRoom(meeting, roomId) {
const { date } = meeting
const roomMeetings = this.getRoomMeetings(date, roomId)
roomMeetings.push(meeting)
const conflictIds = this.findAllConflicts(roomMeetings)
this.updateConflictStatus(roomMeetings, conflictIds)
}
getRoomMeetings(date, roomId) {
if (!this.meetingsMap[date]) this.meetingsMap[date] = {}
if (!this.meetingsMap[date][roomId]) this.meetingsMap[date][roomId] = []
return this.meetingsMap[date][roomId]
}
removeMeetingFromRoom(meeting, roomId) {
const { date } = meeting
const roomMeetings = this.getRoomMeetings(date, roomId)
const index = roomMeetings.findIndex(m => m.id === meeting.id)
if (index !== -1) roomMeetings.splice(index, 1)
const conflictIds = this.findAllConflicts(roomMeetings)
this.updateConflictStatus(roomMeetings, conflictIds)
}
findAllConflicts(roomMeetings) {
const conflictIds = new Set()
for (let i = 0; i < roomMeetings.length; i++) {
for (let j = i + 1; j < roomMeetings.length; j++) {
if (this.hasConflict(roomMeetings[i], roomMeetings[j])) {
conflictIds.add(roomMeetings[i].id)
conflictIds.add(roomMeetings[j].id)
}
}
}
return conflictIds
}
updateConflictStatus(roomMeetings, conflictIds) {
roomMeetings.forEach(m => {
m.isConflict = conflictIds.has(m.id)
})
}
hasConflict(m1, m2) {
return dayjs(m1.end).isAfter(dayjs(m2.start)) && dayjs(m2.end).isAfter(dayjs(m1.start))
}
}
问题 2:测试中重复的测试数据
每个测试用例都重复定义了三个 meeting 对象。提取到公共数据:
json
// data/three-cross.json
[
{ "id": 1, "name": "Meeting 1", "start": "2021-01-01 08:00", "end": "2021-01-01 09:00",
"date": "2021-01-01", "isConflict": false, "roomId": null, "prevRoomId": null },
{ "id": 2, "name": "Meeting 2", "start": "2021-01-01 08:30", "end": "2021-01-01 09:30",
"date": "2021-01-01", "isConflict": false, "roomId": null, "prevRoomId": null },
{ "id": 3, "name": "Meeting 3", "start": "2021-01-01 09:00", "end": "2021-01-01 10:00",
"date": "2021-01-01", "isConflict": false, "roomId": null, "prevRoomId": null }
]
问题 3:Manager 实例创建和重置重复
提取到 test-utils.js:
javascript
import AllConflictManager from '../index'
const manager = new AllConflictManager()
export function resetMeetings(meetings) {
meetings.forEach(m => {
m.isConflict = false
m.prevRoomId = null
m.roomId = null
})
manager.clear()
}
export function assignToRoom(meeting, roomId) {
meeting.roomId = roomId
manager.handleRoomChange(meeting)
}
export function removeMeetingFromRoom(meeting) {
meeting.prevRoomId = meeting.roomId
meeting.roomId = null
manager.handleRoomChange(meeting)
}
export function moveMeeting(meeting, newRoomId) {
meeting.prevRoomId = meeting.roomId
meeting.roomId = newRoomId
manager.handleRoomChange(meeting)
}
问题 4:测试代码精简
重构后的测试文件干净很多:
javascript
import { describe, it, expect, beforeEach } from 'vitest'
import { resetMeetings, assignToRoom, removeMeetingFromRoom, moveMeeting } from './test-utils'
import meetings from '../data/three-cross.json';
describe('三个会议交叉重叠 - 冲突均标红', () => {
beforeEach(() => {
resetMeetings(meetings)
assignToRoom(meetings[0], 'A')
assignToRoom(meetings[1], 'A')
assignToRoom(meetings[2], 'A')
})
it('1 A 2 A 3 A => 1 x 2 x 3 x', () => {
expect(meetings[0].isConflict).toBe(true)
expect(meetings[1].isConflict).toBe(true)
expect(meetings[2].isConflict).toBe(true)
})
// ...其余用例
})
重构后运行测试:
scss
Test Files 1 passed (1)
Tests 7 passed (7)
全部通过。 我们可以在安全网的保护下自信地说:重构没有破坏任何功能。
最终代码全景
源码骨架
scss
AllConflictManager
├── meetingsMap ← 按 date → roomId 的二维存储
├── clear() ← 重置状态
├── handleRoomChange() ← 入口:先移除旧房间,再添加新房间
├── addMeetingToRoom() ← 添加 + 重算冲突
├── removeMeetingFromRoom()← 移除 + 重算冲突
├── findAllConflicts() ← O(n²) 两两比较
├── updateConflictStatus() ← 批量更新标记
└── hasConflict() ← 区间重叠判断
测试覆盖
7 个用例覆盖 3 类操作:
┌─ 添加会议(1 个)
├─ 移出会议(3 个)
└─ 移动会议(3 个)
回顾:TDD 如何驱动了设计
让我们回顾整个过程中,测试是如何驱动设计决策的:
测试驱动了接口设计
第一个测试只验证"添加无冲突会议",导致最初实现只是一个空壳方法。当第二个测试需要"检测重叠"时,才被迫引入 meetingsMap 存储和 hasConflict 方法。
测试驱动了移除逻辑
移出会议的测试迫使我们设计 handleRoomChange 的分支结构(先移除旧房间,再添加新房间)。如果只是按直觉写"添加"逻辑,永远不会考虑到移除后的状态重算。有了移除逻辑后,发现测试用例仍无法通过,meetingsMap 存储需重置。
测试驱动了冲突重算策略
当移出 Meeting 2 后,Meeting 1 的 isConflict 仍然为 true------这个失败迫使我们意识到:每次状态变更后都需要完整重算,而不是增量更新 。recalculateConflicts 方法从这里诞生。
测试驱动了数据抽取
测试代码中的重复数据------7 个用例写了 21 个 meeting 对象------驱动我们将测试数据提到 JSON 文件。这既是重构,也是一种设计信号:测试数据应该与测试逻辑分离。
总结:TDD 的节奏感
回顾完整的红-绿-重构循环:
scss
RED (写测试) → 1. 定义行为期望
2. 确认当前代码做不到
↓
GREEN (写代码) → 3. 用最直接的方式让测试通过
4. 确信结果正确
↓
REFACTOR (优化) → 5. 在测试保护下改进代码质量
6. 测试依然通过
↓
(回到第 1 步,覆盖下一个场景)
这个循环的核心价值在于节奏感:每一步都有明确的目标,每一步的结果都可以立即验证。没有模糊区间,没有"感觉应该没问题"------只有绿色的通过和红色的失败。
当你习惯了这个节奏后,你会发现编码的过程不再是"写完再看",而是一个持续获得正向反馈的过程。每个测试从红变绿的那一刻,都在告诉你:你又推进了一步。
你不需要一次设计出完美的架构。你只需要写一个会失败的测试,然后让它通过。然后重复。最终,好的设计会自己浮现出来。
测试用例
两个重叠的会议
| 会议 | 开始时间 | 结束时间 |
|---|---|---|
| Meeting 1 | 08:00 | 09:00 |
| Meeting 2 | 08:30 | 10:00 |
| 时间关系:Meeting 1 和 Meeting 2 互相重叠 |
| 用例名称 | 操作步骤 | 预期结果 |
|---|---|---|
1 A 2 A => 2 x |
Meeting 1 分配到 A,Meeting 2 分配到 A | Meeting 1 正常,Meeting 2 冲突 |
1 A 2 A -> 1 null => |
上述基础上,Meeting 1 移出 | 两个会议都正常 |
1 A 2 A -> 1 B => |
上述基础上,Meeting 1 移到 B | 两个会议都正常 |
1 A 2 A -> 2 null => |
上述基础上,Meeting 2 移出 | 两个会议都正常 |
1 A 2 A -> 2 B => |
上述基础上,Meeting 2 移到 B | 两个会议都正常 |
三个全重叠的会议
| 会议 | 开始时间 | 结束时间 |
|---|---|---|
| Meeting 1 | 08:00 | 09:30 |
| Meeting 2 | 08:30 | 09:30 |
| Meeting 3 | 09:00 | 10:00 |
| 时间关系:三个会议两两之间都互相重叠 |
| 用例名称 | 操作步骤 | 预期结果 |
|---|---|---|
1 A 2 A 3 A => 1 x 2 x 3 x |
初始状态 | Meeting 1 冲突,Meeting 2 冲突,Meeting 3 冲突 |
1 A 2 A 3 A -> 1 null => 2 x 3 x |
Meeting 1 移出 | Meeting 1 正常,Meeting 2 冲突,Meeting 3 冲突 |
1 A 2 A 3 A -> 2 null => 1x 3 x |
Meeting 2 移出 | Meeting 1 冲突,Meeting 2 正常,Meeting 3 冲突 |
1 A 2 A 3 A -> 3 null => 1 x 2 x |
Meeting 3 移出 | Meeting 1 冲突,Meeting 2 冲突,Meeting 3 正常 |
1 A 2 A 3 A -> 1 B => 2 x 3 x |
Meeting 1 移到 B | Meeting 1 正常,Meeting 2 冲突,Meeting 3 冲突 |
1 A 2 A 3 A -> 2 B => 1 x 3 x |
Meeting 2 移到 B | Meeting 1 冲突,Meeting 2 正常,Meeting 3 冲突 |
1 A 2 A 3 A -> 3 B => 1 x 2 x |
Meeting 3 移到 B | Meeting 1 冲突,Meeting 2 冲突,Meeting 3 正常 |
三个会议 - 交叉重叠
| 会议 | 开始时间 | 结束时间 |
|---|---|---|
| Meeting 1 | 08:00 | 09:00 |
| Meeting 2 | 08:30 | 09:30 |
| Meeting 3 | 09:00 | 10:00 |
时间关系:Meeting 1 与 Meeting 2 重叠,Meeting 2 与 Meeting 3 重叠,Meeting 1 与 Meeting 3 不重叠(首尾相接)
| 用例名称 | 操作步骤 | 预期结果 |
|---|---|---|
1 A 2 A 3 A => 1 x 2 x 3 x |
初始状态 | Meeting 1 冲突,Meeting 2 冲突,Meeting 3 冲突 |
1 A 2 A 3 A -> 1 null => 2 x 3 x |
Meeting 1 移出 | Meeting 1 正常,Meeting 2 冲突,Meeting 3 冲突 |
1 A 2 A 3 A -> 2 null => |
Meeting 2 移出 | 三个会议都正常 |
1 A 2 A 3 A -> 3 null => 1 x 2 x |
Meeting 3 移出 | Meeting 1 冲突,Meeting 2 冲突,Meeting 3 正常 |
1 A 2 A 3 A -> 1 B => 2 x 3 x |
Meeting 1 移到 B | Meeting 1 正常,Meeting 2 冲突,Meeting 3 冲突 |
1 A 2 A 3 A -> 2 B => |
Meeting 2 移到 B | 三个会议都正常 |
1 A 2 A 3 A -> 3 B => 1 x 2 x |
Meeting 3 移到 B | Meeting 1 冲突,Meeting 2 冲突,Meeting 3 正常 |
四个会议 - 链式重叠
| 会议 | 开始时间 | 结束时间 |
|---|---|---|
| Meeting 1 | 08:00 | 09:00 |
| Meeting 2 | 08:30 | 09:30 |
| Meeting 3 | 09:00 | 10:00 |
| Meeting 4 | 09:30 | 10:30 |
时间关系:链式重叠,1-2 重叠,2-3 重叠,3-4 重叠,1-3、1-4、2-4 不重叠
| 用例名称 | 操作步骤 | 预期结果 |
|---|---|---|
1 A 2 A 3 A 4 A => 1 x 2 x 3 x 4 x |
初始状态 | Meeting 1、 2、3、4 冲突 |
1 A 2 A 3 A 4 A -> 1 null => 2 x 3 x 4 x |
Meeting 1 移出 | Meeting 1 正常,Meeting 2、3 冲突,Meeting 4 正常 |
1 A 2 A 3 A 4 A -> 2 null => 3 x 4 x |
Meeting 2 移出 | Meeting 1、2 正常,Meeting 3、4冲突 |
1 A 2 A 3 A 4 A -> 3 null => 1 x 2 x |
Meeting 3 移出 | Meeting 3、4 正常,Meeting 1、2 冲突 |
1 A 2 A 3 A 4 A -> 4 null => 1 x 2 x 3 x |
Meeting 4 移出 | Meeting 4 正常,Meeting 1、2、3 冲突 |
1 A 2 A 3 A 4 A -> 1 B => 2 x 3 x 4 x |
Meeting 1 移到 B | Meeting 1 正常,Meeting 2、3、4 冲突 |
1 A 2 A 3 A 4 A -> 2 B => 3 x 4 x |
Meeting 2 移到 B | Meeting 1、2 正常,Meeting 3、4 冲突 |
1 A 2 A 3 A 4 A -> 3 B => 1 x 2 x |
Meeting 3 移到 B | Meeting 3、4 正常,Meeting 1、2 冲突 |
1 A 2 A 3 A 4 A -> 4 B => 1 x 2 x 3 x |
Meeting 4 移到 B | Meeting4 正常,Meeting 1、2、3 冲突 |