1. 概要

Scalaプログラミング言語には、型間の依存関係を処理する多くの方法があります。 いわゆる「セルフタイプ」アノテーションを使用すると、特性とミックスインの概念を使用して依存関係を宣言できます。

このチュートリアルでは、自己型アノテーションを使用して小さなテスト実行フレームワークを構築します。

2. セルフタイプアノテーションの定義

Scalaの自己型アノテーションは、2つの型間の依存関係を表現する方法です。 タイプAがタイプBに依存している場合、Bのインスタンスを提供せずにAのオブジェクトをインスタンス化することはできません。

テストを実行するためのフレームワークを設定したいとします。 実行環境を表すタイプが必要です。

trait TestEnvironment { 
  val envName: String 
  def readEnvironmentProperties: Map[String, String] 
}

そして、テストを実行するものであるタイプ:

class TestExecutor { env: TestEnvironment => 
  def execute(tests: List[Test]): Boolean = { 
    println(s"Executing test with $envName environment")
    tests.forall(_.execute(readEnvironmentProperties))
  } 
}

テストを実行するには、エグゼキュータにTestEnvironmentのインスタンスが必要です。 Scalaでは、自己型アノテーションを使用してこの制約を表現できます。 表記env:TestEnvironment => は、タイプ TestEnvironment からの依存関係を宣言し、envと呼びます。

自己型アノテーションは、入力のセットが与えられた関数のように見え、新しい型を返します。 この例では、返されるタイプはTestExecutorクラスです。

3. 自己型注釈の解決

TestExecutor クラスは、TestEnvironment特性との依存関係を満たすタイプとのみ混合できます。 Windowsオペレーティングシステムのテスト環境を実装しましょう。

trait WindowsTestEnvironment extends TestEnvironment { 
  override val envName: String = "Windows" 
  override def readEnvironmentProperties: Map[String, String] = 
    System.getenv().asScala.toMap 
}

JUnit 5テストエグゼキュータを実装する場合は、テスト環境も提供する必要があります。

class JUnit5TestExecutor extends TestExecutor with WindowsTestEnvironment {}

テスト環境とテストエグゼキュータを混在させない場合、コンパイラは依存関係が満たされていないことを警告します。

illegal inheritance; 
[error] self-type JUnit5TestExecutor does not conform to TestExecutor's selftype TestExecutor with TestEnvironment 
[error] class JUnit5TestExecutor extends TestExecutor {} 
[error]                                  ^

新しい型の定義中に宣言された依存関係を解決するか、オブジェクトのインスタンス化中にも修正できます。

val windowsGeneralExecutor: TestExecutor = new TestExecutor with WindowsTestEnvironment

この例では、依存関係をenvと呼びます。 ただし、thisリファレンスを使用してそのプロパティにアクセスできます。 クラスTestExecutorのメソッドexecuteでは、クラスで直接宣言されているプロパティenvNameおよびreadEnvironmentPropertiesにアクセスします。

def execute(tests: List[Test]): Boolean = { 
  println(s"Executing test with $envName environment") 
  tests.forall(_.execute(readEnvironmentProperties)) 
}

ただし、プロパティはTestEnvironment特性の一部です。 これは自己型アノテーションのもう1つの特徴であり、この参照の範囲を広げます

すでに定義されているプロパティのシャドウイングを回避するために、セルフタイプアノテーションに付けられた名前を使用できます。 たとえば、クラスTestを実行の前後にログで装飾したいとします。

class TestWithLogging(name: String, assertion: Map[String, String] => Boolean) extends Test(name, assertion) { 
  inner: Test => 
    override def execute(env: Map[String, String]): Boolean = { 
      println("Before the test") 
      val result = inner.execute(env) println("After the test") result 
    } 
}

この場合、名前を依存関係に関連付ける必要があります。 inner 変数を参照すると、含まれているテストのexecuteメソッドにアクセスできます。

4. 結論

このチュートリアルでは、Scalaの自己型アノテーションと、それを適用して型間の依存関係を管理する方法について学びました。

いつものように、このチュートリアルのソースコードはGitHubから入手できます。