Модульное тестирование случайно завершается с сопрограммами - PullRequest
0 голосов
/ 07 марта 2019

У меня проблема с тем, что мои юнит-тесты случайно проваливаются из-за, как мне кажется, условий гонки в сопрограммах.

У меня есть класс, в котором некоторые зависимости, которые высмеиваются в моем UT. (Я использовал как Mockito, так и MockK). При индивидуальном запуске тестов они никогда не заканчиваются ошибкой, даже запуск полного пакета не приводит к их сбою.

Однако, если я выполняю сборку gradlew или полную проверку gradlew (ktlint + detekt + lint + UTs), которые требуют больше ресурсов процессора, то один из тестов случайно завершается неудачей.

Итак, для некоторого фона:

В моем тестируемом классе есть актер, который получает два типа сообщений: постановка в очередь и отправка. Метод register(), который принимает параметр и ставит его в очередь до тех пор, пока не пройдет заданный таймер, а затем он не будет запущен. Если будет сделан другой вызов register(), он поставит новый параметр в очередь, отменит сопрограмму с таймером и вызовет новый.

suspend fun register(param: Param) {

    // This part suspends until the param is added to the DB
    val id = dao.addParam(param)

    actor.offer(Message.Enqueue(param))

    delayJob?.cancelAndJoin()
    delayJob = actor.sendDelayed(this, TIMER, Message.Push)
}

sendDelayed() это расширение, которое я создал

fun <E> SendChannel<E>.sendDelayed(
    scope: CoroutineScope,
    time: Long,
    element: E
) = scope.launch {
    delay(time)
    withContext(NonCancellable) {
        send(element)
    }
}

Одной из зависимостей моего тестируемого класса является CoroutineDispatcher, который я установил в Dispatchers.Unconfined в UT.

Например, один из неудачных тестов выглядит примерно так:

@Mock
private lateinit var mockDao: Dao

private lateinit var classUnderTest: ClassUnderTest

@Before
fun setup() {
    MockitoAnnotations.initMocks(this)

    classUnderTest = ClassUnderTest(mockDao, Dispatchers.Unconfined)
}

@Test
fun `test that fail randomly`() {
    val param = Param()

    runBlocking {
        whenever(mockDao.addParam(param)) doReturn 1

        classUnderTest.register(param)
        delay(TIMER)
    }

    verifyBlocking(mockDao) { update(param) }
}

И он терпит неудачу с NullPointerException. Каким-то образом издевательство не издевается. Я пробовал с MockK, и результат похож, он не может сказать, что не может найти ответ (по той же причине, фреймворк не может найти насмешку).

В этот момент я уже пытался убрать задержку, сменить диспетчер и многое другое. Кажется, я не могу зависеть от запуска сопрограммы для UT. У меня была похожая проблема: простой launch { val id = dao.get(param) } приводил к случайному сбою UT, потому что id не был установлен при проверке.

Должно быть, что-то в корне неверно в том, как я работаю с сопрограммами или как я их тестирую. Буду признателен за любые отзывы.

Спасибо

...