Как выполнить модульное тестирование Thread.currentThread () в Android

0

Итак, я недавно столкнулся с вариантом использования, в котором я хочу провести модульное тестирование для checkNotMainThreadвнутреннего сравнения currentThreadи getMainLooper.thread. Как мы можем это проверить?

0

Чтобы проверить checkNotMainThread. Мы можем использовать принцип IoC (Inverse of Control), когда во время выполнения мы можем подключать различные реализации для тестовой среды и производственной среды.

Представьте, что у вас есть функция, в которой в теле функции вы добавляете проверку checkNotMainThreadтакого типа `оскорбительного программирования, когда, если при вызове функции используется основной поток, вы хотите вывести приложение из строя. Взгляните на пример ниже.

fun doWork() {
    checkNotMainThread()
    ...
}

где детали реализации checkNotMainThreadбудут выглядеть так.

check(
    Thread.currentThread() != Looper.getMainLooper().thread,
    "Method cannot be called on the main application thread (on: %s)",
    Thread.currentThread().name
)

Как вы можете видеть здесь, мы никак не можем переключить детали реализации с помощью (например, внедрения зависимостей или указателя службы), поскольку оба потока немного отличаются.

В этом случае мы можем использовать принципала IoC, чтобы помочь нам выполнить требования. Такой рабочий процесс как:

  1. создать ThreadCheckerExecutorкласс, который служит точкой входа для ловушки.
  2. создать реализацию по умолчанию для ThreadChecker, которую мы можем назвать DefaultThreadCheckerExecutor
  3. создайте, TestWatcherкоторый мы будем использовать в целях тестирования (в этом случае я привожу пример для JUnit4, для JUnit5 он выглядит так же.)

ThreadCheckerExecutor

/**
 * A static class that serves as a central point to execute common tasks.
 *
 * @hide This API is not final.
 */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
class ThreadCheckerExecutor private constructor() {

    /**
     * By default it will use [DefaultThreadCheckerExecutor]
     */
    internal var threadChecker: ThreadChecker = DefaultThreadCheckerExecutor()

    fun setDelegate(threadChecker: ThreadChecker?) {
        if (threadChecker == null) {
            this.threadChecker = DefaultThreadCheckerExecutor()
        } else {
            this.threadChecker = threadChecker
        }
    }

    companion object {

        @Volatile
        @GuardedBy("lock")
        private var sInstance: ThreadCheckerExecutor? = null
        private val lock = Any()

        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
        fun getInstance(): ThreadCheckerExecutor {
            if (sInstance == null) {
                synchronized(lock) {
                    if (sInstance == null) {
                        sInstance = ThreadCheckerExecutor()
                    }
                }
            }
            return sInstance!!
        }
    }
}

DefaultThreadCheckerExecutor

internal class DefaultThreadCheckerExecutor : ThreadChecker {
    override fun checkMainThread() {
        checkState(
            Thread.currentThread() == Looper.getMainLooper().thread,
            "Method cannot be called off the main application thread (on: %s)",
            Thread.currentThread().name
        )
    }

    override fun checkNotMainThread() {
        checkState(
            Thread.currentThread() != Looper.getMainLooper().thread,
            "Method cannot be called on the main application thread (on: %s)",
            Thread.currentThread().name
        )
    }
}

MainLooperExecutorRule

/**
 * A JUnit Test Rule that swaps the background executor used by the call-side with a
 * different one which executes each task synchronously.
 */
class MainLooperExecutorRule : TestWatcher() {
    override fun starting(description: Description?) {
        super.starting(description)
        ThreadCheckerExecutor.getInstance().setDelegate(object : ThreadChecker {
            override fun checkMainThread() {
                /*No Op*/
            }

            override fun checkNotMainThread() {
                /*No Op*/
            }
        })
    }

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

Пример на тестовом классе

class Foo {

    @get:Rule
    val rule : MainLooperExecutorRule()

    // some test
}

И вы сделали!