test-writer
JavaScriptのコンセプトドキュメントページに掲載されたコード例に対して、プロジェクトの規約に従いソースの行番号を参照しながら、Vitestの網羅的なテストを自動生成します。
description の原文を見る
Generate comprehensive Vitest tests for code examples in JavaScript concept documentation pages, following project conventions and referencing source lines
SKILL.md 本文
スキル: コンセプトページ用テストライター
このスキルを使用して、コンセプトドキュメントページ内のすべてのコード例に対する包括的なVitestテストを生成します。テストはドキュメント内のコード例が正確で、説明通りに動作することを検証します。
使用する時期
- 新しいコンセプトページを書いた後
- 既存ページに新しいコード例を追加するとき
- 既存のコード例を更新するとき
- 自動化されたテストでドキュメント正確性を検証するとき
- 公開前にすべての例が正しく動作することを確認するとき
テスト作成方法論
コンセプトページの包括的なテストを作成するため、以下の4つのフェーズに従います。
フェーズ1: コード例の抽出
コンセプトページをスキャンしてすべてのコード例を探し、分類します:
| カテゴリ | 特性 | アクション |
|---|---|---|
| テスト可能 | console.logで出力コメント、戻り値がある | テストを作成 |
| DOM固有 | document、window、DOM API、イベントハンドラを使用 | DOMテストを作成(別ファイル) |
| エラー例 | 意図的にエラーをスロー、失敗を実演 | toThrowでテストを作成 |
| 概念的 | ASCIIダイアグラム、疑似コード、不完全なスニペット | スキップ(理由を記録) |
| ブラウザのみ | jsdomで利用できないブラウザAPIを使用 | スキップまたはモック |
フェーズ2: テストファイルの構造を決定
tests/
├── fundamentals/ # コンセプト1-6
├── functions-execution/ # コンセプト7-8
├── web-platform/ # コンセプト9-10
├── object-oriented/ # コンセプト11-15
├── functional-programming/ # コンセプト16-19
├── async-javascript/ # コンセプト20-22
├── advanced-topics/ # コンセプト23-31
└── beyond/ # 拡張コンセプト
└── {subcategory}/
ファイル命名:
- 標準テスト:
{concept-name}.test.js - DOMテスト:
{concept-name}.dom.test.js
フェーズ3: 例をテストに変換
テスト可能な各コード例について:
- 期待される出力を特定します(
console.logコメントまたはドキュメント化された動作から) expectアサーションに変換します- コメント内にソース行参照を追加します
- ドキュメントセクションと一致する
describeブロック内で関連テストをグループ化します
フェーズ4: 特殊なケースへの対応
| ケース | 解決策 |
|---|---|
| ブラウザのみのAPI | jsdom環境を使用またはメモ付きでスキップ |
| タイミング依存コード | vi.useFakeTimers()を使用またはロジックをテスト(タイミングではなく) |
| 副作用 | 出力をキャプチャまたは変異をテスト |
| 意図的なエラー | expect(() => {...}).toThrow()を使用 |
| 非同期コード | 適切なアサーションを使用したasync/await |
プロジェクトテスト規約
インポートパターン
import { describe, it, expect } from 'vitest'
DOMテストまたはモックが必要なテストの場合:
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
DOMテストファイルヘッダー
/**
* @vitest-environment jsdom
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
Describeブロック構成
ドキュメントの構造と一致させます:
describe('Concept Name', () => {
describe('Section from Documentation', () => {
describe('Subsection if needed', () => {
it('should [specific behavior]', () => {
// テスト
})
})
})
})
テスト命名規則
- 「should」で始まります
- 説明的で具体的です
- ドキュメント化された動作と一致します
// 良い例
it('should return "object" for typeof null', () => {})
it('should throw TypeError when accessing property of undefined', () => {})
it('should resolve promises in order they were created', () => {})
// 悪い例
it('test typeof', () => {})
it('works correctly', () => {})
it('null test', () => {})
ソース行参照
常にドキュメントソースを参照します:
// ============================================================
// ドキュメントのセクション名
// From {concept}.mdx lines XX-YY
// ============================================================
describe('Section Name', () => {
// From lines 45-52: 基本的なtypeofの例
it('should return correct type strings', () => {
// テスト
})
})
テストパターンリファレンス
パターン1: 基本的な値アサーション
ドキュメント:
console.log(typeof "hello") // "string"
console.log(typeof 42) // "number"
テスト:
// From lines XX-YY: typeofの例
it('should return correct type for primitives', () => {
expect(typeof "hello").toBe("string")
expect(typeof 42).toBe("number")
})
パターン2: 複数の関連アサーション
ドキュメント:
let a = "hello"
let b = "hello"
console.log(a === b) // true
let obj1 = { x: 1 }
let obj2 = { x: 1 }
console.log(obj1 === obj2) // false
テスト:
// From lines XX-YY: プリミティブ対オブジェクト比較
it('should compare primitives by value', () => {
let a = "hello"
let b = "hello"
expect(a === b).toBe(true)
})
it('should compare objects by reference', () => {
let obj1 = { x: 1 }
let obj2 = { x: 1 }
expect(obj1 === obj2).toBe(false)
})
パターン3: 関数戻り値
ドキュメント:
function greet(name) {
return "Hello, " + name + "!"
}
console.log(greet("Alice")) // "Hello, Alice!"
テスト:
// From lines XX-YY: greet関数の例
it('should return greeting with name', () => {
function greet(name) {
return "Hello, " + name + "!"
}
expect(greet("Alice")).toBe("Hello, Alice!")
})
パターン4: エラーテスト
ドキュメント:
// これはエラーをスローします!
const obj = null
console.log(obj.property) // TypeError: Cannot read property of null
テスト:
// From lines XX-YY: nullのプロパティへのアクセス
it('should throw TypeError when accessing property of null', () => {
const obj = null
expect(() => {
obj.property
}).toThrow(TypeError)
})
パターン5: 特定のエラーメッセージ
ドキュメント:
function divide(a, b) {
if (b === 0) throw new Error("Cannot divide by zero")
return a / b
}
テスト:
// From lines XX-YY: エラー付きdivide関数
it('should throw error when dividing by zero', () => {
function divide(a, b) {
if (b === 0) throw new Error("Cannot divide by zero")
return a / b
}
expect(() => divide(10, 0)).toThrow("Cannot divide by zero")
expect(divide(10, 2)).toBe(5)
})
パターン6: Async/Awaitテスト
ドキュメント:
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`)
return response.json()
}
テスト:
// From lines XX-YY: async fetchUser関数
it('should fetch user data asynchronously', async () => {
// テスト用にfetchをモック
global.fetch = vi.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ id: 1, name: 'Alice' })
})
)
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`)
return response.json()
}
const user = await fetchUser(1)
expect(user).toEqual({ id: 1, name: 'Alice' })
})
パターン7: Promiseテスト
ドキュメント:
const promise = new Promise((resolve) => {
resolve("done")
})
promise.then(result => console.log(result)) // "done"
テスト:
// From lines XX-YY: 基本的なPromise解決
it('should resolve with correct value', async () => {
const promise = new Promise((resolve) => {
resolve("done")
})
await expect(promise).resolves.toBe("done")
})
パターン8: Promise拒否
ドキュメント:
const promise = new Promise((resolve, reject) => {
reject(new Error("Something went wrong"))
})
テスト:
// From lines XX-YY: Promise拒否
it('should reject with error', async () => {
const promise = new Promise((resolve, reject) => {
reject(new Error("Something went wrong"))
})
await expect(promise).rejects.toThrow("Something went wrong")
})
パターン9: 浮動小数点数比較
ドキュメント:
console.log(0.1 + 0.2) // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3) // false
テスト:
// From lines XX-YY: 浮動小数点精度
it('should demonstrate floating point imprecision', () => {
expect(0.1 + 0.2).not.toBe(0.3)
expect(0.1 + 0.2).toBeCloseTo(0.3)
expect(0.1 + 0.2 === 0.3).toBe(false)
})
パターン10: 配列メソッドテスト
ドキュメント:
const numbers = [1, 2, 3, 4, 5]
const doubled = numbers.map(n => n * 2)
console.log(doubled) // [2, 4, 6, 8, 10]
テスト:
// From lines XX-YY: 配列mapの例
it('should double all numbers in array', () => {
const numbers = [1, 2, 3, 4, 5]
const doubled = numbers.map(n => n * 2)
expect(doubled).toEqual([2, 4, 6, 8, 10])
expect(numbers).toEqual([1, 2, 3, 4, 5]) // 元の配列は変わらない
})
パターン11: オブジェクト変異テスト
ドキュメント:
const obj = { a: 1 }
obj.b = 2
console.log(obj) // { a: 1, b: 2 }
テスト:
// From lines XX-YY: オブジェクト変異
it('should allow adding properties to objects', () => {
const obj = { a: 1 }
obj.b = 2
expect(obj).toEqual({ a: 1, b: 2 })
})
パターン12: クロージャテスト
ドキュメント:
function counter() {
let count = 0
return function() {
count++
return count
}
}
const increment = counter()
console.log(increment()) // 1
console.log(increment()) // 2
console.log(increment()) // 3
テスト:
// From lines XX-YY: クロージャカウンター例
it('should maintain state across calls via closure', () => {
function counter() {
let count = 0
return function() {
count++
return count
}
}
const increment = counter()
expect(increment()).toBe(1)
expect(increment()).toBe(2)
expect(increment()).toBe(3)
})
it('should create independent counters', () => {
function counter() {
let count = 0
return function() {
count++
return count
}
}
const counter1 = counter()
const counter2 = counter()
expect(counter1()).toBe(1)
expect(counter1()).toBe(2)
expect(counter2()).toBe(1) // 独立している
})
パターン13: DOMイベントテスト
ドキュメント:
const button = document.getElementById('myButton')
button.addEventListener('click', function(event) {
console.log('Button clicked!')
console.log(event.type) // "click"
})
テスト(.dom.test.jsファイル内):
/**
* @vitest-environment jsdom
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
describe('DOM Event Handlers', () => {
let button
beforeEach(() => {
button = document.createElement('button')
button.id = 'myButton'
document.body.appendChild(button)
})
afterEach(() => {
document.body.innerHTML = ''
})
// From lines XX-YY: ボタンクリックイベント
it('should fire click event handler', () => {
const output = []
button.addEventListener('click', function(event) {
output.push('Button clicked!')
output.push(event.type)
})
button.click()
expect(output).toEqual(['Button clicked!', 'click'])
})
})
パターン14: DOM操作テスト
ドキュメント:
const div = document.createElement('div')
div.textContent = 'Hello'
div.classList.add('greeting')
document.body.appendChild(div)
テスト:
// From lines XX-YY: 要素の作成と追加
it('should create element with text and class', () => {
const div = document.createElement('div')
div.textContent = 'Hello'
div.classList.add('greeting')
document.body.appendChild(div)
const element = document.querySelector('.greeting')
expect(element).not.toBeNull()
expect(element.textContent).toBe('Hello')
expect(element.classList.contains('greeting')).toBe(true)
})
パターン15: タイマーテスト
ドキュメント:
console.log('First')
setTimeout(() => console.log('Second'), 0)
console.log('Third')
// Output: First, Third, Second
テスト:
// From lines XX-YY: setTimeoutの実行順序
it('should execute setTimeout callback after synchronous code', async () => {
const output = []
output.push('First')
setTimeout(() => output.push('Second'), 0)
output.push('Third')
// setTimeoutの実行を待つ
await new Promise(resolve => setTimeout(resolve, 10))
expect(output).toEqual(['First', 'Third', 'Second'])
})
パターン16: ストリクトモード動作
ドキュメント:
// ストリクトモードではこれはエラーをスロー
"use strict"
x = 10 // ReferenceError: x is not defined
テスト:
// From lines XX-YY: ストリクトモード変数宣言
it('should throw ReferenceError in strict mode for undeclared variables', () => {
// Vitestはデフォルトでストリクトモードで実行
expect(() => {
// evalを使用してストリクトモード動作をテスト
"use strict"
eval('undeclaredVar = 10')
}).toThrow()
})
完全なテストファイルテンプレート
import { describe, it, expect } from 'vitest'
describe('[Concept Name]', () => {
// ============================================================
// [ドキュメントの最初のセクション名]
// From [concept].mdx lines XX-YY
// ============================================================
describe('[First Section]', () => {
// From lines XX-YY: [例の簡潔な説明]
it('should [expected behavior]', () => {
// ドキュメントからのコード
expect(result).toBe(expected)
})
// From lines XX-YY: [次の例の簡潔な説明]
it('should [another expected behavior]', () => {
// ドキュメントからのコード
expect(result).toEqual(expected)
})
})
// ============================================================
// [ドキュメントの2番目のセクション名]
// From [concept].mdx lines XX-YY
// ============================================================
describe('[Second Section]', () => {
// From lines XX-YY: [説明]
it('should [behavior]', () => {
// テスト
})
})
// ============================================================
// エッジケースと一般的な間違い
// From [concept].mdx lines XX-YY
// ============================================================
describe('Edge Cases', () => {
// From lines XX-YY: [エッジケース説明]
it('should handle [edge case]', () => {
// テスト
})
})
describe('Common Mistakes', () => {
// From lines XX-YY: 間違った方法の例
it('should demonstrate the incorrect behavior', () => {
// 「間違った」方法が失敗する理由を示すテスト
})
// From lines XX-YY: 正しい方法の例
it('should demonstrate the correct behavior', () => {
// 正しいアプローチを示すテスト
})
})
})
完全なDOMテストファイルテンプレート
/**
* @vitest-environment jsdom
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
// ============================================================
// [コンセプト名]のDOM例
// From [concept].mdx lines XX-YY
// ============================================================
describe('[Concept Name] - DOM', () => {
// 共有セットアップ
let container
beforeEach(() => {
// 各テスト用に新しいコンテナを作成
container = document.createElement('div')
container.id = 'test-container'
document.body.appendChild(container)
})
afterEach(() => {
// 各テスト後のクリーンアップ
document.body.innerHTML = ''
vi.restoreAllMocks()
})
// ============================================================
// [セクション名]
// From lines XX-YY
// ============================================================
describe('[Section Name]', () => {
// From lines XX-YY: [例の説明]
it('should [expected DOM behavior]', () => {
// セットアップ
const element = document.createElement('div')
container.appendChild(element)
// アクション
element.textContent = 'Hello'
// アサーション
expect(element.textContent).toBe('Hello')
})
})
// ============================================================
// イベント処理
// From lines XX-YY
// ============================================================
describe('Event Handling', () => {
// From lines XX-YY: クリックイベント例
it('should handle click events', () => {
const button = document.createElement('button')
container.appendChild(button)
let clicked = false
button.addEventListener('click', () => {
clicked = true
})
button.click()
expect(clicked).toBe(true)
})
})
})
テストの実行
# すべてのテストを実行
npm test
# 特定のコンセプトのテストを実行
npm test -- tests/fundamentals/primitive-types/
# 特定のファイルのテストを実行
npm test -- tests/fundamentals/primitive-types/primitive-types.test.js
# DOMテストのみを実行
npm test -- tests/fundamentals/primitive-types/primitive-types.dom.test.js
# ウォッチモードで実行
npm run test:watch
# カバレッジ付きで実行
npm run test:coverage
# 詳細出力で実行
npm test -- --reporter=verbose
品質チェックリスト
完全性
- すべてのテスト可能なコード例に対応するテストがある
- テストはドキュメントセクション別に整理されている
- ソース行参照がコメント内に含まれている(From lines XX-YY)
- DOMテストは別の
.dom.test.jsファイルにある - エッジケースとエラー例がテストされている
正確性
- テストは実際のドキュメント化された動作を検証している
- ドキュメント内の出力コメントがテスト期待値と一致している
- 非同期テストは
async/awaitを適切に使用している - エラーテストは正しい
toThrowパターンを使用している - 浮動小数点比較は
toBeCloseToを使用している - オブジェクト比較は
toEqualを使用している(toBeではなく)
規約
- vitestから明示的なインポートを使用している
- describe/itネスティングパターンに従っている
- テスト名は「should」で始まる
- 適切なファイル命名(
{concept}.test.js) - DOMテストはjsdom環境ディレクティブを持っている
検証
- すべてのテストが通る:
npm test -- tests/{category}/{concept}/ - ドキュメント化された理由のない、スキップされたテストがない
- 偽陽性がない(間違った理由で通るテスト)
テストレポートテンプレート
このテンプレートを使用して、コンセプトページのテストカバレッジをドキュメント化します。
# テストカバレッジレポート: [コンセプト名]
**コンセプトページ:** `/docs/concepts/[slug].mdx`
**テストファイル:** `/tests/{category}/{concept}/{concept}.test.js`
**DOMテストファイル:** `/tests/{category}/{concept}/{concept}.dom.test.js`(該当する場合)
**日付:** YYYY-MM-DD
**作成者:** [名前/Claude]
## 概要
| メトリック | 数 |
|-----------|-----|
| ドキュメント内のコード例総数 | XX |
| テスト可能な例 | XX |
| 作成されたテスト | XX |
| 作成されたDOMテスト | XX |
| スキップ(理由付き) | XX |
## セクション別テスト
| セクション | 行範囲 | 例 | テスト | ステータス |
|-----------|--------|-----|--------|-----------|
| [セクション1] | XX-YY | X | X | ✅ |
| [セクション2] | XX-YY | X | X | ✅ |
| [セクション3] | XX-YY | X | X | ⚠️ (1つスキップ) |
## スキップされた例
| 行 | 例の説明 | 理由 |
|----|--------|------|
| XX | コールスタックのASCIIダイアグラム | 概念的で実行不可 |
| YY | ブラウザfetch例 | ネットワーク必須、代わりにモック |
## テスト実行
```bash
npm test -- tests/{category}/{concept}/
結果: ✅ XX通過 | ❌ X失敗 | ⏭️ Xスキップ
注記
[特別な考慮事項、モック要件、または遭遇した問題]
---
## 一般的な問題と解決策
### 問題: テストは通るが、通るべきではない
**問題:** テスト期待値がドキュメント出力と一致しない
**解決策:** 期待値が`console.log`コメントと正確に一致することを確認します
```javascript
// ドキュメントに記載: console.log(result) // [1, 2, 3]
// テストで必ず使用:
expect(result).toEqual([1, 2, 3]) // 配列にはtoBeではなくtoEqualを使用
問題: 非同期テストがタイムアウト
問題: 非同期テストが解決されない
解決策: すべてのPromiseがawaitされ、非同期関数がマークされていることを確認します
// 悪い例
it('should fetch data', () => {
const data = fetchData() // awaitが足りない!
expect(data).toBeDefined()
})
// 良い例
it('should fetch data', async () => {
const data = await fetchData()
expect(data).toBeDefined()
})
問題: DOMテストが「documentは定義されていない」で失敗
問題: jsdom環境ディレクティブが不足している
解決策: ファイルの先頭に環境ディレクティブを追加します
/**
* @vitest-environment jsdom
*/
問題: テスト分離の問題
問題: テストが互いに影響する
解決策: クリーンアップにbeforeEach/afterEachを使用します
afterEach(() => {
document.body.innerHTML = ''
vi.restoreAllMocks()
})
概要
コンセプトページのテストを作成するとき:
- ドキュメントからすべてのコード例を抽出
- テスト可能、DOM、エラー、または概念的として分類
- 正しい場所に正しい命名でテストファイルを作成
- 適切なパターンを使用して各例をテストに変換
- トレーサビリティのためコメント内にソース行を参照
- すべてが通ることを確認するテストを実行
- レポートテンプレートを使用してカバレッジをドキュメント化
覚えておいてください: テストは2つの目的を果たします:
- ドキュメントが正確であることを確認
- コード例が更新されたときの回帰をキャッチ
ドキュメント内のすべてのテスト可能なコード例には対応するテストが必要です。例がテストできない場合、その理由をドキュメント化します。
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- leonardomso
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/leonardomso/33-js-concepts / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。