[Android] Hilt Test와 함께 알아보는 안드로이드 간단 TDD!

윤호이·2023년 11월 5일
0

Test

목록 보기
1/13
post-thumbnail

서론

TDD에 대해 들어 보신적 있나요?

TDD(Test-Driven-Development) 테스트 주도 개발은
테스트 코드를 먼저짜고
테스트를 통과하도록 개발하는 개발 방법론입니다

오늘은 Hilt Test와 함께 TDD가 어떻게 진행되는지
간단한 예제를 통해 제대로 보여드리겠습니다!

TDD 귀찮은데 왜 해야되죠?!?!?

맞죠. TDD는 고사하고 테스트 코드짜는거 조차 귀찮을 때가 많습니다.
굳이 테스트 코드안짜도 앱은 잘 돌아가잖아요?

단기적으로 봤을땐 테스트 코드를 안짜는게 개발 시간도 단축하고
이득 일 수도 있습니다.

하지만! 그럼에도 불구하고 TDD를 채택하는 이유는
귀찮음을 압도하는 장점들을 가지고 있기 때문입니다.

  • 에러가 발생 할 때 일일히 모듈을 디버깅하는데 시간이 많이든다면?

  • 간단한 기능을 추가했는데 그로 인해 주변기능을 전부 테스트 해야 한다면?

  • 코드 리팩토링을 해야 하는데 잘못 건드려서 에러날까봐 두렵다면?

Wow! TDD를 도입해보세요! 이러한 고민들을 줄이는데 도움을 줄 수 있습니다.

미리짜둔 테스트 코드 실행 한방으로 모든 걱정 Clear!

간단한 예제를 통해 TDD를 맛보시죠.

Hilt Test 설정

App Gradle

id ("dagger.hilt.android.plugin")
kotlin("kapt")

testImplementation("com.google.dagger:hilt-android-testing:2.48")
kaptTest("com.google.dagger:hilt-android-compiler:2.47")
androidTestImplementation("com.google.dagger:hilt-android-testing:2.48")
kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.47")
implementation ("com.google.dagger:hilt-android:2.48")
kapt ("com.google.dagger:hilt-compiler:2.47")

Project Gradle

buildscript {
    dependencies {
        classpath ("com.google.dagger:hilt-android-gradle-plugin:2.48.1")
    }
}

필요한 의존성들을 gradle에 추가해줍시다.

TestRunner 설정

class HiltTestRunner : AndroidJUnitRunner() {
    override fun newApplication(
        cl: ClassLoader?,
        className: String?,
        context: Context?
    ): Application {
        return super.newApplication(cl, HiltTestApplication::class.java.name, context)
    }
}

AndroidTest폴더에
테스트에 사용할 HiltTestRunner를 만들어줍시다.

그후 App Gradle의 defaultConfig에서 만들어둔
TestRunner를 지정해줍시다.

 defaultConfig {
        ...

        testInstrumentationRunner = "com.example.hilttest.HiltTestRunner"
        ...
    }

Test에 사용할 Fake를 만들기

interface FakeCalculator {
    fun add(a: Int, b: Int) : Int
}
class FakeCalculatorImpl @Inject constructor() : FakeCalculator {
    override fun add(a: Int, b: Int) = a + b
}

테스트에 사용할 Fake 계산기를 만들어 두겠습니다.

Test용 Hilt모듈 만들기

@Module
@InstallIn(SingletonComponent::class)
abstract class CalculatorModule {
   //Empty...
}
@Module
@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [CalculatorModule::class]
)
abstract class FakeCalculatorModule {

    @Binds
    abstract fun bindsCalculator(calculatorImpl: FakeCalculatorImpl): FakeCalculator
}

의존성을 주입해줄 테스트 모듈과 임시로 추후에 사용할 진짜 모듈을 만들도록합시다.
테스트에서는 replaces = [CalculatorModule::class] 이부분을 통해
FakeCalculatorModule이 사용됩니다.

HiltApplication 등록하기

@HiltAndroidApp
class Application : Application() {
}
//Manifest
android:name=".Application"

Hilt Application을 App폴더에 만들고 Manifest에 등록해줍시다.

Fake가 잘되는지 확인하기

@HiltAndroidTest
class CalculatorTest {

    @get:Rule
    var hiltRule = HiltAndroidRule(this)

    @Inject
    lateinit var fakeCalculator: FakeCalculator

    @Before
    fun setUp() {
        hiltRule.inject()
    }

    @Test
    fun `숫자를_더한다`() {
        val a = 1
        val b = 2

        val result = fakeCalculator.add(a, b)

        assertEquals(3, result)
    }
}

@HiltAndroidTest 을 통해 의존성을 주입받는 클래스라는 것을 명시해줍니다.

HiltAndroidRule로 의존성 주입을 받을 수 있게 해줍니다.

Fake가 잘 돌아가는 것이 확인 되었으니 이 테스트를 통과하도록
진짜 객체를 만들어야겠죠?

테스트를 통과하도록 개발하기

interface Calculator {
    fun add(a: Int, b: Int) : Int
}

class CalculatorImpl @Inject constructor() : Calculator {
    override fun add(a: Int, b: Int) = a + b
}

@Module
@InstallIn(SingletonComponent::class)
abstract class CalculatorModule {
    @Binds
    abstract fun bindsCalculator(calculatorImpl: CalculatorImpl) : Calculator
}

작성해둔 테스트 모듈을 기반으로 진짜 객체를 만들어 주고
테스트 모듈이 아닌 진짜 모듈을 사용하기 위해 Fake Module을 지우거나 변경해줍시다.

Fake를 진짜 객체로 바꿔 테스트 하기

    @Inject
    lateinit var calculator: Calculator
    
    
    @Test
    fun `숫자를_더한다`() {
        val a = 1
        val b = 2

        val result = calculator.add(a, b)

        assertEquals(3, result)
    }

아까 작성해둔 Fake만 진짜 객체로 바꿔줬습니다.

만약 제가 테스트 의도 대로 개발했다면 테스트가 통과하겠죠?

무사히 통과했네요!

테스트 의도대로 개발하지 않는다면?

class CalculatorImpl @Inject constructor() : Calculator {
    override fun add(a: Int, b: Int) = a * b
}

저는 힙한 사람이기 때문에 더하라 하면 곱해버립니다.

테스트 코드 작성자의 의도와는 완전 다르죠?

1 + 2 면 3이 나와야하는데 1 * 2로 해서 2가 나왔네요...

이와 같이 테스트 코드에 맞게 개발하는 것이 TDD입니다.

하고 싶은 말

리팩토링 할 때 진짜 객체의 핵심 로직을 바꿔버린다면
테스트가 실패합니다.

그와 반대로 테스트를 통과한다면 의도에 맞게 잘 개발 중이라는 뜻이니
확신을 가지고 개발을 진행할 수 있게 됩니다.

자신이 잘 개발중인지 또는 건드리면 안되는걸 건드렸는지
확인할 수 있는 좋은 수단이 생겼으니 결론적으로 생산성이 올라갑니다.

이 글을 읽으시는 독자 분들도 TDD는 아니더라도
테스트 코드를 습관화 하여 조금 더 유연한 개발을 하셨으면 좋겠습니다.

참조 및 전체 소스 코드

https://github.com/lyh990517/Android_TDD_Example_Using_HiltTest

https://velog.io/@dhtjdals77/TDD-%EC%A0%95%EC%9D%98-%EB%B0%8F-%EC%9E%A5%EB%8B%A8%EC%A0%90

https://medium.com/hongbeomi-dev/mad-%EC%8A%A4%ED%82%AC-hilt-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%AA%A8%EB%B2%94-%EC%82%AC%EB%A1%80-%EB%B2%88%EC%97%AD-32ea0556fb99

profile
열정은 내 삶의 방식, 꾸준함은 내 삶의 증명

0개의 댓글