테스트 환경을 구성한다고 가정해 보자.
테스트 환경에서 필요한 값들을 추상화해 놓은 TestEnvironment 가 있다.
trait TestEnvironment {
val envName: String
def readEnvironmentProperties: Map[String, String]
}
아래 코드는 TestEnvironment 의 값들을 이용해 테스트를 실행하는 클래스다.
class TestExecutor { env: TestEnvironment => // TestEnvironment 의 구현체를 직접 주입해 주어야 한다.
def execute(tests: List[Test]): Boolean = {
println(s"Executing test with $envName environment")
tests.forall(_.execute(readEnvironmentProperties))
}
}
위 코드에서 env: TestEnvironment 를 다른 곳으로 옮겨보자.
// TestEnvironment 를 관리하는 Component
trait TestEnvironmentComponent {
val env: TestEnvironment // env 는 변수로 사용될 수 있다. 어디선가 override 함으로써 갈아낄 수 있다.
trait TestEnvironment {
val envName: String
def readEnvironmentProperties: Map[String, String]
}
}
TestExecutor 가 TestExecutorComponent 내부로 들어갔다.
TestExecutor 는 더이상 TestEnvironment 에 의존하지 않는다.
대신, TestEnvironment 를 env 라는 변수로 사용할 수 있게 되었다.
trait TestExecutorComponent { this: TestEnvironmentComponent =>
val testExecutor: TestExecutor
class TestExecutor {
def execute(tests: List[Test]): Boolean = {
println(s"Executing test with ${env.envName} environment")
tests.forall(_.execute(env.readEnvironmentProperties))
}
}
}
Component 들간의 의존 관계를 self-type 인 this 로 정할 수 있다.
만약 TestExecutorComponent 가 의존하는 컴포넌트에 LoggingComponent 가 추가된다면
this: TestEnvironmentComponent with LoggingComponent
처럼 할 수 있다.