RIEN😚
이상한 나라의 개발자
RIEN😚
전체 방문자
오늘
어제
  • 분류 전체보기 (125)
    • Algorithm (68)
      • 알고리즘 (0)
      • Baekjoon (8)
      • 프로그래머스 (55)
      • HackerRank (5)
    • Android (30)
      • Project (1)
      • Error (2)
      • Studio (1)
      • Android (26)
    • Kotlin (6)
    • CS (4)
      • 네트워크 (2)
      • 데이터베이스 (2)
    • Front End (5)
      • React (1)
      • VUE (3)
      • Project (0)
      • 기타 (1)
    • 기록 (11)
      • 회고록 (6)
      • TIL (5)

블로그 메뉴

  • Github🔥
  • 포트폴리오🌹

공지사항

인기 글

티스토리

250x250
반응형
hELLO · Designed By 정상우.
RIEN😚

이상한 나라의 개발자

Android/Android

[Android:Codelab] Testing Basic-2

2023. 1. 20. 20:01
728x90
반응형

👇🏻 참고문서

 

Advanced Android in Kotlin 05.1: Testing Basics  |  Android Developers

Learn the basics of testing your Android Kotlin apps. In this codelab you’ll learn to run tests, write basic tests, work with AndroidX Test, as well as test ViewModel and LiveData.

developer.android.com

안드로이드 독학하기 시리즈! 테스트입니다.

이전 Testing Basic-1에 이은 2로 계속 공부하는 중입니다. 😌

 

4. 첫 테스트 코드를 작성해보자.

코드랩에서 제공해주는 프로젝트를 열어 실행하고 왼쪽 drawer에 Statistics에 들어가면 수행중인 todo와 완료한 todo의 비율을 볼 수 있습니다.

이번에 이 비율을 계산해주는 getActiveAndCompleteState 메서드를 테스트하는 코드를 작성해보겠습니다.👍🏻

🌹 해당 메서드는 todoapp/statistics/StatisticsUtils.kt 내에 위치하고 있습니다.

 

4.1 테스트 클래스 만들기

테스트 클래스를 직접 생성해도 되지만, 코드랩과 같이 자동으로 테스트 클래스를 만들어보겠습니다.

1. 테스트하고자 하는 메서드 signature 위에서 마우스 오른쪽 버튼을 클릭하면 나오는 리스트에서 Generate를 클릭해줍니다.

 

2. 그러면 아래와 같은 popup이 뜨는데, 이 중 Test를 클릭해주면 됩니다.

 

3. 생성할 클래스의 이름과 기타 설정을 체크해줍니다.

- 현재 에제에서는 가장 기본적인 테스크 클래스를 만들것이기 때문에 아무것도 체크하지 않고 넘어가도록 하겠습니다.

🌹 기본적으로 추천해주는 이름은 StatisticsUtilsKtTest일 것입니다. 테스트 클래스 이름에 Kt가 들어가는건 흠.. 권장하지 않으므로 Kt를 제외한 StatisticsUtilsTest로 명명해줍시다.

 

4. 마지막으로 해당 클래스가 위치할 디렉토리를 설정해주면 끝입니다.
📌 Testing Basic-1에서 살펴보았듯이, instrumented test는 androidTest에 local test는 test 디렉토리에 위치해야 합니다.

- 테스트 하고자 하는 메서드는 Android 프레임워크 상관없이 단지 계산만 하므로 local test입니다.

- 따라서 test 디렉토리를 선택해줍니다.

 

4.2 테스트 function 만들기

테스트 코드를 작성할 클래스를 만들었으니, 이번에는 그 테스트 코드가 동작할 테스트 함수(function)을 만들어보겠습니다.

@Test
fun getActiveAndCompletedStates_noCompleted_returnHundredZero() {
    // Create an active task ( the false makes this active )
    val tasks = listOf<Task>(
        Task("title", "desc", isCompleted = false)
    )
    // Call function
    val result = getActiveAndCompletedStats(tasks = tasks)
    // Check the result
    assertEquals(result.completedTasksPercent, 0f)
    assertEquals(result.activeTasksPercent, 100f)
}

- 테스트 함수 앞에는 @Test 어노테이션을 붙여주어야 합니다.

- assertion을 이용해 결과값이 기대했던 값과 동일한지 확인할 수 있습니다.

 

테스트 코드를 실행해보면

테스트가 성공적으로 완료된 것을 볼 수 있습니다.

 

4.3 추가적으로 알아두면 좋은 사항 - 1

테스트 함수를 보면 함수명이 정~말 긴 것을 알 수 있습니다. 코드랩에서는 테스트 함수명을 작성할 때 아래와 같은 규칙을 정해주고 있어요.

📌 'subjectUnderTest'_'actionOrInput'_'resultState'

앞에서 정의했던 getActiveAndCompletedStates_noCompleted_returnHundredZero 를 예로 들어보면

  • 테스트하고자 하는 메서드 또는 클래스 - getActiveAndCompletedStates
  • action 또는 input - noCompleted
  • 기대하는 결과값 - returnsHundredZero

 

4.4 추가적으로 알아두면 좋은 사항 - 2

테스트 코드를 작성할 때 Given, When, Then 이 순서를 생각하면서 구조적으로 짜보도록 하자!

1. Given

- 테스트에 필요한 객체를 정의하고 설정을 추가하는 단계입니다.

- 위 예제에서는 테스트할 리스트를 정의하는 코드가 이 Given 단계입니다.

2. When

- 실제로 테스트하고자 하는 동작을 수행하는 단계입니다.

- 위 예제에서는 테스트하고자 하는 getActiveAndCompletedStates 메서드를 호출하는 부분입니다.

3. Then

- When에서 수행한 결과를 확인하는 단계입니다.

- 하나의 assert function이 아닌 여러 assert function이 호출될 수 있습니다.🤗

 

4.5 Hamcrest dependency

Hamcrest라 불리는 프레임워크는 사람(여기서는 개발자)이 보다 테스트 코드를 읽기 쉽도록 해주는 assertion 프레임워크입니다.

 

- JUnit에서 아래와 같이 assert function을 적었다면,

assertEquals(result.completedTasksPercent, 0f)

- Hamcrest는 이를 보다 읽기 쉽게 아래와 같이 작성할 수 있습니다.

assertThat(result.completedTasksPercent, `is`(0f))

 

Hamcrest를 사용하기 위해서는 별도의 dependency를 추가해주어야 합니다.

// 저는 1.1 버전을 사용했습니다.
testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"

그럼 앞에서 작성한 테스트 함수를 Hamcrest를 사용한 버전으로 변경해보겠습니다.

@Test
fun getActiveAndCompletedStates_noCompleted_returnHundredZero() {
    // Create an active task ( the false makes this active )
    val tasks = listOf<Task>(
        Task("title", "desc", isCompleted = false)
    )
    // Call function
    val result = getActiveAndCompletedStats(tasks = tasks)
    // Check the result
    assertThat(result.completedTasksPercent, `is`(0f))
    assertThat(result.activeTasksPercent, `is`(100f))
}
🍿 여기서 잠깐! 왜 is가 아니라 `is`로 적는 것일까?
is는 객체에 사용할 수 있는 예약어이기 때문에, Hamcrest에서 구별하여 사용하기 위해 `is`로 쓰는것입니다.👏🏻

 

5. 다른 테스트 코드도 작성해보자!

이전에 테스트를 해보았던 getActiveAndCompletedStates 메서드의 내부를 보면 아래와 같습니다.

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {
    val totalTasks = tasks!!.size
    val numberOfActiveTasks = tasks.count { it.isActive }
    return StatsResult(
        activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
        completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
    )
}

만약 completed task가 1개있고 active task가 없는 경우라면 StatsResult 객체의 activeTasksPercent는 0f, completedTasksPercent는 100f일 것입니다.

 

하지만! 만약 empty 리스트 또는 null 리스트를 가지고 이전 테스트를 수행하면 에러가 발생하는 것을 알 수 있습니다. ( 아마 실제 개발자는 이런 상황에 0이 반환되도록 구현할 것입니다. )

이런식으로 테스트를 통해 문제점을 파악하고 코드를 수정해나가는 방식을 Test Driven Development(TTD)라고 합니다.

 

5.1 TDD(Test Driven Development)

🌹 TDD란?
기능을 제공하는 코드를 먼저 작성하는 대신에, 테스트 코드를 먼저 작성하는 개발방식입니다. 실제로 기능을 제공하는 코드는 테스트를 완전히 통과한 이후에 작성합니다.
  1. Given / When / Then 구조로 테스트 코드를 작성합니다.
  2. Convention에 맞게 테스트 이름을 작성해줍니다.
  3. 테스트 실행 후, 실패하는 경우가 있는지 확인합니다.
  4. 실패하는 경우에 대응하기 위한 최소한의 코드를 추가 작성합니다.
  5. 모든 테스트에서 1-4를 반복합니다.

예를 들어, 빈 배열인 경우의 테스트 코드를 작성했을 때,
@Test
fun getActiveAndCompletedStates_empty_returnZero() {
    // Given
    val task = emptyList<Task>()

    // When
    val result = getActiveAndCompletedStats(tasks = task)

    // Then
    assertEquals(result.completedTasksPercent, 0f)
    assertEquals(result.activeTasksPercent, 0f)
}

테스트에 실패하는 것을 확인하고나서, 이 때의 버그를 수정하기 위한 최소한의 코드를 추가적으로 작성한다.

 

5.2 Fix the bug

위에서 확인한 버그(에러)를 tasks가 null일 때 0f를 반환할 수 있도록 수정해보자

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {
    return if (tasks.isNullOrEmpty()) {
        StatsResult(0f, 0f)
    } else {
        val totalTasks = tasks.size
        val numberOfActiveTasks = tasks.count { it.isActive }
        StatsResult(
            activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
            completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
        )
    }
}

이렇게 수정하고나서 다시 테스트를 돌려보면, 모두 통과하는 것을 알 수 있습니다.

 

5.3 TDD의 장점

  • 새로운 기능은 항상 그와 관련된 테스트 코드를 가집니다.

             - 테스트코드가 곧 해당 코드들이 어떤 기능을 수행하는지에 대한 문서가 될 수 있습니다.

  • 코드가 정확한 결과를 반환하는지 확인하고 이미 봤었던 동일한 에러가 발생하는 것을 방지할 수 있습니다.

 

👉🏻 Next:

동일한 코드랩을 계속 진행!

다음은 ViewModel과 LiveData 테스트와 관련된 내용이 이어집니다.🤗

반응형

'Android > Android' 카테고리의 다른 글

[Android:Codelab] Testing Basic-4  (0) 2023.01.24
[Android:Codelab] Testing Basic-3  (0) 2023.01.21
[Android:Codelab] Testing Basic-1  (0) 2023.01.20
[Android/Fragment] 1.1 Fragment 추가 예제 코드 까보기  (0) 2022.07.11
[Android/Fragments] 1. Fragment Manager  (0) 2022.07.11
    'Android/Android' 카테고리의 다른 글
    • [Android:Codelab] Testing Basic-4
    • [Android:Codelab] Testing Basic-3
    • [Android:Codelab] Testing Basic-1
    • [Android/Fragment] 1.1 Fragment 추가 예제 코드 까보기
    RIEN😚
    RIEN😚
    안드로이드 / 코틀린 독학으로 취업하자!

    티스토리툴바