当前位置: 首页 > 知识库问答 >
问题:

使用LiveData、协同程序和事务测试Android Room

易镜
2023-03-14

我想测试我的数据库层,我发现自己陷入了一种第22条军规的境地。

测试用例由两件事组成:

  • 保存一些实体
  • 加载实体并断言数据库映射按预期工作

简而言之,问题在于:

  • Insert是一种suspend方法,这意味着它需要在runBlocking{}

我的类是这样的:

@Dao
interface GameDao {
    @Query("SELECT * FROM game")
    fun getAll(): LiveData<List<Game>>

    @Insert
    suspend fun insert(game: Game): Long

    @Insert
    suspend fun insertRound(round: RoundRoom)

    @Transaction
    suspend fun insertGameAndRounds(game: Game, rounds: List<RoundRoom>) {
        val gameId = insert(game)
        rounds.onEach {
            it.gameId = gameId
        }

        rounds.forEach {
            insertRound(it)
        }
    }

测试用例是:

@RunWith(AndroidJUnit4::class)
class RoomTest {
    private lateinit var gameDao: GameDao
    private lateinit var db: AppDatabase

    @get:Rule
    val instantTaskExecutorRule = InstantTaskExecutorRule()

    @Before
    fun createDb() {
        val context = ApplicationProvider.getApplicationContext<Context>()
        db = Room.inMemoryDatabaseBuilder(
            context, AppDatabase::class.java
        ).build()
        gameDao = db.gameDao()
    }

    @Test
    @Throws(Exception::class)
    fun storeAndReadGame() {
        val game = Game(...)

        runBlocking {
            gameDao.insert(game)
        }

        val allGames = gameDao.getAll()

        // the .getValueBlocking cannot be run on the background thread - needs the InstantTaskExecutorRule
        val result = allGames.getValueBlocking() ?: throw InvalidObjectException("null returned as games")

        // some assertions about the result here
    }

    @Test
    fun storeAndReadGameLinkedWithRound() {
        val game = Game(...)

        val rounds = listOf(
            Round(...),
            Round(...),
            Round(...)
        )

        runBlocking {
            // This is where the execution freezes when InstantTaskExecutorRule is used
            gameDao.insertGameAndRounds(game, rounds)
        }

        // retrieve the data, assert on it, etc
    }
}

getValueBlockingLiveData的一个扩展函数,基本上是从上面的链接复制粘贴的

fun <T> LiveData<T>.getValueBlocking(): T? {
    var value: T? = null
    val latch = CountDownLatch(1)

    val observer = Observer<T> { t ->
        value = t
        latch.countDown()
    }

    observeForever(observer)

    latch.await(2, TimeUnit.SECONDS)
    return value
}

测试这个场景的正确方法是什么?在开发数据库映射层时,我需要这些类型的测试,以确保一切都如我所期望的那样工作。

共有2个答案

司寇祖鹤
2023-03-14

问题在于事务本身在内部某处使用运行阻塞,这会导致死锁。我已将InstantTaskExecutorRule更改为此类:

class IsMainExecutorRule : TestWatcher() {

    val defaultExecutor = DefaultTaskExecutor()

    override fun starting(description: Description?) {
        super.starting(description)
        ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
            override fun executeOnDiskIO(runnable: Runnable) {
                defaultExecutor.executeOnDiskIO(runnable)
            }

            override fun postToMainThread(runnable: Runnable) {
                defaultExecutor.executeOnDiskIO(runnable)
            }

            override fun isMainThread(): Boolean {
                return true
            }
        })
    }

    override fun finished(description: Description?) {
        super.finished(description)
        ArchTaskExecutor.getInstance().setDelegate(null)
    }
}

然后在代码中它将是:

@get:Rule
val liveDataRule = IsMainExecutorRule()

它不会导致死锁,但仍允许观察实时数据。

白禄
2023-03-14

这个问题现在有了一个解决方案,在这个答案中进行了解释。

修复方法是在内存数据库生成器中向文件室添加一行:

db = Room
    .inMemoryDatabaseBuilder(context, AppDatabase::class.java)
    .setTransactionExecutor(Executors.newSingleThreadExecutor()) // <-- this makes all the difference
    .build()

使用单线程执行器,测试工作正常。

 类似资料:
  • 我的问题是,我正在制作一个团结的游戏,我想做的是,当我游戏中的敌人击中障碍物时,它每秒都会造成x次伤害。 “桌面”障碍物上有一个对撞机和健康脚本,以及一个用于移动和攻击敌人的碰撞体和脚本。 敌人在与书桌物体碰撞时停止,并造成伤害!但是,损害是持续的...我试过协程和调用,但所有的结果都是一样的;每次触发探测造成10点伤害,而不是每秒。 以下是脚本: 敌人的运动和攻击: 桌面健康:

  • 测试将创建的数据保存在H2测试数据库中,随后的测试在测试套件中执行时将失败。 我如何用事务绕过类的所有测试,并在类的所有测试执行后回滚所有数据库修改?

  • 主要内容:什么是协同(coroutine)?,coroutine_test.lua 文件,实例,生产者-消费者问题,实例什么是协同(coroutine)? Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。 协同是非常强大的功能,但是用起来也很复杂。 线程和协同程序区别 线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。 在任一指定时刻只有

  • 什么是协同(coroutine)? Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。 协同是非常强大的功能,但是用起来也很复杂。 线程和协同程序区别 线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。 在任一指定时刻只有一个协同程序在运行,

  • 我试图做一些测试,看看我的事务方法是否工作正常。然而,我不完全理解我是否应该嘲笑数据库,以及JOOQ是如何进入这个等式的。下面是Service类,其中包含向数据库添加角色的事务。 我使用MySQL和连接到数据库是使用Spring配置文件 我假设不必每次测试事务并在完成后关闭连接时都重新连接到数据库。我知道有 但我不明白它是怎么工作的。 测试上述方法的最佳方法是什么?