
→ TDD는 켄트 벡(Kent Back) 이 고안한 소프트웨어 개발 방법론으로, “테스트 주도 개발” 이라는 이름 그대로 테스트를 먼저 작성하고 이를 기반으로 코드를 작성하는 방식이다. TDD는 다음과 같은 반복적인 과정을 통해 이루어진다.
core:data:source / LocalUserDataSourceTest
package com.bw.data.local.source
import com.bw.data.local.dao.FakeUserDao
import com.bw.core.data.local.db.UserDatabase
import com.bw.core.data.local.entity.UserEntity
import com.bw.core.data.local.source.LocalUserDataSource
import junit.framework.TestCase
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
class LocalUserDataSourceTest {
private lateinit var dataSource: LocalUserDataSource
@Before
fun setup(){
// Room DB 대신 Fake DAO 사용
val fakeDao = FakeUserDao()
dataSource = LocalUserDataSource(fakeDao)
}
@Test
fun insert_and_get_user() = runTest {
val user = UserEntity(user_phone = "01012345678", user_name = "홍길동")
dataSource.insertUser(user)
val count = dataSource.getUserByPhoneAndName("01012345678", "홍길동").first()
TestCase.assertEquals(1, count)
val users = dataSource.getAllUsers()
// ✅ 성공 로그 출력
println("✅ LocalUserDataSourceTest success → user not found, count: $count / users : ${users.toList()}")
}
}
core:data:source / UserRepositoryImplTest
package com.bw.data.local.repository
import com.bw.core.data.repository.UserRepositoryImpl
import com.bw.core.domain.model.User
import com.bw.data.local.source.FakeLocalUserDataSource
import junit.framework.TestCase.assertEquals
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.runTest
import org.junit.Test
class UserRepositoryImplTest {
private val repository = UserRepositoryImpl(FakeLocalUserDataSource())
@Test
fun insert_and_fetch_user_success() = runTest {
val user = User(phone = "01012345678", name = "홍길동")
repository.insertUser(user)
val count = repository.getUserByPhoneAndName("01012345678", "홍길동").first()
assertEquals(1, count)
val users = repository.getAllUsers()
// ✅ 성공 로그 출력
println("✅ UserRepositoryImplTest success → user not found, count: $count / users : ${users.toList()}")
}
}
core:domain:usecase / CheckUserUseCaseTest
package com.bw.domain.usecase
import com.bw.core.domain.model.User
import com.bw.core.domain.usecase.CheckUserUseCase
import com.bw.core.testing.repository.FakeUserRepository
import junit.framework.TestCase.assertEquals
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
class CheckUserUseCaseTest {
private lateinit var fakeUserRepository: FakeUserRepository
private lateinit var checkUserUseCase: CheckUserUseCase
@Before
fun setup(){
fakeUserRepository = FakeUserRepository()
checkUserUseCase = CheckUserUseCase(fakeUserRepository)
}
// ✅ 성공 로그 출력
@Test
fun `when user exists then return count 1`() = runTest { // User가 존재할 경우 -> 1을 반환
// given (준비)
val user = User(phone = "01012345678", name = "홍길동")
fakeUserRepository.insertUser(user)
// when (실행)
val count = checkUserUseCase("01012345678", "홍길동").first()
// then (검증)
assertEquals(1, count)
println("✅ CheckUserUseCaseTest success → user count: $count")
}
@Test
fun `when user does not exist then return count 0`() = runTest { // User가 존재하지 않을 경우 -> 0을 반환
// given
fakeUserRepository.insertUser(User(phone = "01099999999", name = "이순신"))
// when
val count = checkUserUseCase("01012345678", "홍길동").first()
// then
assertEquals(0, count)
println("✅ CheckUserUseCaseTest success → user not found, count: $count")
}
}
feature:login:ui / LoginViewModelTest
package com.bw.feature.login.ui
import app.cash.turbine.test
import com.bw.core.domain.model.User
import com.bw.core.domain.usecase.CheckUserUseCase
import com.bw.core.domain.usecase.InsertUserUseCase
import com.bw.core.testing.repository.FakeUserRepository
import com.bw.core.ui.event.UiEvent
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
class LoginViewModelTest {
private lateinit var viewModel: LoginViewModel
private lateinit var fakeRepository: FakeUserRepository
@Before
fun setup() {
fakeRepository = FakeUserRepository()
val fakeCheckUserUseCase = CheckUserUseCase(fakeRepository)
val fakeInsertUserUseCase = InsertUserUseCase(fakeRepository)
viewModel = LoginViewModel(fakeInsertUserUseCase,fakeCheckUserUseCase)
}
@Test
fun `전화번호 미입력시 토스트 이벤트 발생`() = runTest {
viewModel.updatePhone("")
viewModel.updateName("홍길동")
viewModel.uiEvent.test {
viewModel.signIn()
val event = awaitItem() as UiEvent.ShowToast
assertEquals("전화번호를 입력해주세요.", event.message)
println("💡 event.message = $event")
println("✅ 전화번호 미입력 테스트 성공")
}
}
@Test
fun `전화번호 자릿수 오류시 토스트 이벤트 발생`() = runTest {
viewModel.updatePhone("0101234")
viewModel.updateName("홍길동")
viewModel.uiEvent.test {
viewModel.signIn()
val event = awaitItem() as UiEvent.ShowToast
assertEquals("휴대폰 자릿수를 확인해주세요.", event.message)
println("💡 event.message = $event")
println("✅ 전화번호 자릿수 오류 테스트 성공")
}
}
@Test
fun `이름 미입력시 토스트 이벤트 발생`() = runTest {
viewModel.updatePhone("01012345678")
viewModel.updateName("")
viewModel.uiEvent.test {
viewModel.signIn()
val event = awaitItem() as UiEvent.ShowToast
assertEquals("이름을 입력해주세요.", event.message)
println("💡 event.message = $event")
println("✅ 이름 미입력 테스트 성공")
}
}
@Test
fun `존재하지 않는 유저면 다이얼로그 이벤트 발생`() = runTest {
viewModel.updatePhone("01000000000")
viewModel.updateName("없는사람")
viewModel.uiEvent.test {
viewModel.signIn()
val event = awaitItem() as UiEvent.ShowDialog
assertEquals("로그인 실패", event.title)
println("💡 event.message = $event")
println("✅ 존재하지 않는 유저 테스트 성공: ${event.message}")
}
}
@Test
fun `존재하는 유저면 Navigate 이벤트 발생`() = runTest {
fakeRepository.insertUser(User(0,"01012345678", "홍길동"))
viewModel.updatePhone("01012345678")
viewModel.updateName("홍길동")
viewModel.uiEvent.test {
viewModel.signIn()
val event = awaitItem()
assert(event is UiEvent.Navigate)
println("💡 event.message = $event")
println("✅ 로그인 성공 테스트 성공 (Navigate 이벤트 발생)")
}
}
}