unit-test-utility-methods について
ユーティリティクラス、静的ヘルパー、純粋関数の単体テストを作成する開発者向けスキルです。不要なモックを避け、境界や端のケースを重視します。
主な機能
- 01静的ヘルパーと純粋関数のテスト
- 02nullおよび空入力の確認
- 03境界条件の検証
- 04パラメータ化テストの活用
- 05純粋なユーティリティでのモック回避
活用シーン
- 01文字列の整形と変換のテスト
- 02計算と変換の検証
- 03検証処理やコレクション補助の確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ giuseppe-trisciuoglio/ developer-kit --skill unit-test-utility-methodsインストール後、Claude Code で/unit-test-utility-methodsを入力して読み込まれたか確認します。
同梱ファイルreferences/edge-cases.mdreferences/parameterized-tests.md
詳細
ユーティリティクラスと静的メソッドのユニットテスト
概要
このスキルは、静的ヘルパーメソッドや純粋関数を持つユーティリティクラスのテストを生成します。null の処理、エッジケース、境界条件、および文字列操作、計算、データ検証、コレクションなどの一般的なユーティリティをテストするためのパターンを提供します。純粋関数にはモックは必要ありません。
使用する場面
次の場合にこのスキルを使用します。
- 静的メソッドを持つユーティリティ/ヘルパークラスのテストを書く場合
- 状態や副作用のない純粋関数をテストする場合
- 文字列操作、書式設定、変換ユーティリティをテストする場合
- 計算、変換、数学のヘルパー関数をテストする場合
- データ検証やフォーマッターユーティリティをテストする場合
- ユーティリティコードでの null/空入力の処理を検証する場合
- コレクションや配列のヘルパーメソッドをテストする場合
手順
- テストクラスを作成する:ユーティリティの名前に基づいて命名します(例:
StringUtilsTest) - 正常系をテストする:有効な入力と期待される出力を確認します
- エッジケースをテストする:null、空、空白、単一要素を確認します
- 境界条件をテストする:最大/最小値、大きな数値、精度を確認します
- 説明的な名前を使用する:
test1ではなくshouldCapitalizeFirstLetterを使用します - AssertJ を使用する:読みやすく、メソッドを連結できるアサーションにします
@ParameterizedTestを使用する:類似した複数の入力には使用します(references/parameterized-tests.mdを参照)- モックを避ける:純粋なユーティリティにモックは不要です
例
基本的な静的ユーティリティのテスト
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
class StringUtilsTest {
@Test
void shouldCapitalizeFirstLetter() {
assertThat(StringUtils.capitalize("hello")).isEqualTo("Hello");
}
@Test
void shouldReturnNullForNullInput() {
assertThat(StringUtils.capitalize(null)).isNull();
}
@Test
void shouldHandleEmptyString() {
assertThat(StringUtils.capitalize("")).isEmpty();
}
@Test
void shouldHandleSingleCharacter() {
assertThat(StringUtils.capitalize("a")).isEqualTo("A");
}
}
包括的な例:isEmpty の実装
// Input: public static boolean isEmpty(String str)
// { return str == null || str.trim().isEmpty(); }
class StringUtilsTest {
@Test
void shouldReturnTrueForNullString() {
assertThat(StringUtils.isEmpty(null)).isTrue();
}
@Test
void shouldReturnTrueForEmptyString() {
assertThat(StringUtils.isEmpty("")).isTrue();
}
@Test
void shouldReturnTrueForWhitespaceOnly() {
assertThat(StringUtils.isEmpty(" ")).isTrue();
}
@Test
void shouldReturnFalseForNonEmptyString() {
assertThat(StringUtils.isEmpty("hello")).isFalse();
}
}
null 安全なユーティリティ
class NullSafeUtilsTest {
@Test
void shouldReturnDefaultWhenNull() {
assertThat(NullSafeUtils.getOrDefault(null, "default")).isEqualTo("default");
}
@Test
void shouldReturnValueWhenNotNull() {
assertThat(NullSafeUtils.getOrDefault("value", "default")).isEqualTo("value");
}
@Test
void shouldReturnFalseWhenBlank() {
assertThat(NullSafeUtils.isNotBlank(null)).isFalse();
assertThat(NullSafeUtils.isNotBlank(" ")).isFalse();
}
}
数学/計算ユーティリティ
class MathUtilsTest {
@Test
void shouldCalculatePercentage() {
assertThat(MathUtils.percentage(25, 100)).isEqualTo(25.0);
}
@Test
void shouldHandleZeroDivisor() {
assertThat(MathUtils.percentage(50, 0)).isZero();
}
@Test
void shouldRoundToDecimalPlaces() {
assertThat(MathUtils.round(3.14159, 2)).isEqualTo(3.14);
}
@Test
void shouldHandleFloatingPointWithTolerance() {
assertThat(MathUtils.multiply(0.1, 0.2))
.isCloseTo(0.02, within(0.0001));
}
}
コレクションユーティリティ
class CollectionUtilsTest {
@Test
void shouldFilterList() {
List<Integer> result = CollectionUtils.filter(List.of(1, 2, 3, 4), n -> n % 2 == 0);
assertThat(result).containsExactly(2, 4);
}
@Test
void shouldHandleNullList() {
assertThat(CollectionUtils.filter(null, n -> true)).isEmpty();
}
@Test
void shouldJoinWithSeparator() {
assertThat(CollectionUtils.join(List.of("a", "b", "c"), "-")).isEqualTo("a-b-c");
}
@Test
void shouldDeduplicateList() {
assertThat(CollectionUtils.deduplicate(List.of("a", "b", "a")))
.containsExactlyInAnyOrder("a", "b");
}
}
データ検証ユーティリティ
class ValidatorUtilsTest {
@Test
void shouldValidateEmailFormat() {
assertThat(ValidatorUtils.isValidEmail("[email protected]")).isTrue();
assertThat(ValidatorUtils.isValidEmail("invalid")).isFalse();
}
@Test
void shouldValidateUrlFormat() {
assertThat(ValidatorUtils.isValidUrl("https://example.com")).isTrue();
assertThat(ValidatorUtils.isValidUrl("not a url")).isFalse();
}
@Test
void shouldValidateCreditCard() {
assertThat(ValidatorUtils.isValidCreditCard("4532015112830366")).isTrue();
assertThat(ValidatorUtils.isValidCreditCard("1234567890123456")).isFalse();
}
}
Clock 依存のあるユーティリティ(まれなケース)
@ExtendWith(MockitoExtension.class)
class DateUtilsTest {
@Mock
private Clock clock;
@Test
void shouldGetDateFromClock() {
when(clock.instant()).thenReturn(Instant.parse("2024-01-15T10:00:00Z"));
assertThat(DateUtils.today(clock)).isEqualTo(LocalDate.of(2024, 1, 15));
}
}
ベストプラクティス
- 純粋関数のみをテストする - 副作用や状態への依存がないことを確認します
- 正常系とエッジケースを網羅する - null、空、空白、極端な値を確認します
- 説明的なテスト名を使用する -
shouldReturnNullWhenInputIsNull - 類似した複数の入力には
@ParameterizedTestを使用する(references/parameterized-tests.mdを参照) - 境界条件をテストする - 最小/最大値、オーバーフロー、精度を確認します
- 純粋関数のモックは避ける - Clock などの外部依存関係のみをモックします
- テストを独立させる - テスト間に実行順序への依存がないようにします
制約と注意事項
- 静的メソッドはモックしない:やむを得ない場合に限り、リフレクションユーティリティを使用します
- 純粋関数の要件:状態を持つユーティリティはテストが難しくなるため、不変性を優先します
- 浮動小数点数の精度:完全一致は使用せず、
isCloseTo(delta)を使用します - null 処理の一貫性:ユーティリティが null を返すか例外をスローするかを決め、それに応じてテストします
- スレッドセーフ:静的ユーティリティはスレッドセーフである必要があります。並行動作は個別に検証します
- 不変の入力:ユーティリティが入力パラメーターを変更するかどうかを文書化します
- エッジケースのリファレンス:境界値テストのパターンについては
references/edge-cases.mdを参照してください
コメント
Markdown 対応