【Scala】ScalaTest ~ あれこれ編 ~

■ はじめに

https://dk521123.hatenablog.com/entry/2023/03/27/001306
https://dk521123.hatenablog.com/entry/2023/03/28/003906

の続き。

今回は、Scalaの単体試験する際に
ScalaTest を使用した時のTipsについてまとめる

目次

【1】初期処理・後処理
 1)BeforeAndAfter
 2)BeforeAndAfterAll
【2】一時的テストを停止する
 1)テストメソッドを停止する
 2)テストクラスを全体を停止する
【3】Public以外のメソッドのテスト
 1)private メソッドをテストする
 2)protected メソッドをテストする
【4】Table で色々なパターンの値を用意する
【5】例外処理をテストする
 1)発生した例外を確認する
 2)発生した例外+メッセージを確認する

【1】初期処理・後処理

1)BeforeAndAfter

* 各テスト前・後に呼び出されるメソッドを定義できる
 => 説明よりサンプルとログの出力結果みた方が分かりやすい

1)サンプル

package  com.example

import org.scalatest.funspec.AnyFunSpec
import org.scalatest.BeforeAndAfter

class HelloWorldTest extends AnyFunSpec with BeforeAndAfter {
  before {
    println("Before") // ★ログ
  }

  after {
    println("After") // ★ログ
  }

  describe("Hello World Test") {
    describe("Normal") {
      it("Set name=Mike") {
        println("Test1") // ☆ログ
        val result = HelloWorld.sayHello("Mike")
        assert(result == "Hello, Mike!!")
      }
    }
    describe("Abnormal") {
      it("should produce IllegalArgumentException when name is empty") {
        assertThrows[IllegalArgumentException] {
          println("Test2") // ☆ログ
          HelloWorld.sayHello("")
        }
      }
    }
  }
}

出力結果

Before
Test1 << 各テスト前に「Before」、後に「After」にCallする
After
Before                                                       
Test2 << 各テスト前に「Before」、後に「After」にCallする                                                        
After

2)BeforeAndAfterAll

* 1テスト前・後に呼び出されるメソッドを定義できる

サンプル

package  com.example

import org.scalatest.funspec.AnyFunSpec
import org.scalatest.BeforeAndAfterAll

class HelloWorldTest extends AnyFunSpec with BeforeAndAfterAll {
  override def beforeAll(): Unit = {
    println("beforeAll")
  }

  override def afterAll(): Unit = {
    println("afterAll")
  }
  // 略(上と同じ)
}

出力結果

beforeAll
Test1
Test2
afterAll

【2】一時的テストを停止する

* Ignore (無視)を指定する

1)テストメソッドを停止する

  describe("Hello World Test") {
    describe("Normal") {
      ignore("Set name=Mike") { // ignore() を指定

出力結果

[info] Hello World Test                                      
[info]   Normal                                              
[info]   - Set name=Mike !!! IGNORED !!!  << ★

2)テストクラスを全体を停止する

import org.scalatest.Ignore

@Ignore // ★ここ
class HelloWorldTest extends AnyFunSpec with BeforeAndAfterAll {

【3】Public以外のメソッドのテスト

1)private メソッドをテストする

* 「PrivateMethodTester」「PrivateMethod」を使う

https://www.scalatest.org/user_guide/using_PrivateMethodTester

1)サンプル

import org.scalatest.funspec.AnyFunSpec
import org.scalatest.PrivateMethodTester

class TargetClass {
  // private method
  private def sayHi(name: String): String = {
    require(name.nonEmpty)
    s"Hi, ${name}!!!"
  }
}

class HelloTest extends AnyFunSpec with PrivateMethodTester {
  describe("For Hello World Test") {
    val privateMethod = PrivateMethod[String]('sayHi)
    val targetClass = new TargetClass()
    it ("For private method test") {
      assert(targetClass.invokePrivate(privateMethod("Mike")) == "Hi, Mike!!!")
    }
  }
}

2)protected メソッドをテストする

* UnitTest用にクラスを設けて、そのクラスに継承して
 そのクラスのメソッドから、親クラスを呼び出すだけ
 => Scala以外のプログラムのUnitTestでも使える、、、

Hello.scala

class Hello {
  // ★ protected ★
  protected def sayHello(input: String): String = s"${input}, world"
}

HelloTest.scala

import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.prop.TableDrivenPropertyChecks._

class HelloTest extends AnyFunSuite {
  // ★注目★
  class HelloForUnitTest extends Hello {
    override def sayHello(input: String): String = {
      super.sayHello(input)
    }
  }

  test("Your test case") {
    val target = new HelloForUnitTest
    val data = Table(
      ("input", "expected"),
      ("Hello", "Hello, world"),
      ("Hi", "Hi, world")
    )
    forAll(data) { (input, expected) =>
      val result = target.sayHello(input)
      assert(result == expected)
    }
  }
}

【4】Table で色々なパターンの値を用意する

* Table を使って、テストを行うと可読性があがる
 => 以下のサンプル参照

1)サンプル

Hello.scala

object  Hello {
  def sayHello(input: String): String = s"${input}, world"
}

HelloTest.scala

import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.prop.TableDrivenPropertyChecks._

class HelloTest extends AnyFunSuite {
  test("Your test case") {
    // ★注目★
    val data = Table(
      ("input", "expected"),
      ("Hello", "Hello, world"),
      ("Hi", "Hi, world")
    )
    forAll(data) { (input, expected) =>
      val result = Hello.sayHello(input)
      assert(result == expected)
    }
  }
}

【5】例外処理をテストする

* 以下が参考になる

https://www.baeldung.com/scala/scalatest-test-exceptions

エラーを発生させるメソッド

object Hello {
  def explodingMethod(): Unit = {
    throw new RuntimeException("Hello, exception!!")
  }
}

1)発生した例外を確認する

package  com.example

import org.scalatest.funspec.AnyFunSpec

class HelloWorldTest extends AnyFunSpec {
    describe("Abnormal") {
      it("should produce RuntimeException when name is empty") {
        // ★注目★
        assertThrows[RuntimeException] {
         Hello.explodingMethod()
        }
      }
    }
  }
}

2)発生した例外+メッセージを確認する

import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers.{a, convertToAnyShouldWrapper}

class HelloTest extends AnyFunSuite  {
  test("Hello World Test") {
    val exception = intercept[Exception](Hello.explodingMethod())
    exception shouldBe a[Exception]
    exception shouldBe a[RuntimeException]
    exception.getMessage shouldBe "Hello, exception!!"
  }
}

関連記事

Scala ~ テスティングフレームワーク
https://dk521123.hatenablog.com/entry/2024/06/07/183708
ScalaTest ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2023/03/27/001306
ScalaTest ~ 基本編 ~
https://dk521123.hatenablog.com/entry/2023/03/28/003906
ScalaTest ~ with ScalaCheck ~
https://dk521123.hatenablog.com/entry/2023/03/29/000014
ScalaTest ~ with Mockito ~
https://dk521123.hatenablog.com/entry/2023/03/31/002830
ScalaTest ~ with Coverage ~
https://dk521123.hatenablog.com/entry/2023/08/07/222945
specs2 ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2024/06/08/122708
specs2 ~ 基本編 ~
https://dk521123.hatenablog.com/entry/2024/06/09/221005
Scala ~ 環境構築編 ~
https://dk521123.hatenablog.com/entry/2023/03/10/193805
Scala ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2023/03/12/184331
Scala ~ 基本編 / 繰り返し ~
https://dk521123.hatenablog.com/entry/2023/01/24/000000
Scala ~ 基本編 / Option型 ~
https://dk521123.hatenablog.com/entry/2023/03/09/000000
Scala ~ 基本編 / メソッド ~
https://dk521123.hatenablog.com/entry/2023/03/03/000000
Scala ~ 基本編 / クラス ~
https://dk521123.hatenablog.com/entry/2023/03/14/000857
Scala ~ 基本編 / コレクション ~
https://dk521123.hatenablog.com/entry/2023/03/13/000345
Scala ~ 基本編 / 日付・日時 ~
https://dk521123.hatenablog.com/entry/2023/03/08/000000
Scala ~ 基本編 / 正規表現
https://dk521123.hatenablog.com/entry/2023/03/18/034704
Scala ~ 基本編 / ジェネリック
https://dk521123.hatenablog.com/entry/2023/03/21/003817
ScalaEnum
https://dk521123.hatenablog.com/entry/2023/01/05/000000
Scala ~ ファイル名・パスの扱い ~
https://dk521123.hatenablog.com/entry/2023/03/11/000000
Scala ~ ファイルハンドリング ~
https://dk521123.hatenablog.com/entry/2023/01/03/000000
ScalaYAML
https://dk521123.hatenablog.com/entry/2023/03/16/012034
ScalaJDBC / DB接続 ~
https://dk521123.hatenablog.com/entry/2023/03/26/000950
ScalaAWS SDK
https://dk521123.hatenablog.com/entry/2023/03/24/211033
SBT ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2023/03/22/000000
SBT ~ 基本編 / build.sbt ~
https://dk521123.hatenablog.com/entry/2023/01/27/000000
SBT ~ 基本編 / sbtコマンド ~
https://dk521123.hatenablog.com/entry/2023/01/26/000000
SBT ~ sbtプラグイン
https://dk521123.hatenablog.com/entry/2023/01/25/000000
JavaでEmail ~ JavaMail / Text ~
https://dk521123.hatenablog.com/entry/2016/07/16/222422
JavaでEmail ~ JavaMail / 添付ファイル ~
https://dk521123.hatenablog.com/entry/2016/07/17/023459
JavaでEmail ~ SMTP認証 ~
https://dk521123.hatenablog.com/entry/2016/11/07/215251
JavaでEmail ~ SMTP認証 / DIGEST-MD5
https://dk521123.hatenablog.com/entry/2016/12/07/222229
JavaでEmail ~ JavaMail / TLS
https://dk521123.hatenablog.com/entry/2017/05/03/163219
JavaでEmail ~ JavaMail / Return-Path・Errors-To ~
https://dk521123.hatenablog.com/entry/2017/05/07/000344
Amazon SES ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2017/04/28/234103
Amazon S3 ~ Boto3編 ~
https://dk521123.hatenablog.com/entry/2019/10/21/230004
Amazon S3 ~ Boto3でファイル存在チェック ~
https://dk521123.hatenablog.com/entry/2022/02/26/182526
AWS Glue ~ Scalaでの実装 ~
https://dk521123.hatenablog.com/entry/2023/03/17/000000
AWS Glue ~ ローカル環境を作成する / Glue v3.0版 ~
https://dk521123.hatenablog.com/entry/2022/01/31/165650
LocalStack ~ ローカルで疑似AWSを作成する ~
https://dk521123.hatenablog.com/entry/2019/12/14/010524
LocalStack ~ ローカルで疑似Lambda/S3/DynamoDBを作成する ~
https://dk521123.hatenablog.com/entry/2019/12/16/231149
SparkからSnowflakeへの接続について考える
https://dk521123.hatenablog.com/entry/2023/03/19/013833
Mockito ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2014/07/18/233904
Mockito ~ 基本編 ~
https://dk521123.hatenablog.com/entry/2014/07/19/121409