在Kotlin中测试协同路由

在Kotlin中测试协同路由,kotlin,junit,kotlin-coroutines,Kotlin,Junit,Kotlin Coroutines,我对一个爬虫做了一个简单的测试,它应该调用回购协议40次: @Test fun testX() { // ... runBlocking { crawlYelp.concurrentCrawl() // Thread.sleep(5000) // works if I un-comment } verify(restaurantsRepository, times(40)).saveAll(restaurants) // ... }

我对一个爬虫做了一个简单的测试,它应该调用回购协议40次:

@Test
fun testX() {
   // ... 
   runBlocking {
        crawlYelp.concurrentCrawl()
        // Thread.sleep(5000) // works if I un-comment
   }
   verify(restaurantsRepository, times(40)).saveAll(restaurants)
   // ...
}
这一实施:

suspend fun concurrentCrawl() {
    cities.map { loc ->
        1.rangeTo(10).map { start ->
            GlobalScope.async {
                val rests = scrapYelp.scrap(loc, start * 10)
                restaurantsRepository.saveAll(rests)
            }
        }
    }
}
但是。。。我明白了:

Wanted 40 times:
-> at ....testConcurrentCrawl(CrawlYelpTest.kt:46)
But was 30 times:
(30一直在变化;因此似乎测试没有等待…)

为什么我睡觉的时候它就过去了?如果我运行阻塞,则不需要它

顺便说一句,我有一个应该保持异步的控制器:

@PostMapping("crawl")
suspend fun crawl(): String {
    crawlYelp.concurrentCrawl()
    return "crawling" // this is supposed to be returned right away
}

谢谢

runBlocking
等待所有挂起函数完成,但由于
concurrentclawl
基本上只是使用
GlobalScope.async
currentclawl
在新线程中启动新作业,因此
runBlocking
,在所有作业启动后完成,而不是在所有作业完成后完成

您必须等待以
GlobalScope.async
启动的所有作业以如下方式完成:

suspend fun concurrentCrawl() {
    cities.map { loc ->
        1.rangeTo(10).map { start ->
            GlobalScope.async {
                val rests = scrapYelp.scrap(loc, start * 10)
                restaurantsRepository.saveAll(rests)
            }
        }.awaitAll()
    }
}
如果要等待
concurrentCrawl()
concurrentCrawl()
之外完成,则必须将
延迟的
结果传递给调用函数,如以下示例所示。在这种情况下,可以从
concurrentCrawl()
中删除
suspend
关键字

你也可以用这个(还有更多)

MockK的
verify
有一个
timeout:Long
参数,专门用于在测试中处理这些比赛

您可以保持生产代码不变,并将测试更改为:

import io.mockk.verify

@Test
fun `test X`() = runBlocking {
   // ... 

   crawlYelp.concurrentCrawl()

   verify(exactly = 40, timeout = 5000L) {
      restaurantsRepository.saveAll(restaurants)
   }
   // ...
}

如果在5秒之前的任何时间点验证成功,它将通过并继续。否则,验证(和测试)将失败。

谢谢!但这难道不会导致
concurrentCall
被阻塞吗?或者是线程上下文中的
awaittall
?是的,这会导致
concurrentCrawl
阻塞,但单个爬网将并行执行。如果要等待作业在
concurrentCrawl
之外继续,则必须将作业传递给调用函数。在我的回答中,我举了一个例子,就是需要
挂起
?从文件上看,我不确定什么时候应该使用它。谢谢在第一种情况下,
awaitAll
挂起,因此
concurrentCrawl
必须是一个
suspend fun
。在第二种情况下,它从不挂起,因此它不应该是一个
suspend fun
。另外,当您没有从协同程序中获得结果值时,不要使用
async
。基本上,
Deferred
是一种代码本身的味道。。暂停意味着它可以并行运行或暂停运行而不阻塞主流,对吗?关于
延迟
,我想你的意思是我应该使用
启动
fun concurrentCrawl(): List<Job> {
    return cities.map { loc ->
        1.rangeTo(10).map { start ->
            GlobalScope.launch {
                println("hallo world $start")
            }
        }
    }.flatten()
}

runBlocking {
    concurrentCrawl().joinAll()
}
import io.mockk.verify

@Test
fun `test X`() = runBlocking {
   // ... 

   crawlYelp.concurrentCrawl()

   verify(exactly = 40, timeout = 5000L) {
      restaurantsRepository.saveAll(restaurants)
   }
   // ...
}