Efficient UI testing series
- Introduction
- Local and Instrumentation tests in Android
- Android Studio and Android SDK tools
- UI Testing Frameworks
- Exploring "Espresso Test Recorder"
- First test case with Kakao framework
- Testing Android Fragment in isolation
- Exploring test application and first test cases
- A domain-specific language for testing
- Introducing UI tests with mocking
- Flaky tests
- Achieving efficiency in tests
Introduction
The "Espresso Test Recorder" was presented at Google I/O 16 together with Android Studio 2.2. It was great news for Android Developer and everyone who connected with the Android Development because we can now create UI tests in just a few clicks. However, even right now (January 2020) this tool is not so popular and we will try to understand why.
First of all, let's try to create a test case for one of the scenarios for the Standard Login screen.
Let's go back to the previous article when we discussed frameworks for Android UI testing and try to generate test case for similar scenario.
Espresso Test Recorder in Practice
Scenario:
- Open
SignInActivity
; - Enter the "test" string into the email input field (id:
R.id.email
); - Press the "Sign In" button (id:
R.id.signIn
); - "An email address should be valid" message should be displayed (string const:
R.string.error_email_should_be_valid
).
Let's visualize a login screen of the application:
First of all, we should open the Espresso Test Recorder. We can find the "Record Espresso Test" submenu item in the "Run" menu item.
The developing application will open on the device or emulator as well as the Espresso Test Recorder window.
The next step is to start interaction with the connected device or emulator. Let's press the "LOGIN" button on device or emulator and you can see that the "Tap" action will be added to the "Espresso Test Recorder" window.
Let's finish the test case and do the following steps:
- Tap on "Email address";
- Enter the "test" text;
- Hide the keyboard;
- Tap on the "SIGN IN" button.
After that, the snackbar message will be displayed on the screen and we should press "Add Assertion" so that the screen of the device will be scanned and we will see the following screen of the "Espresso Test Recorder."
Afterwards, we should save the assertion and press the "OK" button. As a result, the test class will be created.
However, we will have a situation in which only one test case will be added to the file. This case is easy as we don't need to wait for data from the file, database, or server, and everything works well. However, when you need to wait for data from any data source, you will have problems because your test will fail without waiting for data. However, we can use sleep
or wait
from UiAutomator framework, but I cannot recommend them. An additional problem is that the test case starts from the main screen of the application when recording the test scenario. Before the recording starts of the test case, we can change the main screen of the application in the AndroidManifest.xml file.
Generated code:
@LargeTest
@RunWith(AndroidJUnit4::class)
class SignInActivityTest {
@Rule
@JvmField
var mActivityTestRule = ActivityTestRule(SignInActivity::class.java)
@Test
fun signInActivityTest() {
val appCompatEditText = onView(
allOf(withId(R.id.email),
childAtPosition(
allOf(withId(R.id.signInRoot),
childAtPosition(
withId(android.R.id.content),
0)),
4),
isDisplayed()))
appCompatEditText.perform(replaceText(" test"), closeSoftKeyboard())
val appCompatButton = onView(
allOf(withId(R.id.signIn), withText("Sign In"),
childAtPosition(
allOf(withId(R.id.signInRoot),
childAtPosition(
withId(android.R.id.content),
0)),
6),
isDisplayed()))
appCompatButton.perform(click())
}
private fun childAtPosition(
parentMatcher: Matcher<View>, position: Int): Matcher<View> {
return object : TypeSafeMatcher<View>() {
override fun describeTo(description: Description) {
description.appendText("Child at position $position in parent ")
parentMatcher.describeTo(description)
}
public override fun matchesSafely(view: View): Boolean {
val parent = view.parent
return parent is ViewGroup && parentMatcher.matches(parent)
&& view == parent.getChildAt(position)
}
}
}
}
However, we can create the same test case in Espresso:
@RunWith(AndroidJUnit4::class)
class EspressoSignInTest {
@Rule @JvmField
val activityRule = ActivityTestRule<SignInActivity>(SignInActivity::class.java)
@Test
fun shouldDisplaySinInErrorWhenEmailIsIncorrect() {
val incorrectEmail = "test"
onView(withId(R.id.email))
.perform(replaceText(incorrectEmail), closeSoftKeyboard())
onView(withId(R.id.signIn))
.perform(click())
onView(withText(R.string.error_email_should_be_valid))
.check(matches(isDisplayed()))
}
}
As you can see, the Espresso Test Recorder cannot replace manually created test cases. So, let's analyze issues which this tool has right now:
- One file has one test case, meaning that you will need a lot of files to cover basic scenarios;
- No waiting mechanism supported; if the application waits for data from any data source, the recorded test will fail or we should manually use
sleep
orwait
funntion from UiAutoator viewer as quick solution. However, generated test will fail without any changes; - Generated test code very detailed; this makes this test slow because the test case verifies many states and properties of View; usually, these checks can be skipped for the test case;
- The "Espresso Test Recorder" generates code which is very complicated to maintain in the future;
While it cannot fully replace creating manual test cases, it can help at some point in a real project.
Note: I recommend to invest time in learning a test framework to create efficient and elegant test cases.
Espresso Test Recorder for developers and QAs
As you can see, the Espresso Test Recorder generates not very readable code and very often these tests execute longer when compared to manually created test cases. However, this tool can be helpful for developers in some cases.
Let's imagine that you don't have UI test cases. Unfortunately, this is a widespread case (during presentations about UI automation, I often ask the question: "How many of you write Android UI tests?" and the amount is always around 15-20%).
So, you want to do refactoring and you don't have a lot of UI tests. As a result, you can generate a few scenarios which can be broken after you finish refactoring. After refactoring you can be sure that the most important cases will be working well. However, the Espresso Test Recorder has many limitations and you cannot generate a test case for any case. We can use it for an error handling inside the application without waiting for data from data sources.
However, I recommend improve knowledge in test automation and creating efficient test cases which will work faster and are easy to maintain.