단위 테스트와 도메인 로직

 

개요

우아한 테크코스 과정을 진행하면서 단위 테스트와 도메인 로직에 대하여 공부하게 되었습니다.

이는 미션에서 요구 사항으로 추가되기도 하였는데요, 단위 테스트와 도메인 로직의 개념과 필요성에 대하여 알게 되었습니다.

 

단위 테스트

 

단위 테스트는 앱의 작은 단위를 사용하고, 코드의 나머지 부분에서 격리하며 예상대로 동작하는지 확인하는 기능을 합니다.

각 기능 단위가 예상되로 수행되고, 앱 전체에서 오류가 발생하지 않는지 확인하는 것이 주 목표입니다.

메서드나 동작 하나를 검증하며, 빠르고 작아야할 필요성이 있습니다.

 

유닛 테스트?

 

유닛 테스트는 단위와는 반대되는 개념을 가지고 있습니다.

작은 단위로 테스트하는 단위 테스트와는 반대로 모든 함수와 메소드에 대한 테스트 케이스를 작성하는 절차입니다.

 

단위 테스트의 필요성

단위 테스트는 소프트웨어 개발에서 중요한 요소이며, 아래와 같은 장점을 가집니다.

 

- 코드의 신뢰성을 확보하여 각 부분의 코드가 예상대로 동작하는지 확인할 수 있다.

- 버그를 식별하고 수정이 용이하며 수정하는 과정을 통해서 안정성을 향상시킬 수 있다.

- 코드 리팩터링 과정에서 단위 테스트를 통해 기존 코드를 변경하고, 기존 기능이 지속적으로 작동하는지 확인할 수 있다.

- 테스트 가능한 코드는 종속성이 적고 모듈화되어 있으며, 다른 프로젝트에서도 쉽게 재사용할 수 있다.

 

 

단위 테스트의 규칙

단위 테스트의 규칙을 준수할 경우 좋은 단위 테스트를 작성할 수 있으며, 유지보수에 용이한 코드를 작성할 수 있습니다.

코드 변경이나 수정에 대하여 안정적이며, 아래와 같은 규칙이 있습니다.

 

- 각 테스트는 서로 독립적이어야 하며, 다른 테스트의 실패에 영향을 받지 않아야 한다.

- 테스트는 어떤 환경에서도 반복 가능해야 하며, 같은 입력에 대하여 항상 같은 결과를 반환해야 한다.

- 테스트는 자체적으로 검증이 가능해야하며, 예상 결과와 실제 결과를 자동으로 검증할 수 있어야 한다.

- 테스트는 하나의 단일 기능 또는 모듈을 테스트해야 하며, 각 테스트 케이스에는 하나의 목적이 있어야 한다.

- 테스트는 가독성이 있어야하며, 작성자는 테스트의 의도를 명확히 드러내야 한다.

- 테스트는 빠르게 실행되어야 하며, 개발 주기 내에서 빠른 피드백을 제공해야 한다.

- 테스트는 변경에 유연하고 확장 가능해야 하며, 코드 변경으로 인해 쉽게 수정되거나 확장이 가능해야 한다.

 

 

도메인 로직

도메인 로직이란 데이터가 어떻게 생성, 저장, 변경될 수 있는지를 결정하는

실제 현실의 비즈니스 규칙을 인코딩하는 프로그램의 일부입니다.

 

소프트웨어 공학에서 도메인은 소프트웨어가 풀고자하는 현실 세상의 문제를 의미하며,

소프트웨어를 개발하는 대상 영역이라고 이해할 수 있습니다.

 

도메인 로직을 활용하면 관심사를 명확하게 분리할 수 있다는 장점을 가지게 됩니다.

 

 

리뷰 

이번 미션을 진행하면서, 아래와 같은 도메인 로직의 단위 테스트 코드를 작성해보았습니다.

해당 미션의 코드를 업무 영역에 적용할 경우 중요하다고 생각한 부분에 대한 테스트를 진행하였습니다.

 

제가 이번 미션에서 생각한 핵심적인 비즈니스 로직은 계산에 대한 정확성 테스트와 정확환 반환값을 반환하는가였습니다.

    @Test
    fun `수익률 계산 정확성 테스트`() {
        val purchasePrice = 5000
        val totalProceeds = 5000
        val managerResult = lottoManager
            .calculateResult(purchasePrice, totalProceeds)
        val trueResult = "100.0"
        assertThat(managerResult).isEqualTo(trueResult)
    }

    @Test
    fun `lotto 번호에 보너스 넘버 포함 테스트`() {
        val gameResult = GameResult.THIRD
        val winningLotto = WinningLotto(setOf(1, 2, 3, 4, 5, 6), 7)
        val lotto = Lotto(listOf(1, 2, 3, 4, 5, 7))
        val managerResult = lottoManager
            .checkBonusResult(gameResult, winningLotto, lotto)
        assertThat(managerResult).isEqualTo(GameResult.SECOND)
    }

    @Test
    fun `lotto 번호에 보너스 넘버 비포함 테스트`() {
        val gameResult = GameResult.THIRD
        val winningLotto = WinningLotto(setOf(1, 2, 3, 4, 5, 6), 7)
        val lotto = Lotto(listOf(1, 2, 3, 4, 5, 8))
        val managerResult = lottoManager
            .checkBonusResult(gameResult, winningLotto, lotto)
        assertThat(managerResult).isEqualTo(GameResult.THIRD)
    }

    @Test
    fun `lotto 번호에 보너스 넘버는 포함되지만 matchNumber이 5가 아닌 경우 테스트`() {
        val gameResult = GameResult.FOURTH
        val winningLotto = WinningLotto(setOf(1, 2, 3, 4, 5, 6), 7)
        val lotto = Lotto(listOf(1, 2, 3, 4, 8, 7))
        val managerResult = lottoManager
            .checkBonusResult(gameResult, winningLotto, lotto)
        assertThat(managerResult).isEqualTo(GameResult.FOURTH)
    }

    @Test
    fun `matchCount에 따른 올바른 GameResult 반환 테스트`() {
        val matchCount = 4
        val managerResult = lottoManager.getMathResult(matchCount)
        assertThat(managerResult).isEqualTo(GameResult.FOURTH)
    }

 

테스트 코드를 작성하는 것은 항상 어렵습니다..

그 중에서 도메인 로직 분리와 단위 테스트에 맞춰서 코드를 분리하는 것은 상당히 머리가 복잡합니다.

기능 구현 중심의 코드가 아닌, 관심사 분리와 의존성을 낮추는 코드에 대하여 항상 생각해야겠습니다.