안녕하세요, 오늘은 KMM에서 로컬디비로 많이 쓰이는 SqlDelight를 Unit Testing 해보겠습니다.
우선적으로 Unit Test 환경은 인텔리제이에서 구성하겠습니다. 현재 안드로이드 스튜디오 최신 릴리즈인 Chipmunk에서 androidTest sourceSet에 대한 오류가 있어 인텔리제이로 유닛테스트 환경을 구성하면 정상적으로 작동합니다.
https://github.com/corona-warn-app/cwa-app-android/issues/5181
https://issuetracker.google.com/issues/233788419
먼저 gradle을 설정해줍니다.
// shared/build.gradle.kts
kotlin {
sourceSets {
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
val androidTest by getting {
dependencies {
implementation("com.squareup.sqldelight:sqlite-driver:1.5.3")
}
}
val iosTest by getting
}
}
android단에서 AndroidSqliteDriver는 안드로이드 컨텍스트가 필요하니 안드로이드 종속적이지 않은 JdbcSqliteDriver를 사용하기 위해 해당 라이브러리를 주입시켜줍니다.
다음으로 각 sourceSets
에 가서 공통 테스트 sqlitedriver를 만들어줍니다.
// commonTest/TestSqlDirverFactory.kt
expect class TestSqlDriverFactory() {
fun createDriver(): SqlDriver
}
// androidTest/TestSqlDriverFactory.kt
// Foreign Key를 사용하지 않을 경우
actual class TestSqlDriverFactory {
actual fun createDriver(): SqlDriver {
return JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
.also(AppDatabase.Schema::create)
}
}
// Foreign Key를 사용할 경우
actual class TestSqlDriverFactory {
actual fun createDriver(): SqlDriver {
val foreignProps = Properties().apply { put("foreign_keys", "true") }
val dbName = JdbcSqliteDriver.IN_MEMORY
return JdbcSqliteDriver(dbName, foreignProps).also(AppDatabase.Schema::create)
}
}
// iosTest/TestSqlDriverFactory.kt
// Foreign Key를 사용할 경우
actual class TestSqlDriverFactory {
actual fun createDriver(): SqlDriver {
return NativeSqliteDriver(
configuration = DatabaseConfiguration(
name = "unit_test.db",
version = AppDatabase.Schema.version,
inMemory = true,
create = { connection ->
wrapConnection(connection) {
AppDatabase.Schema.create(it)
}
}
)
).also {
it.execute(null, "PRAGMA foreign_keys=ON", 0)
}
}
}
// Foreign Key를 사용하지 않을 경우
actual class TestSqlDriverFactory {
actual fun createDriver(): SqlDriver {
return NativeSqliteDriver(
configuration = DatabaseConfiguration(
name = "unit_test.db",
version = AppDatabase.Schema.version,
inMemory = true,
create = { connection ->
wrapConnection(connection) {
AppDatabase.Schema.create(it)
}
}
)
)
}
}
저는 Foreign Key를 사용하고, 퍼포먼스를 높이기 위해 메모리에 데이터를 저장하는 옵션을 지정해줬습니다.
다음으로 간단히 테스트 코드를 작성해주시면 됩니다.
먼저 간단한 테이블을 만들어보겠습니다.
CREATE TABLE Parent(
name TEXT NOT NULL PRIMARY KEY
);
select:
SELECT * FROM Parent;
insert:
INSERT INTO Parent(name) VALUES ?;
delete:
DELETE FROM Parent;
CREATE TABLE Child(
name TEXT NOT NULL PRIMARY KEY,
parentName TEXT NOT NULL,
FOREIGN KEY(parentName) REFERENCES Parent(parentName) ON DELETE CASCADE
);
selectAll:
SELECT * FROM Child;
insert:
INSERT INTO Child(name, parentName) VALUES ?;
이 두 테이블을 가지고 parent 테이블의 레코드가 삭제되면 cascade 정책에 따라 child 테이블의 레코드가 잘 삭제되는 지 테스트하는 코드를 작성해보겠습니다.
class SqlDelightTest {
private lateinit var driver: SqlDriver
private lateinit var database: AppDatabase
@BeforeTest
fun before() {
driver = TestSqlDriverFactory().createDriver()
database = AppDatabase(driver)
}
@Test
fun `child data should be null when on delete cascade`() {
val name = "leetaehoon"
val parent = Parent(name)
val child = Child("child", name)
database.parentQueries.insert(parent)
database.childQueries.insert(child)
var actual = database.childQueries.selectAll().executeAsList()
assertEquals(listOf(child), actual)
database.parentQueries.delete()
assertEquals(null, database.parentQueries.select().executeAsOneOrNull())
actual = database.childQueries.selectAll().executeAsList()
assertEquals(emptyList(), actual)
}
}
저는 이런식으로 foreign key를 적용해서 on delete cascade를 지정했을 때 의도대로 테이블의 레코드가 잘 지워지는지 확인하는 테스트를 작성해봤습니다.
KMM에서 SqlDelight를 테스트하기 위해 중요한 점을 다시 한번 짚어보겠습니다.
인텔리제이
에서 유닛 테스트 환경 구성 및 코드 작성하기PRAGMA foreign_keys=ON
과 같은 커맨드를 추가해줘야 한다.이상입니다. 봐주셔서 감사합니다.
지적과 질문은 언제든지 환영입니다.