コンテンツへスキップ

Crystalコードのテスト

Crystalには、Specモジュールにフル機能のspecライブラリが付属しています。これは、コードの動作方法の実行可能な例を作成するための構造を提供します。

Rspecにインスパイアされたこのライブラリには、ドメイン固有言語(DSL)が含まれており、平易な英語と同様の方法で例を作成できます。

基本的なspecは次のようになります。

require "spec"

describe Array do
  describe "#size" do
    it "correctly reports the number of elements in the Array" do
      [1, 2, 3].size.should eq 3
    end
  end

  describe "#empty?" do
    it "is true when no elements are in the array" do
      ([] of Int32).empty?.should be_true
    end

    it "is false if there are elements in the array" do
      [1].empty?.should be_false
    end
  end
end

specファイルの構成

specモジュールとDSLを使用するには、specファイルにrequire "spec"を追加する必要があります。多くのプロジェクトでは、これらのインクルードを整理するカスタムspecヘルパーを使用しています。

具体的なテストケースは、itブロックで定義されます。オプションの(ただし強く推奨される)説明的な文字列はその目的を示し、ブロックにはテストを実行するメインロジックが含まれます。

定義または概説されているが、まだ動作することが期待されていないテストケースは、itの代わりにpendingを使用して定義できます。これらは実行されませんが、保留中のものとしてspecレポートに表示されます。

itブロックには、テスト対象のコードを呼び出し、そのコードから期待される動作を定義する例が含まれています。各例には複数の期待値を含めることができますが、1つの特定の動作のみをテストする必要があります。

specが含まれている場合、すべてのオブジェクトにインスタンスメソッド#should#should_notがあります。これらのメソッドは、引数として期待値を使用して、テスト対象の値で呼び出されます。期待値が満たされると、コードの実行は継続します。そうでない場合、例は失敗し、このブロック内の他のコードは実行されません。

テストファイルでは、specは、describeセクションとcontextセクションで定義された例グループによって構造化されます。通常、最上位のdescribeは、specによってテストされる外部ユニット(クラスなど)を定義します。さらに、describeセクションは外部ユニット内にネストして、テスト対象のより小さなユニット(個々のメソッドなど)を指定できます。

ユニットテストでは、メソッド名の規則に従うことをお勧めします。外部のdescribeはクラスの名前、内部のdescribeはメソッドを対象とします。インスタンスメソッドには#、クラスメソッドには.が接頭辞として付けられます。

特定のコンテキスト(「空の配列」対「要素を含む配列」など)を確立するために、contextメソッドを使用してこれを閲覧者に伝えることができます。名前は異なりますが、describeとまったく同じように動作します。

describecontextは、引数として説明(通常は文字列)と、個々のspecまたはネストされたグループを含むブロックを取ります。

期待値

期待値は、テスト対象の値(実際値)が特定の値または特定の条件と一致するかどうかを定義します。

等価性、同一性、型

等価性(eq)、同一性(be)、型(be_a)、およびnil(be_nil)をテストする期待値を作成するメソッドがあります。同一性の期待値は#object_idが同一かどうかをテストする.same?を使用することに注意してください。これは、期待値が同じオブジェクトではなく同等のオブジェクトを指している場合にのみ当てはまります。これは参照型の場合にのみ可能であり、構造体や数値などの値型では機能しません。

actual.should eq(expected)   # passes if actual == expected
actual.should be(expected)   # passes if actual.same?(expected)
actual.should be_a(expected) # passes if actual.is_a?(expected)
actual.should be_nil         # passes if actual.nil?

真偽値

actual.should be_true   # passes if actual == true
actual.should be_false  # passes if actual == false
actual.should be_truthy # passes if actual is truthy (neither nil nor false nor Pointer.null)
actual.should be_falsey # passes if actual is falsey (nil, false or Pointer.null)

比較

actual.should be < expected  # passes if actual <  expected
actual.should be <= expected # passes if actual <= expected
actual.should be > expected  # passes if actual >  expected
actual.should be >= expected # passes if actual >= expected

その他のマッチャ

actual.should be_close(expected, delta) # passes if actual is within delta of expected:
#                                         (actual - expected).abs <= delta
actual.should contain(expected) # passes if actual.includes?(expected)
actual.should match(expected)   # passes if actual =~ expected

エラーの期待

これらのマッチャはブロックを実行し、特定の例外が発生した場合にパスします。

expect_raises(MyError) do
  # Passes if this block raises an exception of type MyError.
end

expect_raises(MyError, "error message") do
  # Passes if this block raises an exception of type MyError
  # and the error message contains "error message".
end

expect_raises(MyError, /error \w{7}/) do
  # Passes if this block raises an exception of type MyError
  # and the error message matches the regular expression.
end

expect_raisesは、救出された例外を返すため、例外の特定のプロパティを確認するために使用できます。

ex = expect_raises(MyError) do
  # Passes if this block raises an exception of type MyError.
end
ex.my_error_value.should eq "foo"

specグループへのフォーカス

describecontextitブロックには、次のようにfocus: trueを付けることができます。

it "adds", focus: true do
  (2 + 2).should_not eq(5)
end

このようなものがfocus: trueでマークされている場合、それらの例のみが実行されます。

specへのタグ付け

タグを使用してspecをグループ化し、specランナーに--tag引数を指定してspecのサブセットのみを実行できます(コンパイラの使用を参照)。

describecontextitブロックには、次のようにタグを付けることができます。

it "is slow", tags: "slow" do
  sleep 60
  true.should be_true
end

it "is fast", tags: "fast" do
  true.should be_true
end

例グループ(describeまたはcontext)にタグを付けると、含まれるすべての例に拡張されます。

EnumerableArraySetなど)を指定することで、複数のタグを指定できます。

specの実行

Crystalコンパイラには、実行される例を制限し、出力を調整するためのツールを備えたspecコマンドがあります。プロジェクトのすべてのspecは、crystal specコマンドによってコンパイルおよび実行されます。

慣例により、specはプロジェクトのspec/ディレクトリに配置されます。specファイルは、コンパイラコマンドによって認識されるように、_spec.crで終わる必要があります。

フォルダツリー、個々のファイル、またはファイル内の特定の行からスペックをコンパイルして実行できます。指定された行が`describe`または`context`セクションの先頭にある場合、そのグループ内のすべてのスペックが実行されます。

デフォルトのフォーマッタは、失敗したスペックのファイルと行のスタイルのコマンドを出力します。これにより、その個々のスペックだけを簡単に再実行できます。

--no-colorスイッチを使用して、カラー表示をオフにすることができます。

スペックの実行順序のランダム化

スペックはデフォルトでは定義されている順序で実行されますが、`crystal spec`に`--order random`を渡すことで、ランダムな順序で実行できます。

ランダムな順序で実行されたスペックは、完了時にシード値を表示します。このシード値を使用して、`--order`にシード値を渡すことで、同じ順序でスペックを再実行できます。

# Run all specs in files matching spec/**/*_spec.cr
crystal spec

# Run  all specs in files matching spec/**/*_spec.cr without colors
crystal spec --no-color

# Run all specs in files matching spec/my/test/**/*_spec.cr
crystal spec spec/my/test/

# Run all specs in spec/my/test/file_spec.cr
crystal spec spec/my/test/file_spec.cr

# Run the spec or group defined in line 14 of spec/my/test/file_spec.cr
crystal spec spec/my/test/file_spec.cr:14

# Run all specs tagged with "fast"
crystal spec --tag 'fast'

# Run all specs not tagged with "slow"
crystal spec --tag '~slow'

名前によるスペックの実行、出力形式の調整、ドライランの実行など、追加のオプションがあります。詳細はコンパイラの使用方法を参照してください。

スペックヘルパー

多くのプロジェクトでは、通常`spec/spec_helper.cr`という名前のカスタムスペックヘルパーファイルを使用しています。

このファイルは、`spec`と、すべてのスペックファイルに必要なプロジェクトからのコードなどの他のインクルードを必要とするために使用されます。これは、スペックの作成を容易にし、コードの重複を避けるグローバルヘルパーメソッドを定義するのに適した場所でもあります。

spec/spec_helper.cr
require "spec"
require "../src/my_project.cr"

def create_test_object(name)
  project = MyProject.new(option: false)
  object = project.create_object(name)
  object
end
spec/my_project_spec.cr
require "./spec_helper"

describe "MyProject::Object" do
  it "is created" do
    object = create_test_object(name)
    object.should_not be_nil
  end
end