Intro
내가 작성한 DB 코드가 정상작동하는지 테스트를 해보는 시간이 왔다.
개인적으로 어떤 프로그램을 개발하든 테스트는 필수라고 생각한다.
개발을 다 마치고 실제 제품을 배포했는데 갑자기 오류가 발견되어 다시 전부 수정하는 것보다 미리미리 테스트 케이스를 생성해두고 오류가 발생하기 전 사전에 차단하는 게 훨씬 개발비용이 덜 들기 때문이다.
나중에 테스트하는 방법 관련해서 따로 글을 작성해 볼 예정이다.
DBHelper 테스트
가장 먼저 DBHelper 클래스를 테스트한다.
DBHelper가 제대로 동작하지 않으면 ItemDAO와 AlarmDAO는 보나마나 동작하지 않을 것이기 때문이다.
DBHelperTest.kt
package com.devjaewoo.itsmywaye.database
import android.content.ContentValues
import androidx.test.ext.junit.runners.AndroidJUnit4
import android.content.Context
import android.database.Cursor
import android.provider.BaseColumns
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import com.devjaewoo.itsmywaye.DATABASE_NAME
import com.devjaewoo.itsmywaye.TAG
import com.devjaewoo.itsmywaye.model.Item
import org.junit.After
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class DBHelperTest {
private lateinit var db: DBHelper
@Before
fun setUp() {
Log.d(TAG, "setUp: ")
val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
context.deleteDatabase(DATABASE_NAME)
db = DBHelper(context)
}
@After
fun tearDown() {
Log.d(TAG, "tearDown: ")
db.close()
}
private fun insertTestData() {
db.insert(Item.TableInfo.TABLE_NAME, ContentValues().apply {
put(Item.TableInfo.COLUMN_NAME_ITEM, "Waye")
put(Item.TableInfo.COLUMN_NAME_ENABLE, 1)
put(Item.TableInfo.COLUMN_NAME_FK_ITEM_ALARM, "null")
})
db.insert(Item.TableInfo.TABLE_NAME, ContentValues().apply {
put(Item.TableInfo.COLUMN_NAME_ITEM, "Legendary")
put(Item.TableInfo.COLUMN_NAME_ENABLE, 0)
put(Item.TableInfo.COLUMN_NAME_FK_ITEM_ALARM, "null")
})
}
@Test
fun testInsert() {
insertTestData()
val result: Cursor = db.selectAll(Item.TableInfo.TABLE_NAME)
assertEquals(2, result.count)
}
@Test
fun testSelectAll() {
insertTestData()
val result: Cursor = db.selectAll(Item.TableInfo.TABLE_NAME)
result.moveToNext()
val item1 = Item(
name = result.getString(result.getColumnIndex(Item.TableInfo.COLUMN_NAME_ITEM)),
enabled = result.getInt(result.getColumnIndex(Item.TableInfo.COLUMN_NAME_ENABLE)) == 1)
result.moveToNext()
val item2 = Item(
name = result.getString(result.getColumnIndex(Item.TableInfo.COLUMN_NAME_ITEM)),
enabled = result.getInt(result.getColumnIndex(Item.TableInfo.COLUMN_NAME_ENABLE)) == 1)
assertEquals("Waye", item1.name)
assertEquals(true, item1.enabled)
assertEquals("Legendary", item2.name)
assertEquals(false, item2.enabled)
}
@Test
fun testSelectByWhere() {
insertTestData()
val result = db.select(Item.TableInfo.TABLE_NAME, "${Item.TableInfo.COLUMN_NAME_ITEM} = \"Waye\"")
assertEquals(1, result.count)
result.moveToNext()
assertEquals("1", result.getString(result.getColumnIndex(BaseColumns._ID)))
assertEquals("Waye", result.getString(result.getColumnIndex(Item.TableInfo.COLUMN_NAME_ITEM)))
assertEquals("1", result.getString(result.getColumnIndex(Item.TableInfo.COLUMN_NAME_ENABLE)))
}
@Test
fun testSelectByQuery() {
insertTestData()
val result = db.select("SELECT * FROM ${Item.TableInfo.TABLE_NAME} WHERE ${BaseColumns._ID} = 2")
assertEquals(1, result.count)
result.moveToNext()
assertEquals("2", result.getString(result.getColumnIndex(BaseColumns._ID)))
assertEquals("Legendary", result.getString(result.getColumnIndex(Item.TableInfo.COLUMN_NAME_ITEM)))
assertEquals("0", result.getString(result.getColumnIndex(Item.TableInfo.COLUMN_NAME_ENABLE)))
}
@Test
fun testUpdateBySingleClause() {
insertTestData()
val values = ContentValues().apply {
put(Item.TableInfo.COLUMN_NAME_ITEM, "ITEM1234")
put(Item.TableInfo.COLUMN_NAME_ENABLE, "0")
put(Item.TableInfo.COLUMN_NAME_FK_ITEM_ALARM, "null")
}
db.update(Item.TableInfo.TABLE_NAME, values, "${BaseColumns._ID} = ?", "1")
val result = db.selectAll(Item.TableInfo.TABLE_NAME)
result.moveToNext()
assertEquals("ITEM1234", result.getString(result.getColumnIndex(Item.TableInfo.COLUMN_NAME_ITEM)))
assertEquals("0", result.getString(result.getColumnIndex(Item.TableInfo.COLUMN_NAME_ENABLE)))
}
@Test
fun testUpdateByMultiClause() {
insertTestData()
val values = ContentValues().apply {
put(Item.TableInfo.COLUMN_NAME_ITEM, "ITEM1234")
put(Item.TableInfo.COLUMN_NAME_ENABLE, "0")
put(Item.TableInfo.COLUMN_NAME_FK_ITEM_ALARM, "null")
}
db.update(Item.TableInfo.TABLE_NAME, values, "${BaseColumns._ID} = ? OR ${Item.TableInfo.COLUMN_NAME_ITEM} = ?", arrayOf("1", "Legendary"))
val result = db.selectAll(Item.TableInfo.TABLE_NAME)
result.moveToNext()
assertEquals("1", result.getString(result.getColumnIndex(BaseColumns._ID)))
assertEquals("ITEM1234", result.getString(result.getColumnIndex(Item.TableInfo.COLUMN_NAME_ITEM)))
assertEquals("0", result.getString(result.getColumnIndex(Item.TableInfo.COLUMN_NAME_ENABLE)))
result.moveToNext()
assertEquals("2", result.getString(result.getColumnIndex(BaseColumns._ID)))
assertEquals("ITEM1234", result.getString(result.getColumnIndex(Item.TableInfo.COLUMN_NAME_ITEM)))
assertEquals("0", result.getString(result.getColumnIndex(Item.TableInfo.COLUMN_NAME_ENABLE)))
}
}
DBHelper의 함수들이 정상작동하는지 테스트한다.
아래의 테스트 케이스들을 만들었다.
- 2개의 테스트 데이터 Insert 후 row의 개수를 조회해 2개로 나오는지 확인
- 2개의 테스트 데이터 Insert 후 SelectAll 함수를 사용해 데이터를 읽었을 때 입력한 데이터와 일치하는지 확인
- Select에 where 조건을 넣어 조회했을 때 정상적인 데이터가 나오는지 확인
- Select에 raw query를 넣어 조회했을 때 정상적인 데이터가 나오는지 확인
- 단일 조건 Update가 제대로 데이터를 변경했는지 확인
- 다중 조건 Update가 제대로 데이터를 변경했는지 확인
DB가 안열려서 에러 나고 이전에 테스트용으로 삽입한 데이터가 남아있어서 다음 테스트 때 자꾸 조회되는 등 난리가 났었지만, 다 수정해서 6개의 테스트 케이스 모두 통과하도록 만들었다.
AlarmDAO 테스트
이제 AlarmDAO 클래스를 테스트해야 한다.
DAO는 Model을 통한 입력, 조회가 잘 이루어지는지를 중점으로 테스트해야 한다.
AlarmDAOTest.kt
package com.devjaewoo.itsmywaye.dao
import android.provider.BaseColumns
import androidx.test.platform.app.InstrumentationRegistry
import com.devjaewoo.itsmywaye.DATABASE_NAME
import com.devjaewoo.itsmywaye.model.Alarm
import org.junit.Assert.*
import org.junit.After
import org.junit.Before
import org.junit.Test
class AlarmDAOTest {
private lateinit var alarmDAO: AlarmDAO
@Before
fun setUp() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
context.deleteDatabase(DATABASE_NAME)
alarmDAO = AlarmDAO(context)
}
@After
fun tearDown() {
}
@Test
fun testInsert() {
val alarm1 = Alarm("ABC", 0, 0, 0)
val alarm2 = Alarm("DEF", 1, 1, 1)
val alarm3 = Alarm("GHI", 2, 2, 2)
val row1 = alarmDAO.insert(alarm1)
val row2 = alarmDAO.insert(alarm2)
val row3 = alarmDAO.insert(alarm3)
assertEquals(1, row1)
assertEquals(2, row2)
assertEquals(3, row3)
alarmDAO.select("${BaseColumns._ID} = 1").also {
assertEquals(alarm1.filePath, it?.filePath)
assertEquals(alarm1.volume, it?.volume)
assertEquals(alarm1.repeatTimes, it?.repeatTimes)
assertEquals(alarm1.interval, it?.interval)
}
alarmDAO.select("${BaseColumns._ID} = 2").also {
assertEquals(alarm2.filePath, it?.filePath)
assertEquals(alarm2.volume, it?.volume)
assertEquals(alarm2.repeatTimes, it?.repeatTimes)
assertEquals(alarm2.interval, it?.interval)
}
alarmDAO.select("${BaseColumns._ID} = 3").also {
assertEquals(alarm3.filePath, it?.filePath)
assertEquals(alarm3.volume, it?.volume)
assertEquals(alarm3.repeatTimes, it?.repeatTimes)
assertEquals(alarm3.interval, it?.interval)
}
}
@Test
fun testUpdate() {
val alarm = Alarm("ABC", 0, 0, 0)
alarmDAO.insert(alarm)
var result = alarmDAO.select("${BaseColumns._ID} = 1")
assertNotNull(result)
result?.filePath = "DEF"
result?.volume = 100
result?.repeatTimes = 20
result?.interval = 5
alarmDAO.update(result!!)
result = alarmDAO.select("${BaseColumns._ID} = 1")
assertNotNull(result)
assertEquals("DEF", result?.filePath)
assertEquals(100, result?.volume)
assertEquals(20, result?.repeatTimes)
assertEquals(5, result?.interval)
}
@Test
fun testDeleteSelectAll() {
val alarm1 = Alarm("ABC", 0, 0, 0)
val alarm2 = Alarm("DEF", 1, 1, 1)
val alarm3 = Alarm("GHI", 2, 2, 2)
alarmDAO.insert(alarm1)
alarmDAO.insert(alarm2)
alarmDAO.insert(alarm3)
alarmDAO.delete(2)
val alarms: List<Alarm> = alarmDAO.selectAll()
assertEquals(2, alarms.size)
assertEquals(1, alarms[0].id)
assertEquals(3, alarms[1].id)
assertEquals(alarm1.filePath, alarms[0].filePath)
assertEquals(alarm3.filePath, alarms[1].filePath)
assertEquals(alarm1.volume, alarms[0].volume)
assertEquals(alarm3.volume, alarms[1].volume)
assertEquals(alarm1.repeatTimes, alarms[0].repeatTimes)
assertEquals(alarm3.repeatTimes, alarms[1].repeatTimes)
assertEquals(alarm1.interval, alarms[0].interval)
assertEquals(alarm3.interval, alarms[1].interval)
}
}
AlarmDAO의 함수들이 정상작동하는지 테스트한다.
아래의 테스트 케이스들을 만들었다.
- 3개의 테스트 데이터 Insert 후 ID를 통한 Select 시 입력한 데이터와 일치하는지 확인
- 테스트 데이터 Insert 후 ID는 같지만 데이터는 다른 객체를 만들어 Update 후 제대로 변경되었는지 확인
- 3개의 테스트 데이터 Insert 후 ID가 2인 데이터를 제거한 후, SelectAll 시 2개의 데이터만 들어오는지 확인
DBHelper를 테스트하면서 쌓인 경험이 있어서 그런지 3개의 테스트케이스 모두 금방 통과했다.
ItemDAO 테스트
AlarmDAOTest와 같이 ItemDAO의 함수들이 잘 작동하는지 테스트한다.
ItemDAOTest
package com.devjaewoo.itsmywaye.dao
import android.provider.BaseColumns
import androidx.test.platform.app.InstrumentationRegistry
import com.devjaewoo.itsmywaye.DATABASE_NAME
import com.devjaewoo.itsmywaye.model.Alarm
import com.devjaewoo.itsmywaye.model.Item
import org.junit.Assert.*
import org.junit.After
import org.junit.Before
import org.junit.Test
class ItemDAOTest {
private lateinit var itemDAO: ItemDAO
private lateinit var alarmDAO: AlarmDAO
@Before
fun setUp() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
context.deleteDatabase(DATABASE_NAME)
itemDAO = ItemDAO(context)
alarmDAO = AlarmDAO(context)
}
@After
fun tearDown() {
}
@Test
fun testInsertSelect() {
val alarm = Alarm("Test", 1, 2, 3)
val alarmID = alarmDAO.insert(alarm)
alarm.id = alarmID
val item1 = Item("Waye", true, alarm)
val item2 = Item("Legendary", true, null)
val item3 = Item("Epic", false, null)
itemDAO.insert(item1)
itemDAO.insert(item2)
itemDAO.insert(item3)
itemDAO.select("${BaseColumns._ID} = 1").also {
assertEquals(item1.name, it?.name)
assertEquals(item1.enabled, it?.enabled)
assertNotNull(it?.alarm)
assertEquals(alarm.filePath, it?.alarm!!.filePath)
assertEquals(alarm.volume, it.alarm!!.volume)
assertEquals(alarm.repeatTimes, it.alarm!!.repeatTimes)
assertEquals(alarm.interval, it.alarm!!.interval)
}
itemDAO.select("${BaseColumns._ID} = 2").also {
assertEquals(item2.name, it?.name)
assertEquals(item2.enabled, it?.enabled)
assertNull(it?.alarm)
}
itemDAO.select("${BaseColumns._ID} = 3").also {
assertEquals(item3.name, it?.name)
assertEquals(item3.enabled, it?.enabled)
assertNull(it?.alarm)
}
}
@Test
fun testUpdate() {
val item = Item("Waye", true)
itemDAO.insert(item)
var result = itemDAO.select("${BaseColumns._ID} = 1")
val alarm = Alarm("Test", 1, 2, 3)
val alarmID = alarmDAO.insert(alarm)
alarm.id = alarmID
assertNotNull(result)
result?.name = "Legendary"
result?.enabled = false
result?.alarm = alarm
itemDAO.update(result!!)
result = itemDAO.select("${BaseColumns._ID} = 1")
assertNotNull(result)
assertEquals("Legendary", result?.name)
assertEquals(false, result?.enabled)
assertNotNull(result?.alarm)
assertEquals(alarm.filePath, result?.alarm!!.filePath)
assertEquals(alarm.volume, result?.alarm!!.volume)
assertEquals(alarm.repeatTimes, result?.alarm!!.repeatTimes)
assertEquals(alarm.interval, result?.alarm!!.interval)
}
@Test
fun testDelete() {
val item1 = Item("Waye", true)
val item2 = Item("Legendary", true)
val item3 = Item("Epic", false)
itemDAO.insert(item1)
itemDAO.insert(item2)
itemDAO.insert(item3)
itemDAO.delete(2)
val items: List<Item> = itemDAO.selectAll()
assertEquals(2, items.size)
assertEquals(1, items[0].id)
assertEquals(3, items[1].id)
assertEquals("Waye", items[0].name)
assertEquals("Epic", items[1].name)
assertEquals(true, items[0].enabled)
assertEquals(false, items[1].enabled)
}
@Test
fun testDelete_AlarmDelete() {
val alarm = Alarm("Test", 1, 2, 3)
val alarmID = alarmDAO.insert(alarm)
alarm.id = alarmID
val item = Item("Waye", true, alarm)
itemDAO.insert(item)
alarmDAO.delete(alarmID)
val result = itemDAO.select("${BaseColumns._ID} = 1")
assertNotNull(result)
assertNull(result?.alarm)
}
}
AlarmDAO와 비슷하지만, 마지막에 Alarm을 입력하고, 해당 Alarm을 제거했을 때 FK가 NULL이 되는지 테스트하는 케이스를 넣어줬다.
4개의 테스트케이스 모두 한번에 통과했다.
한번에 통과하면 좋은건데 자꾸 찜찜한 기분이 든다.
나중에 통과 못하는 테스트케이스가 나올때까지 계속 추가해야겠다.
테스트 결과 확인
모든 테스트 클래스 작성 후 모든 테스트들을 일괄 수행해봤다.
아주 잘 된다.
다음 시간부턴 UI를 만들어야겠다.
'Projects > It's My Waye' 카테고리의 다른 글
[It's My Waye] 8. 알람 UI 구현 (0) | 2022.02.09 |
---|---|
[It's My Waye] 7. 메인화면 UI 구현 (DrawerLayout) (0) | 2022.01.23 |
[It's My Waye] 5. DB 기능 구현 - DAO 작성 (0) | 2022.01.22 |
[It's My Waye] 4. DB 기능 구현 - Model 작성 (0) | 2022.01.22 |
[It's My Waye] 3. DB 기능 구현 - DBHelper (0) | 2022.01.22 |