sooleeandtomas

안드로이드 코틀린 아키텍쳐와 테스트 (1) 본문

코틀린/kotlin

안드로이드 코틀린 아키텍쳐와 테스트 (1)

sooleeandtomas 2022. 7. 13. 00:29

## 아키텍처가 필요한 이유

➖ 유지보수

➖ 개발자와의 협업 (컨벤션이 있기 때문에 이해하기 쉽다)

➖ 테스트에 용이 (의존성이 레이어 별로 분리되어 있다. "안드로이드에 의존하는 코드"는 단위 테스트가 힘들어진다)

➖ 책임의 격리로 인해 개발 시 설계 및 구현의 용이함

➖ 멀티모듈로 나누기에 적절하다. (빌드 속도 향상, TDD 사이클 속도 향상, 가독성 향상, 재사용성 향상...등의 장점이 많다)

➖ 변경에 유연하게 대응


## 단위 테스트

➖ 기능을 개발하는 당장에는 필요 없을 수 있지만, 테스트 코드가 없다면 추후 코드를 수정해야 하는 개발자가 기존 요구 사항을 지키면서 코드를 작성하기 힘들다.

➖ 협업 시 테스트 코드를 보고 요구사항을 빠르게 캐치할 수 있다.

 

## 안드로이드의 단위 테스트

➖ 도메인 로직은 domain 모듈로 분리 (또는 feature 모듈로 분리한다.)

   ✖️ 무엇이 도메인 로직인가 고민하기 (UI 로직과 분리)

   ✖️ 객체지향적으로 생각하기 

➖ JUnit5 or JUnit4 (안드로이드에서는 공식적으로 JUnit4만 지원한다.)

 

프로젝트를 생성하면 총 3개의 Source Set이 생성됩니다.

[main, androidTest, test]

main : 프로덕션 코드

androidTest: UI 테스트 (안드로이드에 의존적인 테스트 espresso, UI automator)

test: 주로 단위 테스트, 통합 테스트도 가능 (안드로이드에 의존성이 없는 테스트 Robolectric, HTTP 요청)

(integrationTest 라는 것도 있음)

 

## 안드로이드의 UI 테스트

➖ espresso

 white box 테스트 내부 구현을 알아야 작성 가능

 black box 테스트 내부 구현을 몰라도 작성 가능

 프로덕션 코드를 참조하여 테스트 가능

 


## 안드로이드의 단위 test 예제

app/java/com.soolee.androidtest1(test)/CounterTest.kt

package com.soolee.androidtest1

import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test

class CounterTest {
    private lateinit var counter: Counter

    @Before
    fun setUp() {
        counter = Counter()
    }

    @Test
    fun 숫자는_0부터_시작한다() {
        //when
        var actual: String = counter.value.toString();

        //then
        assertThat(actual).isEqualTo("0");
    }

    @Test
    fun 숫자가_1_증가한다() {
        //when
        counter.increment()
        var actual: String = counter.value.toString();

        //then
        assertThat(actual).isEqualTo("1");
    }

    @Test
    fun 숫자가_1_감소한다() {
        //when
        counter.decrement()
        var actual: String = counter.value.toString();

        //then
        assertThat(actual).isEqualTo("-1");
    }
}

 

app/java/com.soolee.androidtest1/MainAcitivity.kt

package com.soolee.androidtest1

import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class MainActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?){
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val buttonUp = findViewById<Button>(R.id.buttonUp)
        val buttonDown = findViewById<Button>(R.id.buttonDown)
        val text = findViewById<TextView>(R.id.textView)
        val counter = Counter()

        buttonUp.setOnClickListener{
            counter.increment();
        }
        buttonDown.setOnClickListener{
            counter.decrement()
            text.text = counter.value.toString()
        }
    }
}

 

app/java/com.soolee.androidtest1/Counter.kt

package com.soolee.androidtest1

public class Counter {
    var value: Int = 0

    fun increment(){
        value += 1
    }

    fun decrement(){
        value -= 1
    }
}

 


 

## 안드로이드의 UI test 예제

 

app/java/com.soolee.androidtest1(androidTest)/MainAcitivityTest.kt

class MainActivityTest {
    @get:Rule
    var activityScenarioRule = ActivityScenarioRule(MainActivity::class.java)

    //사용자가 피연산자 0~9 버튼을 누르면 화면에 해당 숫자가 화면에 보여야 한다.
    @Test
    fun 버튼_0을_누르면_화면에_0이_보여야_한다() {
        //when
        onView(withId(R.id.button0)).perform(click())

        //then
        onView(withId(R.id.textView)).check(matches(withText("0")))
    }
}

 

app/java/com.soolee.androidtest1/MainAcitivity.kt

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val button0 = findViewById<Button>(R.id.button0)
        val text = findViewById<TextView>(R.id.textView)

        button0.setOnClickListener {
            text.text = "0"
        }
 }
Comments