본문 바로가기

Android/TDD(Test Driven Development, 테스트 주도 개발)

Unit Test(유닛 테스트) 팁 5가지 - TDD(테스트 주도 개발)

반응형

static method 제한적 사용

테스트 대상이 되는 함수 내부에서 static method를 사용한다면, 해당 부분은 Mocking 할 수 없습니다.

따라서 위 테스트와 마찬가지로 실행 환경에 영향을 받는 테스트를 작성하게 됩니다.

singleton 및 object 제한적 사용

싱글톤 패턴이나 코틀린의 object를 사용하는 경우, 여러 테스트를 한 번에 테스트할 때 변수를 공유하기 때문에 문제가 발생할 수 있습니다.

Object 개념과 Data Structure 개념 구분하여 사용

Object는 메소드 실행을 위한 클래스를 말하며, Data Structure는 데이터 저장을 위한 클래스를 말합니다. 코틀린에서는 Data Structure를 위해 Data Class를 사용하므로 둘을 구분하기 더욱 용이합니다. 두 개념을 혼용하여 사용할 경우 코드가 금방 복잡해지므로 각 개념을 별도로 사용하도록 합시다.

테스트 함수의 구조는 AAA

테스트 함수의 구조는 AAA(Arrange - Act - Assert) 규칙을 따르는 편이 테스트를 이해하기 좋습니다. argumentCaptor 등을 선언하는 선언부가 arrange, 테스트하고자 하는 함수를 실행하는 부분이 act, 테스트 결과를 검증하는 부분이 assert입니다.

@Test
fun 기능_상태_기댓값() {
  // Arrange
  val argumentCaptor = ArgumentCaptor.forClass(String::class.java)
  // Act
  systemUnderTest.someFunction()
  // Assert
  verify(testUnit.testFunction(argumentCaptor.capture()))
  assertEquals(expected, argumentCaptor.value)
}

assert 구문은 하나만 존재하는 것이 좋지만, 위와 같이 두 속성이 긴밀하게 연관된 경우에는 assert 문을 추가해도 괜찮습니다.

@RunWith, @Mock annotation으로 mock 초기화 생략

이전에는 다음과 같이 Mockito.mock() 함수를 통해 mock을 초기화하는 과정이 필요했습니다.

class UpdateUsernameUseCaseSyncTest {

  lateinit var systemUnderTest: UpdateUsernameUseCaseSync

  lateinit var updateUsernameHttpEndpointSync: UpdateUsernameHttpEndpointSync
  lateinit var usersCache: UsersCache
  lateinit var eventBusPoster: EventBusPoster

  @Before
  fun setUp() {
    updateUsernameHttpEndpointSync = Mockito.mock(UpdateUsernameHttpEndpointSync::class.java)
    usersCache = Mockito.mock(UsersCache::class.java)
    eventBusPoster = Mockito.mock(EventBusPoster::class.java)

    systemUnderTest = UpdateUsernameUseCaseSync(
      updateUsernameHttpEndpointSync,
      usersCache,
      eventBusPoster,
    )
  }

  // ...

}

테스트 클래스에 @RunWith(MockitoJUnitRunner::class)를 적용하고, mock 초기화가 필요한 변수에 @Mock annotation을 적용하면 이러한 초기화 과정을 생략할 수 있습니다. 단, lateinit 속성은 뗄 수 없으니 이 점 참고해주세요.

@RunWith(MockitoJUnitRunner::class)
class UpdateUsernameUseCaseSyncTest {

  lateinit var systemUnderTest: UpdateUsernameUseCaseSync

  @Mock
  lateinit var updateUsernameHttpEndpointSync: UpdateUsernameHttpEndpointSync

  @Mock
  lateinit var usersCache: UsersCache

  @Mock
  lateinit var eventBusPoster: EventBusPoster

  @Before
  fun setUp() {
    systemUnderTest = UpdateUsernameUseCaseSync(
      updateUsernameHttpEndpointSync,
      usersCache,
      eventBusPoster,
    )
    setSuccessCase()
  }

  // ...

}
반응형