Skip to content

Commit ae6fa67

Browse files
committed
refactor: Add and remove tests (#2748)
1 parent c611499 commit ae6fa67

File tree

2 files changed

+67
-41
lines changed

2 files changed

+67
-41
lines changed

docs/dev-notes/2025-10-25/refactor-getMergedTasksMap/lesson.md

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,50 @@ const pairs = {
135135
| テストデータ集約 | 保守性向上 |
136136
| beforeEach で初期化 | テスト間の独立性確保 |
137137

138+
### 6. **コード レビュー フィードバック対応**
139+
140+
#### 指摘事項
141+
142+
| 項目 | 内容 | 対応 |
143+
| ------------------ | ------------------------------------ | ------------------ |
144+
| 弱い Assertion | `toContain()` では不正確 | `toBe()` に変更 |
145+
| 冗長テスト | O(n²) の全ペア比較テストは不要 | O(n) Set検証に統一 |
146+
| beforeEach削減 | イミュータブルデータの不要なコピー | 削除 |
147+
| 特殊文字カバレッジ | **デリミタ文字(コロン)が未テスト** | 3ケース追加 |
148+
149+
#### 学んだことと根拠
150+
151+
- **アサーション強度**: `toContain()` は部分一致で誤検知の可能性 → `toBe()` で完全一致を保証
152+
- **テスト効率**: 冗長な検証は実装と同じ複雑さ → 代表的パターンだけ実装
153+
- **パーサビリティ脆弱性**: デリミタ文字(`:`)が ID に含まれると `split(':')` で分割失敗 → **デリミタ自体のテストケースが必須**
154+
155+
#### 対応結果
156+
157+
- ✅ Assertion を 4 個改善(`toContain()``toBe()` 統一)
158+
- ✅ 冗長テスト 1 個削除(O(n²) → O(n))
159+
- ✅ コロン文字テスト 3 個追加(`contestId` のみ、`taskId` のみ、両方)
160+
-**テスト総数: 26 → 29 個**(全成功 ✅)
161+
162+
#### 重要な発見
163+
164+
**コロン文字は関数内で保存されるが、デリミタと同じため使用時に注意が必要**
165+
166+
```typescript
167+
// 実装例:コロンを含む ID
168+
const key = createContestTaskPairKey('abc:123', 'task_a');
169+
// 結果: "abc:123:task_a" (コロン3個)
170+
171+
// ⚠️ split(':') での分割に注意
172+
const [contestId, ...taskIdParts] = key.split(':');
173+
// contestId = 'abc', taskIdParts = ['123', 'task_a'] ❌ 失敗!
174+
```
175+
176+
**教訓**: デリミタ文字も含めてテストし、実装側で検証ルールを明確にすべき。
177+
138178
## テスト統計
139179

140-
- **総テスト数**: 27 個(全成功 ✅)
141-
- **パラメタライズテスト**: 2 グループ(合計 8 ケース)
180+
- **総テスト数**: 29 個(全成功 ✅)
181+
- **パラメタライズテスト**: 2 グループ(合計 11 ケース)
142182
- **ヘルパー関数**: 5 個
143183
- **テストデータセット**: 3 グループ(normal, edge, anomaly)
184+
- **特殊文字カバレッジ**: パイプ 4 ケース + コロン 3 ケース + その他 8 ケース

src/test/lib/utils/contest_task_pair.test.ts

Lines changed: 24 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, test, expect, beforeEach } from 'vitest';
1+
import { describe, test, expect } from 'vitest';
22

33
import { createContestTaskPairKey } from '$lib/utils/contest_task_pair';
44

@@ -9,7 +9,7 @@ describe('createContestTaskPairKey', () => {
99
taskId: string;
1010
}
1111

12-
type TestPairs = TestPair[];
12+
type TestPairs = readonly TestPair[];
1313

1414
// Helper to create test pairs
1515
const pairs = {
@@ -44,23 +44,16 @@ describe('createContestTaskPairKey', () => {
4444
{ contestId: 'abc|123', taskId: 'abc|123_a' },
4545
{ contestId: 'abc||123', taskId: 'task||id' },
4646
{ contestId: 'abc.*+?^${}()', taskId: 'task[a-z]' },
47+
{ contestId: 'abc:123', taskId: 'task_a' },
48+
{ contestId: 'abc123', taskId: 'task:a' },
49+
{ contestId: 'abc:123', taskId: 'task:a' },
4750
{ contestId: 'abc日本語123', taskId: 'task日本語a' },
4851
{ contestId: 'abc😀', taskId: 'task😀' },
4952
{ contestId: 'abc\n123', taskId: 'task\na' },
5053
{ contestId: 'abc\t123', taskId: 'task\ta' },
5154
] as const,
5255
};
5356

54-
let testNormalPairs: TestPairs;
55-
let testEdgePairs: TestPairs;
56-
let testAnomalyPairs: TestPairs;
57-
58-
beforeEach(() => {
59-
testNormalPairs = [...pairs.normal];
60-
testEdgePairs = [...pairs.edge];
61-
testAnomalyPairs = [...pairs.anomaly];
62-
});
63-
6457
// Helper functions:
6558
// To generate expected key
6659
const getExpectedKey = (contestId: string, taskId: string) => `${contestId}:${taskId}`;
@@ -98,35 +91,35 @@ describe('createContestTaskPairKey', () => {
9891

9992
describe('Base cases', () => {
10093
test('expects to create a key with valid contest_id and task_id', () => {
101-
const pair = testNormalPairs[0];
94+
const pair = pairs.normal[0];
10295
const key = createTestKey(pair);
10396

10497
expect(key).toBe(getExpectedKey(pair.contestId, pair.taskId));
10598
expect(typeof key).toBe('string');
10699
});
107100

108101
test('expects to create different keys for different contest_ids', () => {
109-
const pair = testNormalPairs[0];
102+
const pair = pairs.normal[0];
110103
const modifiedPair = { ...pair, contestId: 'abc124' };
111104

112105
expectKeysDifferent(pair, modifiedPair);
113106
});
114107

115108
test('expects to create different keys for different task_ids', () => {
116-
const pair = testNormalPairs[0];
109+
const pair = pairs.normal[0];
117110
const modifiedPair = { ...pair, taskId: 'abc100_b' };
118111

119112
expectKeysDifferent(pair, modifiedPair);
120113
});
121114

122115
test('expects to create consistent keys for the same inputs', () => {
123-
const pair = testNormalPairs[0];
116+
const pair = pairs.normal[0];
124117

125118
expectKeysToBeConsistent(pair);
126119
});
127120

128121
test('expects to work with various contest types', () => {
129-
testMultiplePairs(testNormalPairs, ({ contestId, taskId }) => {
122+
testMultiplePairs(pairs.normal, ({ contestId, taskId }) => {
130123
const pair = { contestId, taskId };
131124
const key = createTestKey(pair);
132125

@@ -142,10 +135,10 @@ describe('createContestTaskPairKey', () => {
142135
});
143136

144137
test('expects to work with numeric task identifiers', () => {
145-
const pair = testNormalPairs[5]; // typical90_a
138+
const pair = pairs.normal[5]; // typical90_a
146139
const key = createTestKey(pair);
147140

148-
expect(key).toContain(pair.taskId);
141+
expect(key).toBe(getExpectedKey(pair.contestId, pair.taskId));
149142
});
150143

151144
test('expects to work with long contest and task IDs', () => {
@@ -162,7 +155,7 @@ describe('createContestTaskPairKey', () => {
162155
describe('Edge cases', () => {
163156
test('expects all edge cases to format correctly', () => {
164157
// Filter out empty string cases as they should throw
165-
const validEdgePairs = testEdgePairs.filter(
158+
const validEdgePairs = pairs.edge.filter(
166159
(pair) => pair.contestId.trim() !== '' && pair.taskId.trim() !== '',
167160
);
168161

@@ -187,14 +180,14 @@ describe('createContestTaskPairKey', () => {
187180
);
188181

189182
test('expects to preserve order of contest_id and task_id', () => {
190-
const pair1 = testNormalPairs[0];
183+
const pair1 = pairs.normal[0];
191184
const pair2 = { contestId: pair1.taskId, taskId: pair1.contestId };
192185

193186
expectKeysDifferent(pair1, pair2);
194187
});
195188

196189
test('expects to include the colon separator', () => {
197-
const pair = testNormalPairs[0];
190+
const pair = pairs.normal[0];
198191
const key = createTestKey(pair);
199192

200193
expect(key).toContain(':');
@@ -205,7 +198,7 @@ describe('createContestTaskPairKey', () => {
205198
describe('Anomaly cases', () => {
206199
test('expects anomaly cases with special characters to format correctly', () => {
207200
// Filter out pipe character cases for basic testing
208-
const specialCharCases = testAnomalyPairs.slice(4);
201+
const specialCharCases = pairs.anomaly.slice(4);
209202

210203
testMultiplePairs(specialCharCases, ({ contestId, taskId }) => {
211204
const pair = { contestId, taskId };
@@ -224,7 +217,10 @@ describe('createContestTaskPairKey', () => {
224217
'abc||123:task||id',
225218
'multiple consecutive pipes',
226219
],
227-
])('expects pipe characters to be preserved (%s)', (pair, expected) => {
220+
[{ contestId: 'abc:123', taskId: 'task_a' }, 'abc:123:task_a', 'colon in contest_id'],
221+
[{ contestId: 'abc123', taskId: 'task:a' }, 'abc123:task:a', 'colon in task_id'],
222+
[{ contestId: 'abc:123', taskId: 'task:a' }, 'abc:123:task:a', 'colons in both IDs'],
223+
])('expects special characters to be preserved (%s)', (pair, expected) => {
228224
const key = createTestKey(pair);
229225
expect(key).toBe(expected);
230226
});
@@ -246,7 +242,7 @@ describe('createContestTaskPairKey', () => {
246242

247243
describe('Key validation', () => {
248244
test('expects key to be parseable back into components', () => {
249-
testMultiplePairs(testNormalPairs, ({ contestId, taskId }) => {
245+
testMultiplePairs(pairs.normal, ({ contestId, taskId }) => {
250246
const pair = { contestId, taskId };
251247
const key = createTestKey(pair);
252248
const [parsedContestId, parsedTaskId] = key.split(':');
@@ -257,7 +253,7 @@ describe('createContestTaskPairKey', () => {
257253
});
258254

259255
test('expects key with colon separator to be splittable into two parts', () => {
260-
const pair = testNormalPairs[0];
256+
const pair = pairs.normal[0];
261257
const key = createTestKey(pair);
262258
const parts = key.split(':');
263259

@@ -267,29 +263,18 @@ describe('createContestTaskPairKey', () => {
267263
});
268264

269265
test('expects all keys to follow the same format pattern', () => {
270-
const keys = testNormalPairs.map((pair) => createTestKey(pair));
266+
const keys = pairs.normal.map((pair) => createTestKey(pair));
271267

272268
keys.forEach((key) => {
273269
expect(key).toMatch(/^.+:.+$/);
274270
});
275271
});
276272

277273
test('expects multiple keys to be unique', () => {
278-
const keys = testNormalPairs.map((pair) => createTestKey(pair));
274+
const keys = pairs.normal.map((pair) => createTestKey(pair));
279275
const uniqueKeys = new Set(keys);
280276

281277
expect(uniqueKeys.size).toBe(keys.length);
282278
});
283-
284-
test('expects keys from different pairs to be different', () => {
285-
const selectedPairs = testNormalPairs.slice(0, 3);
286-
const keys = selectedPairs.map((pair) => createTestKey(pair));
287-
288-
for (let i = 0; i < keys.length; i++) {
289-
for (let j = i + 1; j < keys.length; j++) {
290-
expect(keys[i]).not.toBe(keys[j]);
291-
}
292-
}
293-
});
294279
});
295280
});

0 commit comments

Comments
 (0)