1. 概要

ScalaTest は、 Scala エコシステムで最も人気があり、完全で使いやすいテストフレームワークの1つです。

ScalaTestが他のテストツールと異なる点は、XUnitやBDDなどのさまざまなテストスタイルをすぐにサポートできることです。

この入門チュートリアルでは、XUnitとBDD のサポートを調べる前に、最初のテストを作成することから始めます。 また、マッチャーやモックなど、他のいくつかの重要な機能についても説明します。

その過程で、結果のコードがいかに簡潔で理解しやすいかを示します。

2. 依存関係 

ScalaTestの設定は非常に簡単です。 scalatest依存関係をbuild.sbtファイルに追加するだけで済みます。

libraryDependencies += "org.scalatest" %% "scalatest" % "3.1.1" % "test"

または、Mavenプロジェクトがある場合は、pom.xmlにScalaTestを追加できます。

<dependency>
  <groupId>org.scalatest</groupId>
  <artifactId>scalatest_2.13</artifactId>
  <version>3.1.1</version>
  <scope>test</scope>
</dependency>

いつものように、 MavenCentralから最新バージョンを入手できます。

3. 重要な概念

少しコンテキストを説明するために、実際の例に飛び込む前に、ScalaTestの主要な概念のいくつかを強調しましょう。

  • ScalaTestの中心にあるのは、スイートの概念です。これは、0個以上のテストの単なるコレクションです。
  • 実際、Scala trait Suite は、テストの記述と実行の方法を定義するいくつかのライフサイクルメソッドを宣言しています。
  • ありがたいことに、ScalaTestは、さまざまなテストスタイルをサポートするためにSuiteを拡張する多くのスタイル特性をすでに提供しています。
  • さらに、これらのスタイルの特性を、MatchersBeforeAndAfterなど、ScalaTestがスタック可能な特性と呼ぶ任意の数と組み合わせることができます。
  • このアプローチを使用すると、一貫性があり、読みやすく、焦点を絞ったテストをすばやく構築できます。
  • 最後に、もちろん、ScalaTestのスタイルまたはScalaTestの外部の積み重ね可能な特性の上に構築することは完全に可能です。

4. 最初の単体テストの作成

ScalaTestを構成し、フレームワークの背後にある哲学のいくつかを理解したので、リストをテストするための簡単な単体テストを定義することから始めましょう。

class ListFunSuite extends FunSuite {

  test("An empty List should have size 0") {
    assert(List.empty.size == 0)
  }

}

ここでは、テストListFunSuiteFunSuite特性を拡張しています。 この特性は通常、xUnitの経験を持つ人々を対象としており、説明的なテスト名を使用してテストをわかりやすいtest()ブロックに編成できます。

上記の例では、空のリストのサイズがゼロであると主張しているだけです。 次に、sbtを使用してテストを実行しましょう。

sbt:scala-tutorials> testOnly com.baeldung.scala.scalatest.ListFunSuite
...
[info] Done compiling.
[info] ListFunSuite:
[info] - An empty List should have size 0
[info] ScalaTest
[info] Run completed in 87 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[info] Passed: Total 1, Failed 0, Errors 0, Passed 1
[success] Total time: 1 s, completed Mar 25, 2020 4:32:17 PM

予想どおり、テストに合格し、sbtは簡単なテストレポートの概要を出力します。

これで、先に進んで、テストスイートにさらに高度なテストを追加できます。

たとえば、メソッドが予期された例外をスローするかどうかをテストする必要がある場合もあります。 これはassertThrowsで実行できます。

test("Accessing invalid index should throw IndexOutOfBoundsException") {
  val fruit = List("Banana", "Pineapple", "Apple");
  assert(fruit.head == "Banana")
  assertThrows[IndexOutOfBoundsException] {
    fruit(5)
  }
}

このテストでは、最初にフルーツリストの最初のアイテムがバナナであることを確認し、次にで無効なインデックスにアクセスしようとしたときにIndexOutOfBoundsExceptionを受け取ることを確認します。果物のリスト。

5. さまざまなテストスタイルの使用

前に述べたように、ScalaTestは箱から出してすぐにいくつかの異なるテストスタイルをサポートします。 前のセクションでは、FunSuiteトレイトの使用方法を説明しました。 このセクションでは、他のより人気のあるスタイルのいくつかを見ていきます。

5.1. FlatSpecを採用

FlatSpec 特性の背後にある主な前提は、BDDスタイルの開発を促進することです。 私たちが作成するテストの構造は本質的にネストされていないため、フラットと名付けられています。 さらに、この特性は、説明的な仕様スタイルの名前を使用して、より焦点を絞ったテストを作成するようにガイドしようとします。

実際、ScalaTestプロジェクトでは、単体テストを作成するためのデフォルトの選択肢としてFlatSpecを使用することを推奨しています。 そのことを念頭に置いて、単純な List の例を再実装しましょう。ただし、今回はFlatSpecから拡張します。

class ListFlatSpec extends FlatSpec {

  "An empty List" should "have size 0" in {
    assert(List.empty.size == 0)
  }

  it should "throw an IndexOutOfBoundsException when trying to access any element" in {
    val emptyList = List();
    assertThrows[IndexOutOfBoundsException] {
      emptyList(1)
    }
  }
}

私たちのテストは、仕様のように、つまり「XはYでなければならない」というようになりました。 そして、間違いなく、説明はより自然な流れを持っています。

FunSuite の例との重要な違いの1つは、 it – を使用すると、これが前の名前(この場合は「空のリスト」)のエイリアスになることです。

ListFlatSpec テストを実行すると、出力でこれがより明確にわかります。

...
[info] ListFlatSpec:
[info] An empty List
[info] - should have size 0
[info] - should throw an IndexOutOfBoundsException when trying to access any element
...

5.2. FunSpecの操作

FunSpec を使用して、このBDDスタイルのテストをさらに一歩進めることができます。

class ListFunSpec extends FunSpec {

  describe("A List") {
    describe("when empty") {
      it("should have size 0") {
        assert(List.empty.size == 0)
      }

      it("should throw an IndexOutOfBoundsException when to access an element") {
        val emptyList = List();
        assertThrows[IndexOutOfBoundsException] {
          emptyList(1)
        }
      }
    }
  }
}

この種の構文は、RSpecまたはJavascriptを使用する人々に馴染みがあるはずです。 ここでの主な違いは、テストの構造がネストによって提供され、describeとitを使用して記述されていることです。

ListFunSpec テストを実行すると、ネストがより明確にわかります。

[info] ListFunSpec:
[info] A List
[info]   when empty
[info]   - should have size 0
[info]   - should throw an IndexOutOfBoundsException when to access an element

簡単に要約すると、BDDスタイルのテストにFunSpecを使用できます。 この特性は、仕様スタイルのテストを作成するための優れた汎用の選択肢を提供します。

明示的に述べられていない限り、残りの例では、FlatSpecを使用します。

6. 前後

多くの場合、各テストの前後にいくつかの一般的なコードを実行する必要があります。 これにより、テストでの重複コードを最小限に抑え、ファイルやデータベース接続などのリソースを設定できます。 または、テストメソッド間でいくつかの変数を単純に共有したい場合もあります。

ScalaTestでは、BeforeAndAfterトレイトを組み合わせて、各テストの前に実行するコードを登録するだけです。 これを実現するには、実行するコードを登録するbefore句とafter句を宣言するだけです。

class StringFlatSpecWithBeforeAndAfter extends FlatSpec with BeforeAndAfter {

  val builder = new StringBuilder;

  before {
    builder.append("Baeldung ")
  }

  after {
    builder.clear()
  }
  
  // ...

この簡単な例では、テスト間で共有されるStringBuilderインスタンスvalを作成し、各テストの前にテキストを追加するだけで、各テストの後に[X199X ]ビルダー変数。

次に、 builder 変数を直接使用して、テストにデータを入力できます。

"Baeldung" should "be interesting" in {
  assert(builder.toString === "Baeldung ")

  builder.append("is very interesting!")
  assert(builder.toString === "Baeldung is very interesting!")
}

it should "have great tutorials" in {
  assert(builder.toString === "Baeldung ")

  builder.append("has great tutorials!")
  assert(builder.toString === "Baeldung has great tutorials!")
}

beforeおよびafterコードがテストコードと通信できる唯一の方法は、何らかの副作用メカニズムを介することであることに注意してください。 たとえば、インスタンスvalから保持されている可変オブジェクトの状態を変更することでそれを実行できることを確認しました。

7. マッチャーの操作

これまで、テストでは標準のアサーションを使用してきましたが、これはデフォルトで任意のスタイルの特性で使用できます。 ただし、ScalaTestは、単語を使用してテストでアサーションを表現するための強力なドメイン固有言語(DSL)を提供しますしたほうがいい。 

これを使用するには、Matchers特性を単純に組み合わせることができます。

import org.scalatest.{FlatSpec, Matchers}

class ExampleFlatSpecWithMatchers extends FlatSpec with Matchers {
...

7.1. ベーシックマッチャー

実際にいくつかの基本的なマッチャーを見てみましょう。 まず、いくつかの簡単な同等性チェックから始めます。

"A matcher" should "let us check equality" in {
  val number = 25
  number should equal (25)
  number shouldEqual 25
}

it should "also let us check equality using be" in {
  val number = 25
  number should be (25)
  number shouldBe 25
}

一般的に言って、同等性をチェックする方法は主に2つあります。

上記の最初のテストでは、equalshouldEqualを使用します。 ここでの唯一の違いは、shouldEqualは引数値に括弧を必要としないことです。 同様に、2番目のテストでは、括弧付きと括弧なしの引数を使用して、beshouldBeマッチャーが表示されます。

ただし、最初のテストと2番目のテストには微妙な違いがあります。 * equal バリエーションを使用する場合、これらの構成は暗黙の Equality [T] を取り、計算された値を期待値で検証するため、等価性チェックをカスタマイズできます。

私たちが何を意味するのかを理解するための簡単な例を見てみましょう。

it should "also let us customize equality" in {
  " baeldung  " should equal("baeldung")(after being trimmed)
}

トリミングされた後の式は、 Equality [String] になり、2番目のパラメーターとしてequalに明示的に渡されます。

beで2番目のバリエーションを使用すると、等価性チェックをカスタマイズできません。 経験則として、単に値を比較したい場合は、で十分です。 また、コンパイルが最速です。

もちろん、テスト時にサイズと長さを頻繁にチェックする必要がありますが、それが理にかなっている場合に限ります。

it should "let us check the length of strings" in {
  val result = "baeldung"
  result should have length 8
}

コレクションのサイズも確認できます。

it should "let us check the size of collections" in {
  val days = List("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
  days should have size 7
}

具体的なルールではありませんが、lengthまたはsizeのどちらを使用するかを決定する良い方法は、lengthまたは getLength という名前のメソッドは、「の長さ」に適用できます。 同様に、同じルールをサイズ構文に適合させることができます。

長さまたはサイズフィールドのタイプ、またはメソッドの戻りタイプは、IntまたはLongのいずれかである必要があります。

7.2. 文字列の確認

ある時点で、ほぼ確実に、テスト内から文字列値をチェックしたいと思うでしょう。 Matchers ミックスインを使用して、さまざまな操作をサポートしています。

いくつかの簡単な文字列チェックから始めましょう:

it should "let us check different parts of a string" in {
  val headline = "Baeldung is really cool!"
  headline should startWith("Baeldung")
  headline should endWith("cool!")
  headline should include("really")
}

ここで、電子メールアドレスが有効であることを確認したいとします。

it should "let us check that an email is valid" in {
  "[email protected]" should fullyMatch regex """[^@]+@[^\.]+\..+"""
}

ご覧のとおり、正規表現を使用して簡単に厳密なチェックを行うことができます。

7.3. その他の便利なマッチャー

このセクションでは、Matchers特性のいくつかのより価値のある機能を簡単に見ていきます。 時々、私たちは空虚さをチェックしたいかもしれません。

このために、ScalaTestはemptyトークンを提供します。

it should "let us check a list is empty" in {
  List.empty shouldBe empty
}

同様に、 emptyと一緒にnot述語を使用して逆をチェックすることもできます。

it should "let us check a list is NOT empty" in {
  List(1, 2, 3) should not be empty
}

最後のいくつかの例では、コレクションに特定の要素が含まれているかどうかを確認する方法を示し、その後、時々必要になる可能性のあるオブジェクトのタイプを確認する方法を示します。

it should "let us check a map contains a given key and value" in {
  Map('x' -> 10, 'y' -> 20, 'z' -> 30) should contain('y' -> 20)
}

このセクションを締めくくるには、オブジェクトのタイプを確認する方法を見てみましょう。

it should "let us check the type of an object" in {
  List(1, 2, 3) shouldBe a[List[_]]
  List(1, 2, 3) should not be a[Map[_, _]]
}

このセクションでは、マッチャーを使用して印象的な shouldDSLを組み込む方法を説明しました。 このスタイルは非公式な感じで、コードを読みやすくし、会話のように流れます。

このチュートリアルでは、この重要な特性の表面にのみ触れました。 詳細と例については、完全なドキュメントを参照してください。

8。 タグ付けテスト

私たち全員が時々行う必要があることは、おそらく断続的な障害を診断するときに、ユニットテストを一時的にオフにすることです。

デフォルトでは、ScalaTestはこれを正確に実行するために使用できる1つのタグ無視をサポートしています。 テストケース定義のitを置き換えるだけです。

ignore should "let us check a list is empty" in {
  List.empty shouldBe empty
}

テストスイートを実行すると、出力にこれがはっきりと表示されます。

sbt:scala-tutorials> testOnly com.baeldung.scala.scalatest.ExampleFlatSpecWithMatchers
...
[info] - should let us check that an email is valid
[info] - should let us check a list is empty !!! IGNORED !!!
[info] - should let us check a list is NOT empty
...

さらに、 tagsedAs を使用して、別の方法でテストにタグを付けることもできます。

object BaeldungJavaTag extends Tag("com.baeldung.scala.scalatest.BaeldungJavaTag")

class TaggedFlatSpec extends FlatSpec with Matchers {

  "Baeldung" should "be interesting" taggedAs (BaeldungJavaTag) in {
    "Baeldung has articles about Java" should include("Java")
  }
}

この短い例では、独自の BaeldungJavaTag、を定義し、tagsedBy句の後にテスト定義で使用します。

次に、BaeldungJavaTagでタグ付けされたテストケースのみを実行する可能性があります。

sbt "testOnly -- -n com.baeldung.scala.scalatest.BaeldungJavaTag"
...
[info] All tests passed.
[info] Passed: Total 1, Failed 0, Errors 0, Passed 1

通常、遅いテストや、他のテストほど頻繁に実行したくないテストにタグを付けたい場合があります。 ScalaTestは、上記のアプローチを使用してこれを簡単にします。

9. 嘲笑

ScalaTestは、任意のJavaモックフレームワークの使用を許可します。または、ネイティブのScalaモックライブラリであるScalaMockを使用できます。 ここでは、ScalaTestのScalaMockの使用方法について簡単に説明します。

まず、build.sbtに依存関係を追加する必要があります。

libraryDependencies += "org.scalamock" %% "scalamock" % "4.4.0" % Test

次に、開始するには、トレイトMockFactoryをミックスする必要があります。

class ScalaMockFlatSpec extends FlatSpec with MockFactory with Matchers {

次に、先に進んでScalaMockを操作できます。

"A mocked Foo" should "return a mocked bar value" in {
  val mockFoo = mock[Foo]
  mockFoo.bar _ expects() returning 6

  mockFoo.bar should be(6)
}

class Foo {
  def bar = 100
}

この非常に単純な例では、モックオブジェクトを作成して期待値を設定する方法を示しています。次に、barメソッドによって返される値が期待どおりであることを確認できます。

10. 結論 

要約すると、このチュートリアルでは、Scalaの包括的なテストフレームワークであるScalaTestを最初に見てきました。

まず、いくつかの重要な概念を説明することから始め、最初のテストの書き方を見ました。 次に、いくつかの異なるテストスタイルと、テストにいくつかの優れたマッチャーを組み合わせる方法を確認しました。

最後に、テストでタグとモックを使用する方法について簡単に説明しました。

いつものように、記事の完全なソースコードは、GitHubから入手できます。