Arturoトラックでのテスト

ExercismでArturoの演習をテストする方法を学びます


Arturoトラックでは、すべての演習のテストにUnitt 3.0.0の単体テストパッケージを使っています。 このパッケージは、モダンなRSpec風の構文と、旧来のXUnit風の構文の両方をサポートするテスティングフレームワークを提供します。 ローカルの演習フォルダーでarturo tester.artまたはexercism testを実行すると、初回実行時にインターネットからパッケージがインストールされます。 別の方法として、arturo --package install unitt 3.0.0を実行してUnittをインストールすることもできます。 最初は、最初のテストだけが実行され、結果が報告されます。 そのテストが通ったら、次のテストのスキップを解除し、それぞれのテストが通るまでテストを再実行します。 RSpecとXUnitのどちらの構成でも、それぞれit.skipまたはtest.skipから.skipを削除するだけです。 すべてのテストが通ったら解答を提出しますが、テストランナーはスキップされたテストも含めてすべてのテストを実行することに注意してください。

はじめに

ダウンロードした演習フォルダーにはいくつかのファイルがありますが、特に重要なのは次の3つです。

leap/
├── src/leap.art         # Your solution file
├── tests/test-leap.art  # Test cases for the exercise
└── tester.art           # Test runner entry point

arturo tester.artを実行すると、Unittがセットアップされ、tests/test-leap.art内のテストが実行されます。 テストは、解答が書かれたsrc/leap.artをインポートし、特定の入力に対して解答が何を返すべきかについて、一連のアサーションを行います。

解答ファイル

isLeap?: function [year][
    panic "please implement the isLeap? function"
]

演習を始めるとき、解答ファイルには、期待される基本的な構造を持つテンプレートが用意されています。 Leapのこの例では、ファイルに、期待されるisLeap?関数の最小限の定義と、注目すべき点として、テストの実行を止めるpanicが含まれています。 panicの行を削除してから、isLeap?の実装を書き始めましょう。

RSpecのテスト構造

import.version:3.0.0 {unitt}!
import {src/leap}!

describe "Leap" [
    it "a year not divisible by 4 is a common year" [
        expects.be:'false? @[isLeap? 2015]
    ]

    it.skip "a year divisible by 4 and not divisible by 100 is a leap year" [
        expects.be:'true? @[isLeap? 1996]
    ]
]
  • describeは、共通の何かを検証するテストケースのブロックを定義します。入れ子にすることもできます。
  • itとit.skipはどちらも1つのテストケースを定義し、それぞれ実行されるかスキップされます。
  • expectsは、テストケースの中心となるアサーションを導入します。expects.beは、アサーションを行うために使われる述語関数を取ります。

expects.be:'false? @[isLeap? 2015]では、テストスイートは、関数isLeap?が2015年に対してfalseを返すべきだというアサーションを行っています。 別の書き方として、expects.be:'equal? @[false isLeapYear? 2015]があります。これは、isLeapYear? 2015の戻り値がfalseと等しいことをアサートしています。 2つの値を比較するこの長い形式は、Arturoトラックで最もよく使われるアサーションの種類です。 ブロック内では、期待値が常に結果の値より先に来ます。

XUnitのテスト構造

suite "Leap" [
    test "a year not divisible by 4 is a common year" [
        result: isLeap? 2015
        assert -> false = result
    ]

    test.skip "a year divisible by 4 and not divisible by 100 is a leap year" [
        result: isLeap? 1996
        assert -> true = result
    ]
]

Unittのv1で導入されたXUnit風のAPIは、後方互換性のためにサポートされており、アサーションの柔軟性が低い点を除けば、モダンなRSpec風のAPIとほぼ同等です。

  • suiteは、共通の何かを検証するテストケースのブロックを定義します。入れ子にすることもできます。
  • testとtest.skipはどちらも1つのテストケースを定義し、それぞれ実行されるかスキップされます。
  • assertは、テストケースの中心となるアサーションを導入します。直後の->の右側の式がtrueでなければなりません。

XUnitのテストでは、通常、結果を表す変数と、必要に応じて期待値を表す変数を、assertの行の前に作ります。

テスト出力

RSpecとXUnitのどちらのAPIを使っていても、テスト出力はほぼ同じです。 違いは、各テスト名の下に表示される内容だけです。 XUnitでは評価された値を使ってアサーションを報告するので、false = falseのように表示されます。 一方、RSpecではexpectsとともにfalse?関数を使ったので、false? falseのように報告されます。

まず最初に、開始用ファイルのpanicを削除していない場合、Arturoはエラーを報告し、テストは一切評価しません。

Uncaught error in tests/test-leap.art:
#[
        output: {
                
                ══╡ Program Error ╞═════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════ <script> ══
                
                  Please implement the isLeap? function
                
                
        }
        code: 1
]

panicを置き換えると、Arturoはテスト内で行われた各アサーションの状態を報告します。 ここでは最初のテストは通りますが、2番目のテストはスキップされています。

======== .unitt/tests/test-leap.art ========

Describe: Leap

    ✅ - assert that a year not divisible by 4 is a common year
         ✅: false? false

    ⏩ - assert that a year divisible by 4 and not divisible by 100 is a leap year
         skipped!


========== Summary ==========

⏏️   TOTAL: 2 assertions
✅  PASSED: 1 assertion
⏩ SKIPPED: 1 assertion
❌  FAILED: 0 assertion

========== ======= ==========

2番目のテストのスキップを手動で解除すると、両方のテストが実行されます。 ただし、現在のisLeap?の実装はfalseしか返さないので、2番目のテストはアサーションに失敗します。

======== .unitt/tests/test-leap.art ========

Describe: Leap

    ✅ - assert that a year not divisible by 4 is a common year
         ✅: false? false

    ❌ - assert that a year divisible by 4 and not divisible by 100 is a leap year
         ❌: true? false


========== Summary ==========

⏏️   TOTAL: 2 assertions
✅  PASSED: 1 assertion
⏩ SKIPPED: 0 assertion
❌  FAILED: 1 assertion

========== ======= ==========

スキップを解除するテストが増えるにつれて、この流れが続きます。 すべてのテストが通ったら、その演習は完了で、解答を提出できます。 なお、テストランナーは、ローカルでスキップされているかどうかに関わらず、すべてのテストを実行します。