티라미수 코딩생활

[Android] App Test 간단하게 구현해보기! (feat. TDD) 본문

Programming/Android

[Android] App Test 간단하게 구현해보기! (feat. TDD)

Aosta 2022. 11. 9. 17:20

간단한 Local Test 작성해보기

2022.11.09 - [Programming/Android] - [Android] App Test 시작해보기

 

[Android] App Test 시작해보기

내가 앱 테스트를 공부하게 된 이유 모든 공부가 그렇지만 안드로이드를 개발하고 공부해오면서 매번 느끼는 것은 공부할 게 참 많다라는 것이었다. 그러다보니 공부 할 것도 당장 필요한 것만

tiramisu-code.tistory.com

CodeLab : https://developer.android.com/codelabs/advanced-android-kotlin-training-testing-basics#0

 

지난 글에서 처음으로 App Test를 처음 공부하고 글을 남겼는데, 이번엔 공부한 것을 바탕으로 간단하게 테스트 작성을 해보려고 한다. 작성하게 될 코드는 Android Developer 사이트에서 제공하는 CodeLab 기반으로 작성했다.

그리고 TDD 라는 개념도 간단하게 알아보고 적용해보고자 한다.

 

작성할 테스트 내용

학교 어느 반에서 시험을 본 학생들이 답안을 제출했고 점수가 나왔다.

신입 개발자 Wonny는 학생들의 점수 데이터를 모아 해당 시험의 평균과 가장 높은 점수는 몇인지 코드를 작성했다.

코드를 작성은 했는데 테스트 없이 앱을 배포하기엔 언제 터질지 모르는 폭탄을 세상에 던져놓은 느낌이 든 Wonny는

이 코드를 검증하기 위해 테스트 코드를 작성하기로 한다.

 

internal fun getAverageAndWinner(students: List<StudentInfo>): StudentResult {
    var total = 0
    var winner: StudentInfo? = null

    for (student in students) {
        total += student.score
        winner?.let {
            if (it.score < student.score)
                winner = student
        } ?: run {
            winner = student
        }
    }

    return StudentResult(
        average = (total / students.size).toDouble(),
        winner = winner!!.name,
        winnerScore = winner!!.score
    )
    
}

data class StudentResult(
    val average: Double,
    val winner: String,
    val winnerScore: Int,
)

data class StudentInfo(
    val name: String,
    val score: Int,
)

테스트 전 작성한 내용은 위와 같다.

이름과 점수로 구성된 데이터(StudentInfo)가 들어오면 평균과 가장 시험을 잘 본 학생의 정도를 담고 있는 데이터(StudentResult)를 도출한다. 이제 이 함수를 테스트 해보고자 한다.

 

테스트 코드 작성 방법 (JUnit4 기준)

함수나 클래스 우클릭 > Generate.. > test..

해당 창에서 어떤 Library 로 테스트를 작성할 것인지 결정할 수 있다.

위 창에서 onCreate 와 onDestroy 와 비슷한 기능을 하는 @Before 와 @After 등을 미리 만들 수 있지만

이번엔 간단하게만 구현해보기 위해 아무것도 만들지 않고 생성해본다! 

Class name 은 최초 CalcUtilsKtTest 으로 이름이 나오는데 원하는 이름으로 커스텀 할 수도 있다. 

그대로 쓴다면 Kt 문자는 빼서 CalcUtilsTest 으로 작성해주자. 

 

경로를 선택해야한다!

OK 버튼을 누르게 되면 위처럼 경로를 지정하는 창이 뜬다.

여기서 unitTest 와 androidTest를 선택 할 수 있는데 보통 다음과 같은 기준으로 지정한다.

  • unitTest : JVM 만 활용해서 테스트를 진행하고 함수를 테스트할 때 사용
  • androidTest : android 기능을 사용하거나 UI 테스트를 진행해야할 때 사용 (기기나 에뮬 사용)

그러면 테스트 클래스가 생성되는데 만들 때 아무것도 설정하지 않고 만들었지 때문에 비어있는 클래스가 생성된다.

이제 실제 테스트 코드들을 작성해나갈 차례다.

여기서 TDD(Test Driven Development) 을 적용해서 작성해본다!

 

갑자기 TDD(Test Driven Development)는 뭘까?

Test Driven Development or TDD is a school of programming thought that says instead of writing your feature code first, you write your tests first. Then you write your feature code with the goal of passing your tests.

코드랩에서 정의한 TDD 는 '기능 코드를 작성하기 전에 테스트 작성을 먼저 한다. 이를 통해 별도 테스트 없이 기능 코드를 완성한다' 이다. 그리고 TDD 를 통해 테스트를 작성하는 것은 아래의 순서를 통해서 작성한다.

  1. 규칙을 지킨 이름을 정하고 Given, When, Then 구조를 사용해서 테스트를 작성한다.
  2. 테스트 실패를 확인한다.
  3. 통과 될만한 간단한 코드를 작성한다.
  4. 이를 반복한다!

아래 그림은 제가 직접 그린게 아니라 무려 공식 사이트에 있는겁니다..

 

출처 : Android Developers

 

 

테스트 코드 이름 지어주기

개발은 창조의 영역이라고 하기 때문에 테스트 코드 작성도 자유이긴 하지만,

위에서 말한 규칙을 지킨 네이밍은 어떤 테스트인지 알기 쉽게 짓는 것이다. 

 

subjectUnderTest_actionOrInput_resultState

 

풀이하면 '테스트할내용_입력할값이나행동_결과예상값' 이런 식으로 이름을 지으면 된다.

 

테스트 코드 작성해보기

import org.hamcrest.MatcherAssert.assertThat
// build.gradle -> testImplementation "org.hamcrest:hamcrest-all:1.3"
import org.junit.Test

class CalcUtilsTest {

    @Test
    fun getAverageAndWinner_winnerScore_returnsHundred() {
        // Given 3 student's scores
        val studentInfo = listOf(
            StudentInfo("Kyu", 100),
            StudentInfo("Wonny", 80),
            StudentInfo("Jo", 60)
        )

        // When the winner's score(winnerScore) is computed
        val result = getAverageAndWinner(studentInfo)

        // Then the result is 100
        assertThat(result.winnerScore, `is`(100))
    }

}

테스트 코드를 작성할 때 세 파트로 나눠서 작성한다. 파트를 간단하게 비유하자면

  • Given : 내가 테스트할 예시 값을 주고
  • When : 이 값으로 테스트 한다면?
  • Then : 이러한 결과값이 나올 것이다!

내가 작성한 코드는

  • Given : 세명의 학생시험 정보를 가지고
  • When : getAverageAndWinner 함수를 실행한다면
  • Then : 결과(가장 높은 점수) 는 100이 나올거야!

위 코드를 실행해보면

작성한 테스트 코드의 결과

초록색 체크가 테스트 성공을 나타내준다! 하지만 테스트 코드는 다양하게 작성해봐야 하기 때문에 추가적으로 몇가지 테스트를 더 해보기로 한다. 두 개의 테스트를 추가해서 아래의 코드로 다시 실행(run) 해본다.

 

class CalcUtilsTest {
	
    // 테스트1 : 가장 높은 점수를 잘 도출하는가?
    @Test
    fun getAverageAndWinner_winnerScore_returnsHundred() {
        // Given 3 student's scores
        val studentInfo = listOf(
            StudentInfo("Kyu", 100),
            StudentInfo("Wonny", 80),
            StudentInfo("Jo", 60)
        )

        // When the winner's score(winnerScore) is computed
        val result = getAverageAndWinner(studentInfo)

        // Then the result is 100
        assertThat(result.winnerScore, `is`(100))
    }

 	// 테스트2 : 평균 점수를 잘 도출하는가?
    @Test
    fun getAverageAndWinner_average_returnsEighty() {
        // Given 3 student's scores
        val studentInfo = listOf(
            StudentInfo("Kyu", 100),
            StudentInfo("Wonny", 80),
            StudentInfo("Jo", 60)
        )

        // When the average is computed
        val result = getAverageAndWinner(studentInfo)

        // Then the result is 80
        assertThat(result.average, `is`(80.0))
    }

 	// 테스트3 : 빈 값이 들어왔을 때는 오류가 나지 않는가?
    @Test
    fun getAverageAndWinner_empty_returnEmpty() {
        // Given
        val studentInfo : List<StudentInfo> = arrayListOf()

        // When
        val result = getAverageAndWinner(studentInfo)

        // Then
        assertThat(result, `is`(StudentResult(0.0, "", 0)))
    }


}

 

하지만 결과는 3개의 테스트 중 1개의 실패와 2개의 통과가 나왔다.

빈 값이 들어왔을 때 오류를 getAverageAndWinner_empty_returnEmpty() 테스트 함수를 통해 잡아냈다.

발생한 오류 수정해보기

그리고 해당 오류를 참고해서 작성한 코드를 수정한다.

Parameter 로 빈 값이 들어왔을 때는 임의의 빈 값으로 리턴을 해주기 때문에 오류는 발생하지 않을 것이다.

internal fun getAverageAndWinner(students: List<StudentInfo>): StudentResult {
    var total = 0
    var winner: StudentInfo? = null

	// 빈값이 들어왔을 때는 임의이 빈 값을 만들어줘야 오류가 나지 않네?
    if (students.isEmpty())
        return StudentResult(0.0, "", 0)

    for (student in students) {
        total += student.score
        winner?.let {
            if (it.score < student.score)
                winner = student
        } ?: run {
            winner = student
        }
    }

    return StudentResult(
        average = (total / students.size).toDouble(),
        winner = winner!!.name,
        winnerScore = winner!!.score
    )

}

 

수정한 코드는 테스트를 잘 통과했다.

 

다시 한번 테스트를 실행했더니 이번엔 실패 없이 모두 통과된 것을 확인할 수 있었다.

지금까지 정말 간단한 예를 통해 테스트 코드를 작성해봤다.

솔직히 다른 분들의 블로그들을 보면서

'와 테스트 작성하는게 되게 복잡하고 어려워보이는데(Hilt 처음 봤을때 급의 좌절감)' 라고 생각하고 지레 겁을 먹었는데,

코드랩을 복습하면서 정리를 했더니 조금이나마 개념이 잡히는 것 같다.

Comments