안드로이드 테스트 코드를 배워보자 (2) Room Unit Test
https://sh1mj1-log.tistory.com/175
이전 글에서 이어집니다.
Room Unit Test - ViewModel, LiveData 등 사용
Room DB 는 안드로이드 Jetpack Components 입니다. 그러므로 안드로이드 종속성이 필요합니다.
안드로이드 개발자 문서 - Room 지속성 라이브러리는 SQLite에 추상화 계층을 제공하여 SQLite를 완벽히 활용하면서 더 견고한 데이터베이스 액세스를 가능하게 합니다.
gradle 에 Room 종속성 추가
dependencies {
def room_version = "2.5.0"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
...
// To use Kotlin annotation processing tool (kapt)
kapt "androidx.room:room-compiler:$room_version"
// 코루틴 기능을 사용하기 위해서는 ktx artifact 를 Room 추가
implementation("androidx.room:room-ktx:$room_version")
}
만약 kapt 를 추가하지 않았다면 `build.gradle` 상단에 아래 `plugins` 도 추가해 주어야 합니다.
plugins {
...
id 'kotlin-kapt'
}
추가해주어야 합니다.
즉, Room android Test 를 하려면 AndroidTest 디렉토리에 테스트 코드를 만들어야 하죠. 코드는 이 안드로이드 개발자 문서의 코드를 참조하여 변형했습니다. (안드로이드 개발자 문서 코드 깃허브)
그 전에 먼저 테스트할 데이터 클래스, 데이터베이스 클래스, Room DAO 클래스를 만들어야 합니다.
`User` 데이터 클래스
@Entity(tableName = "users")
data class User (
@PrimaryKey
@ColumnInfo(name = "userid")
val id: String = UUID.randomUUID().toString(),
@ColumnInfo(name = "username")
val userName: String
)
`UserDatabase` 데이터베이스 클래스
@Database(entities = [User::class], version = 1)
abstract class UserDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
@Volatile
private var INSTANCE: UserDatabase? = null
fun getInstance(context: Context): UserDatabase =
INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
}
private fun buildDatabase(context: Context): UserDatabase =
Room.databaseBuilder(
context.applicationContext,
UserDatabase::class.java, "Sample.db"
)
.build()
}
}
`UserDao` dao 클래스
@Dao
interface UserDao {
@Query("SELECT * FROM Users")
suspend fun getAllUsers(): List<User>
@Query("SELECT * FROM Users WHERE userid = :id")
suspend fun getUserById(id: String): User
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUsers(user: List<User>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: User)
@Query("DELETE FROM Users")
suspend fun deleteAllUsers()
}
이렇게 간단한 `User ` 관련 dao, database 까지 만들었습니다.
Test 작성하기
이제 Test 클래스를 만들어 봅시다. 먼저 코드부터 작성하고 봅시다.
`MovieDaoTest`
@RunWith(AndroidJUnit4::class)
class RoomDaoTest {
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
private lateinit var dao: UserDao
private lateinit var database: UserDatabase
@Before
fun setUp() {
database = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
UserDatabase::class.java
).build()
dao = database.userDao()
}
@After
fun tearDown() {
database.close()
}
@Test
fun saveUsersTest() = runBlocking {
val users = listOf(
User("1","username1"),
User("2","username2"),
User("3","username3")
)
dao.insertUsers(users)
val allUsers = dao.getAllUsers()
assertThat(allUsers).isEqualTo(users)
}
@Test
fun deleteUsersTest() = runBlocking {
val users = listOf(
User("1","username1"),
User("2","username2"),
User("3","username3")
)
dao.insertUsers(users)
dao.deleteAllUsers()
val usersResult = dao.getAllUsers()
assertThat(usersResult).isEmpty()
}
}
Room 테스트 코드 프로세스는 아래와 같습니다.
- `@Before` 에서 Room 데이터베이스와 DAO 를 미리 만들어 둔다.
- Room DAO 메소드들을 테스트한 후에 `@After` 에서 데이터베이스 커넥션을 `close()` 해준다.
그런데 위 코드에서 낯선 코드들이 많습니다. 하나씩 차례대로 보시죠.
@RunWith(AndroidJUnit4::class) ??
공식 문서에서는 이렇게 설명하고 있습니다. (공식 문서)
`AndroidJUnitRunner` 클래스는 Espresso 및 UI Automator 테스트 프레임워크를 사용하는 것을 비롯하여 Android 기기에서 JUnit 3 또는 JUnit 4 스타일 테스트 클래스를 실행할 수 있는 JUnit 테스트 실행기입니다.
이 테스트 실행기는 테스트 패키지와 테스트 대상 앱을 기기에 로드하여 테스트를 실행하고 테스트 결과를 보고하는 역할을 합니다. 이 클래스는 JUnit 3 테스트만 지원하는 `InstrumentationTestRunner` 클래스를 대체합니다.
runBlocking ??
`runBlocking` 은 코틀린의 코루틴을 사용하는 동안 suspend 함수를 호출하는 코드 블록을 지원하는 함수입니다.
`runBlocking` 은 주로 테스트나 메인 함수처럼 비동기 코드를 호출하기 어려운 상황에서 간단하게 사용됩니다.
여기서 Room 에 접근하는 비동기 작업(`saveUsersTest` 함수 내에서 `dao.insertUsers(users)` 을 수행하고 있죠!
즉, 테스트 코드에서 코루틴을 사용하는 경우에 유용하며, 특히 비동기 작업을 동기적으로 따로 처리해야 하는 테스트에서 자주 사용됩니다.
`runBlocking` 함수는 이전에 코루틴에 대해 알아볼 때 잠시 사용하고 간단히 설명한 적이 있습니다. ([코틀린] 그래서 코루틴이 뭔데?)
테스트를 실제로 돌려보면 아래처럼 결과가 나옵니다.
이전에 (test) 패키지에서 Unit Test 를 했을 때의 결과와 조금 다르게 나오는 것을 볼 수 있습니다. 그리고 앞에서 말했듯이 Room 관련 테스트를 진행할 때는 안드로이드 종속성이 필요하기 때문에 예뮬레이터가 실행되는 것도 볼 수 있습니다.
Reference Link
https://youngest-programming.tistory.com/492
https://developer.android.com/training/data-storage/room?hl=ko
https://github.com/android/architecture-components-samples/tree/main/BasicRxJavaSampleKotlin
https://developer.android.com/training/testing/junit-runner?hl=ko