ポケモンバトルを題材にした共通フィクスチャとテストのカテゴリ化をJUnitで学ぶ①
職場でユニットテストをを書く機会があったのですが、そのそもユニットテストを書いたことない自分からすると最初から何を理解すればいいかわからなかったため、自分用の記事にしました。
この記事はプログラミング初心者が自分のメモ用として書いている記事です。
そのため、誤った情報が記述されている可能性があります。
自己判断で読み進めてください
JUnit 5では、テストの効率化と可読性向上のために、共通の初期化処理を行う「共通フィクスチャ」と、テストを分類する「カテゴリ化」の機能が提供されていますこの記事では、ポケモンバトルを題材に、これらの機能の使い方を具体的なコード例とともに解説します
前提条件
JUnit 5(JUnit Jupiter)を用
Javaでの開発環境が整ってる
ポケモンバトルの基本的な概念に馴染みがる
使用するポケモンはピカチュウとイーブイ(可愛いよね)
テスト対象:Pokemon
と PokemonBattle
クラス
まず、テスト対象となる Pokemon
クラスと PokemonBattle
クラスを定義しす
Pokemon
クラス
public class Pokemon {
private String name;
private int hp;
private int attackPower;
public Pokemon(String name, int hp, int attackPower) {
this.name = name;
this.hp = hp;
this.attackPower = attackPower;
}
public int getHp() {
return hp;
}
public int getAttackPower() {
return attackPower;
}
public void receiveDamage(int damage) {
this.hp = Math.max(this.hp - damage, 0);
}
public String getName() {
return name;
}
}
PokemonBattle
くらs
public class PokemonBattle {
private Pokemon attacker;
private Pokemon defender;
public PokemonBattle(Pokemon attacker, Pokemon defender) {
this.attacker = attacker;
this.defender = defender;
}
public void attack() {
int damage = attacker.getAttackPower();
defender.receiveDamage(damage);
}
public Pokemon getAttacker() {
return attacker;
}
public Pokemon getDefender() {
return defender;
}
}
共通フィクスチャ:@BeforeEach
の用意
共通フィクスチャとは、各テストメソッドの実行前に共通の初期化処理を行う仕組す。JUnit 5では、@BeforeEach
アノテーションを使用して実現ます。
以下のテストクラスでは、@BeforeEach
を使用して、各テストメソッドの前に共通のポケモンインスタンスとバトルインスタンスを初期化してます。
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class PokemonBattleTest {
private Pokemon pikachu;
private Pokemon eievui;
private PokemonBattle battle;
@BeforeEach
public void setUp() {
pikachu = new Pokemon("Pikachu", 100, 20);
eievui = new Pokemon("Eievui", 100, 15);
battle = new PokemonBattle(pikachu, eievui);
}
@Test
public void testAttackReducesHp() {
battle.attack();
assertEquals(80, eievui.getHp());
}
}
このように、@BeforeEach
を使用することで、各テストメソッドでの初期化コードを共通化し、コードの重複を避けることがでます。
@BeforeEachがないとどうなる?
共通フィクスチャがないと、テストごとに手動で初期化コードを書く必要があり、以下のようなデメリットが発生します
コード重複:すべてのテストで同じ new Pokemon(...) を繰り返すことに。
可読性低下:どのポケモンを使っているか毎回読み解く必要がある。
メンテナンス性低下:ポケモンの初期HPを変更したいとき、全テストに修正が必要になる
@BeforeEachがない例
@Test
public void testPikachuVsEievui() {
Pokemon pikachu = new Pokemon("Pikachu", 100, 20);
Pokemon eievui = new Pokemon("Eievui", 100, 15);
PokemonBattle battle = new PokemonBattle(pikachu, eievui);
battle.attack();
assertEquals(80, eievui.getHp());
}
テストのカテゴリ化:@Tag
活用
テストのカテゴリ化とは、テストメソッドやクラスにタグを付与し、特定のタグを持つテストのみを実行するなどのフィルタリングを可能にする仕様です。JUnit 5では、@Tag
アノテーションを使用して実行します
以下のテストクラスでは、@Tag
を使用して、テストメソッドを「critical」や「edge」などのカテゴリに分類します
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class PokemonBattleTest {
private Pokemon pikachu;
private Pokemon eievui;
private PokemonBattle battle;
@BeforeEach
public void setUp() {
pikachu = new Pokemon("Pikachu", 100, 20);
eievui = new Pokemon("Eievui", 100, 15);
battle = new PokemonBattle(pikachu, eievui);
}
@Test
@Tag("critical")
public void testCriticalHit() {
int criticalDamage = pikachu.getAttackPower() * 2;
eievui.receiveDamage(criticalDamage);
assertEquals(60, eievui.getHp());
}
@Test
@Tag("edge")
public void testAttackWhenHpIsZero() {
eievui.receiveDamage(100);
assertEquals(0, eievui.getHp());
battle.attack();
assertEquals(0, eievui.getHp(), "HPが0のときに攻撃を受けてもHPは0のまま");
}
}
このように、@Tag
を使用することで、テストを分類し、特定のカテゴリのテストのみを実行することがきます
最後に
ポケモンのテストを書くの楽しい。例題がイメージしやすく、JUnitの使い方がよく分かった。
テストの数が3個を超えたら、タグでの分類が便利になってくる。
@BeforeEach をサボるとテスト修正時に地獄をみる(絶対に共通セットアップにまとめる)