テストの書き方#

テストは、プログラムの品質と保守性を高めるうえで重要です。プログラムの振る舞いを検証し、時間が経っても回帰を防ぐための仕様としても機能します。

MoonBit にはテスト機能があり、テストを書く作業をより簡単にできます。

テストブロック#

MoonBit には、インラインのテストケースを書くための test コードブロックがあります。例えば:

test "test_name" {
  assert_eq(1 + 1, 2)
  assert_eq(2 + 2, 4)
  debug_inspect([1, 2, 3], content="[1, 2, 3]")
}

test コードブロックは、本質的には Unit を返す関数ですが、Error、つまり戻り値型の位置では Unit!Error と表示されるエラーを送出する可能性があります。これは moon test の実行中に呼び出され、ビルドシステムを通じてテストレポートを出力します。assert_eq 関数は標準ライブラリ由来で、アサーションが失敗するとエラーメッセージを表示してテストを終了します。文字列 "test_name" はテストケースを識別するために使われますが、省略可能です。

テスト名が "panic" で始まる場合、そのテストの期待動作は panic を発生させることであり、panic が発生した場合にのみテストが成功します。例えば:

test "panic_test" {
  let _ : Int = Option::None.unwrap()
}

スナップショットテスト#

期待値を細かく指定しながらテストを書くのは面倒なことがあります。そのため MoonBit には 3 種類のスナップショットテストがあります。いずれも moon test --update で自動的に挿入または更新できます。

Show のスナップショット#

Show トレイトを実装しているものなら、inspect(x, content="x") を使って検査できます。前述のとおり、Show は導出可能な組み込みトレイトであり、データ構造の内容を表示する to_string を提供します。ラベル付き引数 content は、moon test --update が自動で挿入するため省略できます:

struct X {
  x : Int
} derive(Debug)

test "show snapshot test" {
  debug_inspect({ x: 10 }, content="{ x: 10 }")
}

JSON のスナップショット#

導出された Show トレイトの問題は、整形出力をしないため、出力が非常に長くなることです。

解決策は @json.inspect(x, content=x) を使うことです。これにより、出力は JSON 構造になり、整形後に読みやすくなります。

enum Rec {
  End
  Really_long_name_that_is_difficult_to_read(Rec)
} derive(Debug, ToJson)

test "json snapshot test" {
  let r = Really_long_name_that_is_difficult_to_read(
    Really_long_name_that_is_difficult_to_read(
      Really_long_name_that_is_difficult_to_read(End),
    ),
  )
  debug_inspect(
    r,
    content="Really_long_name_that_is_difficult_to_read(Really_long_name_that_is_difficult_to_read(Really_long_name_that_is_difficult_to_read(End)))",
  )
  json_inspect(r, content=[
    "Really_long_name_that_is_difficult_to_read",
    [
      "Really_long_name_that_is_difficult_to_read",
      ["Really_long_name_that_is_difficult_to_read", "End"],
    ],
  ])
}

必要な情報だけを残すために、独自の ToJson を実装することもできます。

あらゆるもののスナップショット#

それでも、1 つのデータ構造だけでなく、処理全体の出力を記録したいことがあります。

完全なスナップショットテストでは、@test.T::write@test.T::writeln を使って何でも記録できます:

test "record anything" (t : @test.Test) {
  t.write("Hello, world!")
  t.writeln(" And hello, MoonBit!")
  t.snapshot(filename="record_anything.txt")
}

これにより、そのパッケージの __snapshot__ 以下に、指定したファイル名のファイルが作成されます:

Hello, world! And hello, MoonBit!

これは、画像や動画、あるいは独自データの生成など、アプリケーションの生成出力をテストするためにも使えます。

@test.T::snapshot は常に例外を送出するため、テストブロックの最後で使う必要がある点に注意してください。

ブラックボックステストとホワイトボックステスト#

ライブラリを開発するときは、利用者が正しく使えるかを確認することが重要です。例えば、型や関数を public にし忘れることがあります。そのため MoonBit には BlackBox テストがあり、開発者が利用者の立場を把握しやすくしています。

  • パッケージ内のすべてのメンバーにアクセスできるテストは、すべてが見えるため WhiteBox テストと呼ばれます。この種のテストは、インラインで定義することも、名前が _wbtest.mbt で終わるファイルに定義することもできます。

  • パッケージの public メンバーのみにアクセスできるテストは、BlackBox テストと呼ばれます。この種のテストは、名前が _test.mbt で終わるファイルに定義する必要があります。

WhiteBox テストファイル(_wbtest.mbt)は、パッケージ設定(moon.pkg、または旧形式の moon.pkg.json)の import および wbtest-import セクションで定義されたパッケージを import します。

BlackBox テストファイル(_test.mbt)は、現在のパッケージと、パッケージ設定(moon.pkg、または旧形式の moon.pkg.json)の import および test-import セクションで定義されたパッケージを import します。