本文主要是介绍浓缩摘要_测试浓缩咖啡匹配器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
浓缩摘要
If you are using instrumentation tests in your Android app, it is likely that you are using Espresso
— allowing you to define expectations about your app screens and how to interact with them.
如果您在Android应用程序中使用检测测试,则很可能是在使用Espresso
允许您定义对应用程序屏幕以及如何与之交互的期望。
During your day-by-day as a developer you will eventually implement some custom views. However, Espresso
won’t have the ability to assert the specific behaviors of your custom view out of the box. To make it possible you will have to create your own Espresso
matchers. In this article I want to show you how to create a simple Matcher and how to run tests for it in the JVM.
在作为开发人员的日常工作中, 您 最终将实现一些自定义视图 。 但是, Espresso
无法开箱即用地声明您的自定义视图的特定行为。 为了使之成为可能,您将必须创建自己的Espresso
匹配器。 在本文中, 我想向您展示如何创建一个简单的Matcher以及如何在JVM中对其运行测试。
The example we will follow is the implementation of a custom Button
that has a loading state. I will break it down into 3 steps: naive implementation of the LoadingButton
, creation of an appropriate Matcher
and the test for that Matcher
. At the end I will just showcase a simple usage of our new tested Matcher.
我们将遵循的示例是具有加载状态的自定义Button
的实现。 我将它分解为3个步骤:天真实施的LoadingButton
,适当的创建Matcher
和测试该Matcher
。 最后,我将展示新测试的Matcher的简单用法。


Creating the
LoadingButton
创建
LoadingButton
For the sake of simplicity I’m not going to focus on the implementation of the LoadingButton.
The only relevant part is that we would have a setter/getter
for the loading state.
为了简单起见,我将不着重于LoadingButton.
的实现LoadingButton.
唯一相关的部分是,我们将为加载状态 setter/getter
一个setter/getter
。
package com.example.uiimport android.content.Context
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatButtonclass LoadingButton @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0
) : AppCompatButton(context, attrs, defStyleAttr) {var isLoading: Boolean = falseset(value) {// make the button show the loading state....field = value}}
2. Creating the WithButtonLoading
Matcher
2.创建 WithButtonLoading
匹配器
When creating a custom Matcher
for a custom property of your View
you should extend a BoundedMatcher<T, S extends T>
. When extending it you will have to override two methods:
为View
的自定义属性创建自定义Matcher
,应扩展BoundedMatcher<T, S extends T>
。 扩展它时,您将必须重写两个方法:
describeTo(description: Description)
— here you should add a relevant message todescription
so that you can understand better when this matcher assertion fails.describeTo(description: Description)
-在此处应在description
添加一条相关消息,以便在此匹配器断言失败时可以更好地理解。matchesSafely(item: S): Boolean
— you should return aBoolean
stating whether or not the givenitem
respects your logic.matchesSafely(item: S): Boolean
—您应该返回一个Boolean
说明给定item
是否符合您的逻辑。
In our example we would do as follows:
在我们的示例中,我们将执行以下操作:
package com.example.testingimport android.view.View
import androidx.test.espresso.matcher.BoundedMatcher
import com.example.ui.LoadingButton
import org.hamcrest.Descriptioninternal class WithButtonLoadingMatcher :BoundedMatcher<View, LoadingButton>(LoadingButton::class.java) {override fun describeTo(description: Description) {description.appendText("with button in loading state")}override fun matchesSafely(item: LoadingButton): Boolean = item.isLoading
}
In this case we would use describeTo
to describe our component (a button in a loading state) and the matchesSafely
to assert that the given item
is in the desired loading state.
在这种情况下,我们将使用describeTo
描述我们的组件(处于加载状态的按钮),然后使用matchesSafely
断言给定item
处于所需的加载状态。
An important point is that I actually made this class internal
as this exposes an implementation detail. It is an observed good practice on Espresso
matchers to have public wrappers of the internal matchers that provide friendlier/more readable APIs.
重要的一点是,我实际上使此类成为internal
类,因为这暴露了实现细节。 在Espresso
匹配器上,观察到的良好做法是使用内部匹配器的公共包装程序来提供更友好/更易读的API。
We would then define a public ButtonMatchers
that provides methods to create matchers that check if the button is loading or not loading.
然后,我们将定义一个公共ButtonMatchers
,它提供用于创建匹配器的方法,以检查按钮是否正在加载。
package com.example.testing.internalimport android.view.View
import org.hamcrest.CoreMatchers.not
import org.hamcrest.Matcherobject ButtonMatchers {fun isLoading(): Matcher<View> = WithButtonLoadingMatcher()fun isNotLoading(): Matcher<View> = not(WithButtonLoadingMatcher())
}
3. Creating the Test
3.创建测试
In order to test this matcher in with a fast approach and with no need to run instrumentation tests, we will leverage FragmentScenario
which are able to run on top of JVM. This will create reliable and fast tests when compared with the instrumentation tests.
为了以一种快速的方法来测试此匹配器,并且无需运行测试,我们将利用能够在JVM之上运行的FragmentScenario
。 与仪器测试相比,这将创建可靠且快速的测试。
The idea is that we will use FragmentScenario + Roboelectrict
to launch a Fragment
that inflates our LoadingButton
, so that we can interact with it and perform assertions regarding the Matcher
behavior.
这个想法是,我们将使用FragmentScenario + Roboelectrict
启动一个使我们的LoadingButton
膨胀的Fragment
,以便我们可以与它进行交互并执行有关Matcher
行为的断言。
Below you can see that we are testing the behavior of ButtonMatchers.isLoading
matcher: if the Matcher
behaves correctly when the button is loading and not loading.
在下面,您可以看到我们正在测试ButtonMatchers.isLoading
匹配器的行为:如果在加载和不加载按钮时Matcher
行为正确。
package com.example.testingimport androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.example.testing.ButtonMatchers.isLoading
import com.example.testing.scenario.launchViewInFragment
import com.example.ui.LoadingButton
import junit.framework.AssertionFailedError
import org.hamcrest.CoreMatchers.instanceOf
import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config@Config(sdk = [28])
@RunWith(AndroidJUnit4::class)
class ButtonMatchersTest {@Testfun whenButtonIsLoading_isLoadingMatcher_ShouldNotThrowException() {launchViewInFragment {LoadingButton(context).apply { isLoading = true }}onView(instanceOf(LoadingButton::class.java)).check(matches(isLoading()))}@Testfun whenButtonIsNotLoading_isLoadingMatcher_ShouldThrowException() {launchViewInFragment {LoadingButton(context).apply { isLoading = false }}assertThrows(AssertionFailedError::class.java) {onView(instanceOf(LoadingButton::class.java)).check(matches(isLoading()))}}
}
The magic happens on launchViewInFragment
, which takes a function that creates the View
that will be inflated in the Fragment
.
魔术发生在launchViewInFragment
,它采用了一个创建将在Fragment
launchViewInFragment
的View
的函数。
The internals of launchViewInFragment
make use of FragmentScenario
to setup and launch the test.
launchViewInFragment
的内部使用FragmentScenario
来设置和启动测试。
package com.example.testing.scenarioimport android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainerinternal fun launchViewInFragment(instantiate: ViewBuilder.() -> View?): FragmentScenario<Fragment> =launchFragmentInContainer(instantiate = { ViewHostFragment(instantiate) })/*** An empty [Fragment]. This [Fragment] is used to host a [View] in [launchViewInFragment].** @see [launchViewInFragment] for more details.*/
internal class ViewHostFragment constructor(private val viewFactory: ViewBuilder.() -> View?
) : Fragment() {override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {val args = ViewBuilder(requireContext(), inflater, container, savedInstanceState)return viewFactory(args)}
}internal data class ViewBuilder internal constructor(val context: Context,val inflater: LayoutInflater,val container: ViewGroup? = null,val savedInstanceState: Bundle? = null
)
If we then run our tests and, the output on Android Studio will be the following:
如果然后运行测试,则Android Studio上的输出将如下所示:

You can see that it has a initial overheard time spent, but after that the tests run really fast. And the more tests you have the less the overhead will be noticeable in the full test run time.
您可以看到它有一个最初的听说时间,但是之后测试运行得非常快。 而且您拥有的测试越多,在整个测试运行时间内的开销就越小。
To finalize I just wanted to briefly show how we would use our new Matchers
in an instrumentation test. In this example, we assume the button loading state is toggled everytime you click it. You can see the matchers being used on the assertButtonIsLoading
and assertButtonIsNotLoading
.
最后,我只想简单地展示一下如何在仪器测试中使用新的Matchers
。 在此示例中,我们假设每次单击按钮时都会切换按钮的加载状态。 您可以看到在assertButtonIsLoading
和assertButtonIsNotLoading
上使用的匹配器。
package com.example.matchersimport androidx.test.core.app.launchActivity
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.example.testing.internal.ButtonMatchers.isLoading
import com.example.testing.internal.ButtonMatchers.isNotLoadingimport org.junit.Test
import org.junit.runner.RunWith@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {@Testfun verifyButtonToggleBehavior() {launchActivity<MainActivity>()assertButtonIsNotLoading()clickButton()assertButtonIsLoading()clickButton()assertButtonIsNotLoading()}private fun assertButtonIsLoading() {onView(withId(R.id.loadingButton)).check(matches(isLoading()))}private fun assertButtonIsNotLoading() {onView(withId(R.id.loadingButton)).check(matches(isNotLoading()))}private fun clickButton() {onView(withId(R.id.loadingButton)).perform(click())}
}
You can find a sample project describing this example here.
您可以在此处找到描述此示例的示例项目 。
Espresso
浓咖啡
FragmentScenario
片段场景
Creating a CustomViewMatcher by Thiago Lopes Silva
由Thiago Lopes Silva 创建CustomViewMatcher
翻译自: https://proandroiddev.com/testing-espresso-matchers-1c3c587d7d39
浓缩摘要
相关文章:
这篇关于浓缩摘要_测试浓缩咖啡匹配器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!