第一步:build.gradle文件添加测试相关依赖
dependencies {
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.19.0'
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0'//mock()
testImplementation 'org.mockito:mockito-inline:2.13.0'//适配单元测试时,不能调用final类
...
}
第二步新建测试类(项目中存在的类)
BasePresenter:
package com.example.test
abstract class BasePresenter<V> {
protected var view: V? = null
fun attachView(view: V) {
this.view = view
}
fun detachView() {
this.view = null
}
}
Recipe:
package com.example.test
class Recipe(var id : String ="",var title :String = "",var imageUrl : String ="",var sourceUrl : String ="",var isFavorited : Boolean = false)
RecipeRepository:
package com.example.test
interface RecipeRepository {
fun addFavorite(item: Recipe)
fun removeFavorite(item: Recipe)
fun getFavoriteRecipes(): List<Recipe>
fun getRecipes(query: String, callback: RepositoryCallback<List<Recipe>>)
}
interface RepositoryCallback<in T> {
fun onSuccess(t: T?)
fun onError()
}
SearchResultsPresenter:
package com.example.test
/**
* JunitTestClass
*/
class SearchResultsPresenter(private val repository: RecipeRepository) :
BasePresenter<SearchResultsPresenter.View>() {
private var recipes: List<Recipe>? = null
fun search(query: String) {
view?.showLoading()
repository.getRecipes(query, object : RepositoryCallback<List<Recipe>> {
override fun onSuccess(recipes: List<Recipe>?) {
this@SearchResultsPresenter.recipes = recipes
if (recipes != null && recipes.isNotEmpty()) {
view?.showRecipes(recipes)
} else {
view?.showEmptyRecipes()
}
}
override fun onError() {
view?.showError()
}
})
}
fun addFavorite(recipe: Recipe) {
recipe.isFavorited = true
repository.addFavorite(recipe)
val recipeIndex = recipes?.indexOf(recipe)
if (recipeIndex != null) {
view?.refreshFavoriteStatus(recipeIndex)
}
}
fun removeFavorite(recipe: Recipe) {
repository.removeFavorite(recipe)
recipe.isFavorited = false
val recipeIndex = recipes?.indexOf(recipe)
if (recipeIndex != null) {
view?.refreshFavoriteStatus(recipeIndex)
}
}
interface View {
fun showLoading()
fun showRecipes(recipes: List<Recipe>)
fun showEmptyRecipes()
fun showError()
fun refreshFavoriteStatus(recipeIndex: Int)
}
}
第三步:给presenter新建测试类
1>、windows快捷键:ctrl+shift+T(也可鼠标右键->go to->Test);
2>、接着有测试类会让你选择,没有的->create new test...;
3>
4>新建成功
第四步:编写测试方法
1>verify方法(@Before将在@Test等方法之前调用)
class SearchResultsPresenterTest{
private lateinit var repository: RecipeRepository
private lateinit var presenter: SearchResultsPresenter
private lateinit var view: SearchResultsPresenter.View
@Before //最先调用(初始化)
fun setup() {
repository = mock()
view = mock()
presenter = SearchResultsPresenter(repository)
presenter.attachView(view)
}
@Test
fun search_callsShowLoading() {
presenter.search("eggs")
// verify(view).showLoading()//验证搜索时showLoading会不会被调用到
verify(view).showEmptyRecipes()//验证showEmptyRecipes会不会被调用到
// verify(view).showRecipes(mock())//验证showRecipes会不会被调用到
}
}
如果verify方法验证了这次运行未调用此方法,运行后将会抛出错误,便于我们验证是否运行后调用该方法(如果调用了,日志没有反应)
同时刚才我们的coverage运行结果可以直接查看方法被调用的情况
2、doAnswer方法(插桩):
@Test
fun search_callShowRecipes() {
val recipe = Recipe("id", "title", "imageUrl", "sourceUrl", false)
val recipes = listOf(recipe)
doAnswer {//执行相应的方法(getRecipes)并拿到回调(Stubbing--插桩)
val callback: RepositoryCallback<List<Recipe>> = it.getArgument(1)
// callback.onSuccess(recipes)//此时以下的verify将会全部通过
callback.onError()//这里也可以用onError()方法验证(此时下面的verify(view).showRecipes(eq(recipes))将会抛出错误)
}.whenever(repository).getRecipes(eq("eggs"), any())
presenter.search("eggs")
verify(repository).getRecipes(eq("eggs"), any())
verify(view).showRecipes(eq(recipes))
}
通过doAnswer方法和verify方法,我们可以验证代码是否正确调用了正确的方法(行为验证)
3:Assert 方法:
@Test
fun addFavorite_shouldUpdateRecipeStatus() {
val recipe = Recipe("id", "title", "imageUrl", "sourceUrl", false)
presenter.addFavorite(recipe)
//状态验证,assertThat(T actual, Matcher<? super T> matcher)左边为待验证的字段,右边为匹配器,
// is是kotlin关键字,所以'is'。如果验证字段不同将会抛出错误。
Assert.assertThat(recipe.isFavorited, CoreMatchers.`is`(false))
}
状态验证,assertThat(T actual, Matcher<? super T> matcher)左边为待验证的字段,右边为匹配器,is是kotlin关键字,所以'is';
@Test
fun removeFavorite_shouldUpdateRecipeStatus() {
val recipe = Recipe("id", "title", "imageUrl", "sourceUrl", true)
presenter.removeFavorite(recipe)
Assert.assertThat(recipe.isFavorited, CoreMatchers.`is`(false))
}
SearchResultsPresenterTest
package com.example.test
import com.nhaarman.mockitokotlin2.*
import org.hamcrest.CoreMatchers
import org.junit.Assert
import org.junit.Before
import org.junit.Test
class SearchResultsPresenterTest{
private lateinit var repository: RecipeRepository
private lateinit var presenter: SearchResultsPresenter
private lateinit var view: SearchResultsPresenter.View
@Before //最先调用(初始化)
fun setup() {
repository = mock()
view = mock()
presenter = SearchResultsPresenter(repository)
presenter.attachView(view)
}
@Test
fun search_callsShowLoading() {
presenter.search("eggs")
// verify(view).showLoading()//验证搜索时showLoading会不会被调用到
verify(view).showEmptyRecipes()//验证showEmptyRecipes会不会被调用到
// verify(view).showRecipes(mock())//验证showRecipes会不会被调用到
}
@Test
fun search_callShowRecipes() {
val recipe = Recipe("id", "title", "imageUrl", "sourceUrl", false)
val recipes = listOf(recipe)
doAnswer {//执行相应的方法(getRecipes)并拿到回调(Stubbing--插桩)
val callback: RepositoryCallback<List<Recipe>> = it.getArgument(1)
callback.onSuccess(recipes)//此时以下的verify将会全部通过
// callback.onError()//这里也可以用onError()方法验证(此时下面的verify(view).showRecipes(eq(recipes))将会抛出错误)
}.whenever(repository).getRecipes(eq("eggs"), any())
presenter.search("eggs")
verify(repository).getRecipes(eq("eggs"), any())
verify(view).showRecipes(eq(recipes))
}
@Test
fun search_error_callShowError() {
doAnswer {
val callback: RepositoryCallback<List<Recipe>> = it.getArgument(1)
callback.onError()
}.whenever(repository).getRecipes(eq("eggs"), any())
presenter.search("eggs")
verify(repository).getRecipes(eq("eggs"), any())
verify(view).showError()
}
@Test
fun addFavorite_shouldUpdateRecipeStatus() {
val recipe = Recipe("id", "title", "imageUrl", "sourceUrl", false)
presenter.addFavorite(recipe)
//状态验证,assertThat(T actual, Matcher<? super T> matcher)左边为待验证的字段,右边为匹配器,
// is是kotlin关键字,所以'is'。如果验证字段不同将会抛出错误。
Assert.assertThat(recipe.isFavorited, CoreMatchers.`is`(false))
}
@Test
fun removeFavorite_shouldUpdateRecipeStatus() {
val recipe = Recipe("id", "title", "imageUrl", "sourceUrl", true)
presenter.removeFavorite(recipe)
Assert.assertThat(recipe.isFavorited, CoreMatchers.`is`(false))
}
}
参考连接:
https://juejin.im/post/6844903848272723975#heading-14