在 Kotlin 中测试协程
Testing coroutines in Kotlin
我有一个关于应该调用 repo 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一直在变,看来考试不等了...)
为什么我做睡眠的时候就过去了?鉴于我 运行 blocking..
不需要它
顺便说一句,我有一个应该保持异步的控制器:
@PostMapping("crawl")
suspend fun crawl(): String {
crawlYelp.concurrentCrawl()
return "crawling" // this is supposed to be returned right away
}
谢谢
runBlocking
等待所有挂起函数完成,但由于 concurrentCrawl
基本上只是在 GlobalScope.async
currentCrawl
的新线程中启动新作业,因此 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()
之外完成,那么您必须将 Deferred
结果传递给调用函数,如下例所示。在这种情况下,可以从 concurrentCrawl()
.
中删除 suspend
关键字
fun concurrentCrawl(): List<Deferred<Unit>> {
return cities.map { loc ->
1.rangeTo(10).map { start ->
GlobalScope.async {
println("hallo world $start")
}
}
}.flatten()
}
runBlocking {
concurrentCrawl().awaitAll()
}
如评论中所述:在这种情况下,async
方法没有 return 任何值,因此最好改用 launch:
fun concurrentCrawl(): List<Job> {
return cities.map { loc ->
1.rangeTo(10).map { start ->
GlobalScope.launch {
println("hallo world $start")
}
}
}.flatten()
}
runBlocking {
concurrentCrawl().joinAll()
}
您也可以为此使用 MockK(以及更多)。
MockK 的 verify
有一个 timeout : Long
参数专门用于在测试中处理这些竞争。
您可以保持生产代码不变,并将您的测试更改为:
import io.mockk.verify
@Test
fun `test X`() = runBlocking {
// ...
crawlYelp.concurrentCrawl()
verify(exactly = 40, timeout = 5000L) {
restaurantsRepository.saveAll(restaurants)
}
// ...
}
如果在 5 秒之前的任何时候验证成功,它将通过并继续。否则,验证(和测试)将失败。
我有一个关于应该调用 repo 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一直在变,看来考试不等了...)
为什么我做睡眠的时候就过去了?鉴于我 运行 blocking..
不需要它顺便说一句,我有一个应该保持异步的控制器:
@PostMapping("crawl")
suspend fun crawl(): String {
crawlYelp.concurrentCrawl()
return "crawling" // this is supposed to be returned right away
}
谢谢
runBlocking
等待所有挂起函数完成,但由于 concurrentCrawl
基本上只是在 GlobalScope.async
currentCrawl
的新线程中启动新作业,因此 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()
之外完成,那么您必须将 Deferred
结果传递给调用函数,如下例所示。在这种情况下,可以从 concurrentCrawl()
.
suspend
关键字
fun concurrentCrawl(): List<Deferred<Unit>> {
return cities.map { loc ->
1.rangeTo(10).map { start ->
GlobalScope.async {
println("hallo world $start")
}
}
}.flatten()
}
runBlocking {
concurrentCrawl().awaitAll()
}
如评论中所述:在这种情况下,async
方法没有 return 任何值,因此最好改用 launch:
fun concurrentCrawl(): List<Job> {
return cities.map { loc ->
1.rangeTo(10).map { start ->
GlobalScope.launch {
println("hallo world $start")
}
}
}.flatten()
}
runBlocking {
concurrentCrawl().joinAll()
}
您也可以为此使用 MockK(以及更多)。
MockK 的 verify
有一个 timeout : Long
参数专门用于在测试中处理这些竞争。
您可以保持生产代码不变,并将您的测试更改为:
import io.mockk.verify
@Test
fun `test X`() = runBlocking {
// ...
crawlYelp.concurrentCrawl()
verify(exactly = 40, timeout = 5000L) {
restaurantsRepository.saveAll(restaurants)
}
// ...
}
如果在 5 秒之前的任何时候验证成功,它将通过并继续。否则,验证(和测试)将失败。