サッカーワールドカップ開幕まであと7日です。
前のワールドカップはいつだったかな~と気になったので、
以前「西暦からサッカーワールドカップ開催年」を判別するコードを作成しました。
下記がJavadocを書いた際に作成したコードです。
/**
* 渡された西暦年がサッカーワールドカップ開催年であるかどうかを判定する
* @param year 西暦
* @return サッカーワールドカップ開催都市であればture
* @throws IllegalArgumentException まだオリンピック開催が確定していない年を渡した場合
*/
public boolean isSoccerWorldCupYear(int year) throws IllegalArgumentException {
if (2026 < year) {
throw new IllegalArgumentException("2026年までサポートしています。入力" + year);
}
return year % 4 == 2;
}
サッカーワールドカップは、西暦を4で割って余りが2になる年に開催されます。
また、現時点では次回大会の2026年までの開催が確定しています。
その旨をコードに落とし込んだのですが、必ずしも4年に1度開催しているわけではありません。
例えば、同じく4年に1度開催しているオリンピックはコロナで延期になりましたね。
サッカーワールドカップも戦争で中止になった年があります。
ということは、このコードにはバグがあると言えます。
(他にも西暦18年は、4で割って余りが2になりますが、当然ワールドカップは開催されていません)
大した修正量ではないので、すぐに直してしまえばいいのですが、
Junitを学んだのでテストコードを書いてから修正を行いたいと思います。
テストを書く前に、まずはテスト項目を洗い出しました。
境界値下限 ⇒ サッカーワールドカップ開催以前の年はfalse
通常開催年 ⇒ 4年周期の開催年はtrue
非開催年 ⇒ 4年周期から外れる非開催年はfalse
中止年 ⇒ 戦争で中止になった年はfalse
延期開催年 ⇒ 4年間隔ではない例外的な開催年はtrue
※サッカーワールドカップには延期開催年は無かったため、テスト項目から除外。
境界値上限 ⇒ 開催地が決定している年よりも後は例外発生
テスト項目をもとに、テストコードを書くと下記のようになりました。
package soccer;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@SuppressWarnings("ALL")
class WorldCupTest {
@Test
void 境界値下限() {
// サッカーワールドカップ開始以前
assertFalse(new WorldCup().isSoccerWorldCupYear(1890), "1890年");
assertFalse(new WorldCup().isSoccerWorldCupYear(1925), "1925年");
assertFalse(new WorldCup().isSoccerWorldCupYear(1929), "1929年");
// 初回開催年
assertTrue(new WorldCup().isSoccerWorldCupYear(1930), "1930年");
}
@Test
void 四年周期の開催年() {
int years = {1950, 1986, 2002, 2018};
for (int year: years) {
assertTrue(new WorldCup().isSoccerWorldCupYear(year), year + "年");
}
}
@Test
void 戦争による開催中止年() {
int years = {1942, 1946};
for (int year: years) {
assertFalse(new WorldCup().isSoccerWorldCupYear(year), year + "年");
}
}
@Test
void 境界値上限() {
assertDoesNotThrow(
() -> new WorldCup().isSoccerWorldCupYear(2025));
assertDoesNotThrow(
() -> new WorldCup().isSoccerWorldCupYear(2026));
// 開催が決定している2026年より後は例外が発生すること
assertThrows(IllegalArgumentException.class,
() -> new WorldCup().isSoccerWorldCupYear(2027));
assertThrows(IllegalArgumentException.class,
() -> new WorldCup().isSoccerWorldCupYear(2028));
}
}
このテストコードを実行するともちろん失敗します。
次に失敗したケースのテストが通るように実装を進めていきます。
package soccer;
public class WorldCup {
/**
* 渡された西暦年がサッカーワールドカップ開催年であるかどうかを判定する
* @param year 西暦
* @return サッカーワールドカップ開催都市であればture
* @throws IllegalArgumentException まだオリンピック開催が確定していない年を渡した場合
*/
public boolean isSoccerWorldCupYear(int year) throws IllegalArgumentException {
// 境界値下限
if (year < 1930) {
return false;
}
// 戦争で中止
if (year == 1942 || year == 1946) {
return false;
}
// 境界値上限を超えたら例外発生
if (year > 2026) {
throw new IllegalArgumentException("2026年までサポートしています。入力" + year);
}
// 四年周期の開催年
return year % 4 == 2;
}
}
テストケースが通るようになりました。
今回は軽く実装していましたが、実装よりもテストを先に書く手法を
「テスト駆動開発」と呼ぶらしく注目されている手法とのことです。
※コードのあちこちで文字サイズがバラバラだったり、表示がおかしかったりしますが、理由がわかりません。ご了承ください。