到達点は、「手元で一度動いた」を、繰り返し確かめられる状態にすることです。前提は第17・18回です。
保存を含む契約を確かめる
次のTaskAppTest.javaを同じフォルダへ保存します。外部ライブラリなしで実行できる小さなテストです。大きく育てるなら第11回のJUnitへ移します。
import java.nio.file.*;
public class TaskAppTest {
static void check(boolean condition, String message) {
if (!condition) throw new AssertionError(message);
}
public static void main(String[] args) throws Exception {
Path dir = Files.createTempDirectory("task-test-");
Path file = dir.resolve("tasks.tsv");
try {
TaskApp.run(new String[]{file.toString(), "add", "復習"});
check(TaskApp.load(file).size() == 1, "追加と再読み込み");
TaskApp.run(new String[]{file.toString(), "done", "1"});
TaskApp.run(new String[]{file.toString(), "done", "1"});
check(TaskApp.load(file).get(0).done(), "二回完了しても完了");
String before = Files.readString(file);
try {
TaskApp.run(new String[]{file.toString(), "done", "999"});
throw new AssertionError("不存在IDを受け入れた");
} catch (IllegalArgumentException expected) { }
check(before.equals(Files.readString(file)), "失敗時に保存内容を維持");
Files.writeString(file, "broken\n");
try {
TaskApp.run(new String[]{file.toString(), "add", "新規"});
throw new AssertionError("破損ファイルを受け入れた");
} catch (IllegalArgumentException expected) { }
check(Files.readString(file).equals("broken\n"), "破損を隠して上書きしない");
System.out.println("すべてのテストが成功");
} finally {
Files.deleteIfExists(file);
Files.deleteIfExists(dir);
}
}
}
javac -encoding UTF-8 Task.java TaskApp.java TaskAppTest.java、java TaskAppTest の順で実行します。最後の行が成功メッセージになります。利用者の保存ファイルではなく、テスト専用の一時ディレクトリを使うことが重要です。
実行できる形で渡す
jar --create --file tasks.jar --main-class TaskApp Task.class TaskApp.class でJARを作り、java -jar tasks.jar tasks.tsv list で起動できます。JRE/JDK 21以降が必要です。外部依存を使う場合は、単に自作classだけをJARへ入れても依存ライブラリは同梱されません。
READMEには必要なJava版、ビルド方法、三つの操作、保存場所、失敗時の終了コード、同時更新に非対応であることを書きます。Gitではソースと設定を追跡し、classや実データを意図せず登録しないようにします。CIでコンパイルとテストを実行すれば、変更のたびに同じ確認ができます。
練習と解答
練習:空の題名、重複IDを含むファイル、0というIDのテストを追加します。
解答の要点:どれも例外となり、保存内容が変わらないことを検証します。エラーメッセージの句読点まで固定するより、利用者に約束した振る舞いを守るテストを優先します。
公式資料
jarコマンドが参照先です。
Java全20回の目次 | 前の回 | 次の回