GitHub - junhyung0927/Unit-Test-Study: 단위 테스트 스터디 학습 저장소

들어가기 앞서

앞서 챕터에서 목에 관련된 이야기를 많이 했었다. 목은 테스트 대상 시스템과 의존성 간의 상호 작용을 모방하고 검사 하는 데 도움이 되는 테스트 대역이다. 목은 비관리 의존성에만 적용해야 한다.

<aside> 💡 비관리 의존성

해당 의존성과의 상호 작용을 외부에서 볼 수 있는 것을 뜻한다. 예를 들어, SMTP 서버와 메시지 버스 등이 있다. 둘 다 다른 애플리케이션에서 볼 수 있는 부작용을 발생시킨다.

</aside>

만약, 다른 곳에 목을 사용하게 되면 깨지기 쉬운 테스트(리팩터링 내성이 없는 테스트)가 된다.

9장에서는 목에 대해 리팩터링 내성과 회귀 방지를 최대화해서 최대 가치의 통합 테스트를 개발하는 데 도움이 되는 지침을 알아본다.

목의 가치를 극대화하기

앞서 진행했던 CRM 시스템 예제 프로젝트를 보면서 목의 가치를 극대화하는 방법을 살펴보자.

// 8장 UserController
data class UserController(
    private val _database: Database,
    private val _messageBus: MessageBus
) {

    fun changeEmail(userId: Int, newEmail: String): String {

        val userData = _database.getUserById(userId)
        val user = UserFactory.create(userData)

				...
				
				// 사용자 도메인 이벤트 전달
        **EventDispatcher().dispatch(user.domainEvents)**

        return "OK"
    }

}

8장의 UserController는 EventDispatcherdispatch 메서드를 사용해서 user의 도메인 이벤트를 두 가지 책임으로 분리하여 지원 로깅을 테스트 한다.

또한, User 클래스는 changeEmail 메서드의 시작과 끝에서 직접 로거 인스턴스를 사용하고 있다. 진단 로깅은 개발자만을 위한 것이기 때문에, 굳이 테스트할 필요도 없고, 도메인 모델 테스트에 포함되지 않아도 된다. 하지만 가능하다면 User나 다른 도메인 클래스에서 진단 로깅을 사용하진 말자.

// 8장 User 클래스
data class User(
    val userId: Int,
    var email: String,
    var type: UserType,
    val isEmailConfirmed: Boolean,
    val logger: Logger,
    val domainLogger: DomainLogger,
    val domainEvents: List<EmailChangeEvent>
) {
			...

    fun changeEmail(newEmail: String, company: Company) {
				//진단 로깅
        **logger.info(
            "changing email for user $userId to $newEmail"
        )
			
				...**

        if (type != newType) {
            val delta = if (newType == UserType.Employee) {
                1
            } else {
                -1
            }
            company.changeNumberOfEmployees(delta)
						// 지원 로깅
            **addDomainEvent(UserTypeChangedEvent(userId, type, newType))**
        }

        email = newEmail
        type = newType
				// 지원 로깅
        **addDomainEvent(EmailChangeEvent(userId, newEmail))**

				// 진단 로깅
        **logger.info(
            "$email is changed for user $userId"
        )**
    }

    private fun addDomainEvent(userTypeChangedEvent: EmailChangeEvent) {
        // messageBus.sendEmailChangedMessage로 변환
    }

    private fun addDomainEvent(userTypeChangedEvent: UserTypeChangedEvent) {
        // domainLogger.userTypeHasChanged로 변환
    }
}

이제 진단 로깅을 제거한 UserController와 User 클래스를 살펴보자.

// 9장 UserController
data class UserController(
    private val _database: Database,
    private val _messageBus: MessageBus,
    **private val _domainLogger: IDomainLogger,**
) {

    fun changeEmail(userId: Int, newEmail: String): String {

        val userData = _database.getUserById(userId)
        val user = UserFactory.create(userData)

        val error = user.canChangeEmail()
        error?.let { return it }

        val companyData = _database.getCompany()
        val company = CompanyFactory.create(companyData)

        user.changeEmail(newEmail, company)

        _database.saveCompany(company)
        _database.saveUser(user)

        **EventDispatcher().dispatch(user.domainEvents)**

        return "OK"
    }

}