重试Android测试并在运行之间清除数据库

5 小时前   出处: testableapple.com  作/译者:Testableapple/Ares

引言

当各路高手还在为“该禁用不稳定的测试代码还是重试它们”争论不休时,我想直接给你们展示如何实现后者。

此方法专供勇士——后果自负哦!

实现重试策略

我们先创建两个类:

  • Retry - 用于重试任意特定测试代码
  • RetryRule - 负责整个测试套件的重试逻辑
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement

/** Annotation to retry a specific failed test. **/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
public annotation class Retry(val count: Int)

/** Rule to retry all failed tests. **/
public class RetryRule(private val count: Int) : TestRule {

    override fun apply(base: Statement, description: Description): Statement = statement(base, description)

    private fun statement(base: Statement, description: Description): Statement {
        return object : Statement() {
            @Throws(Throwable::class)
            override fun evaluate() {
                val retryAnnotation: Retry? = description.getAnnotation(Retry::class.java)
                val retryCount = retryAnnotation?.count ?: count
                var caughtThrowable: Throwable? = null

                for (i in 0 until retryCount) {
                    try {
                        System.err.println("${description.displayName}: run #${(i + 1)} started.")
                        base.evaluate()
                        return
                    } catch (t: Throwable) {
                        System.err.println("${description.displayName}: run #${(i + 1)} failed.")
                        caughtThrowable = t
                    }
                }

                throw caughtThrowable ?: IllegalStateException()
            }
        }
    }
}

接下来,我们创建两个简单的测试用例,它们将绝望地尝试断言两个不同的数字不相等:

import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test

abstract class BaseTestClass {

    @get:Rule
    val retryRule = RetryRule(count = 3)
}

class SampleTest : BaseTestClass() {

    @Test // <~~~~~~ This test have 3 attempts to pass
    fun testNewFancyRetryRule() {
        assertEquals(33, 42)
    }

    @Retry(count = 4)
    @Test // <~~~~~~ This test have 4 attempts to pass
    fun testNewFancyRetryAnnotation() {
        assertEquals(42, 33)
    }
}

重试之前清理数据库

很可能,你希望每次重试时环境尽可能干净。

为实现这一点,我们来实现DatabaseOperations类,它将在每次测试重试之间负责清理数据库:

import android.database.sqlite.SQLiteDatabase
import androidx.test.platform.app.InstrumentationRegistry
import java.io.File

public open class DatabaseOperations {

    public open fun clearDatabases() {
        val databaseOperations = DatabaseOperations()
        val dbFiles = databaseOperations.getAllDatabaseFiles().filterNot { shouldIgnoreFile(it.path) }
        dbFiles.forEach { clearDatabase(it, databaseOperations) }
    }

    private fun shouldIgnoreFile(path: String): Boolean {
        val ignoredSuffixes = arrayOf("-journal", "-shm", "-uid", "-wal")
        return ignoredSuffixes.any { path.endsWith(it) }
    }

    private fun clearDatabase(dbFile: File, dbOperations: DatabaseOperations) {
        dbOperations.openDatabase(dbFile).use { database ->
            val tablesToClear = dbOperations.getTableNames(database).filterNot { it == "room_master_table" }
            tablesToClear.forEach { dbOperations.deleteTableContent(database, it) }
        }
    }

    private fun getAllDatabaseFiles(): List<File> {
        return InstrumentationRegistry.getInstrumentation().targetContext.let { context ->
            context.databaseList().map { context.getDatabasePath(it) }
        }
    }

    private fun openDatabase(databaseFile: File): SQLiteDatabase {
        return SQLiteDatabase.openDatabase(databaseFile.absolutePath, null, 0)
    }

    private fun getTableNames(sqLiteDatabase: SQLiteDatabase): List<String> {
        sqLiteDatabase.rawQuery("SELECT name FROM sqlite_master WHERE type IN (?, ?)", arrayOf("table", "view"))
            .use { cursor ->
                val tableNames = ArrayList<String>()
                while (cursor.moveToNext()) {
                    tableNames.add(cursor.getString(0))
                }
                return tableNames
            }
    }

    private fun deleteTableContent(sqLiteDatabase: SQLiteDatabase, tableName: String) {
        sqLiteDatabase.delete(tableName, null, null)
    }
}

因此,RetryRule 类的语句函数将进行一些调整:

private fun statement(base: Statement, description: Description): Statement {
    return object : Statement() {
        @Throws(Throwable::class)
        override fun evaluate() {
            val retryAnnotation: Retry? = description.getAnnotation(Retry::class.java)
            val retryCount = retryAnnotation?.count ?: count
            val databaseOperations = DatabaseOperations() // <~~~~~~ new line
            var caughtThrowable: Throwable? = null

            for (i in 0 until retryCount) {
                try {
                    System.err.println("${description.displayName}: run #${(i + 1)} started.")
                    base.evaluate()
                    return
                } catch (t: Throwable) {
                    System.err.println("${description.displayName}: run #${(i + 1)} failed.")
                    databaseOperations.clearDatabases() // <~~~~~~ new line
                    caughtThrowable = t
                }
            }

            throw caughtThrowable ?: IllegalStateException()
        }
    }
}

总结

该重试,还是不该重试,这是个问题。你的意见如何呢?欢迎在评论区给出你的意见。


声明:本文为本站编辑转载,文章版权归原作者所有。文章内容为作者个人观点,本站只提供转载参考(依行业惯例严格标明出处和作译者),目的在于传递更多专业信息,普惠测试相关从业者,开源分享,推动行业交流和进步。 如涉及作品内容、版权和其它问题,请原作者及时与本站联系(QQ:1017718740),我们将第一时间进行处理。本站拥有对此声明的最终解释权!欢迎大家通过新浪微博(@测试窝)或微信公众号(测试窝)关注我们,与我们的编辑和其他窝友交流。
/18 人阅读/0 条评论 发表评论

登录 后发表评论
最新文章