============
Testing Apps
============
Unit Testing
============
``Unit tests`` are referred to as “local unit tests”. The tests run without a
device or an emulator attached. However, unit tests cannot test the UI for your
app without mocking objects such as an Activity.
In your Android Studio project, source files for local unit tests are stored at
*module-name/src/test/java/*.
``Instrumented unit`` tests are tests that run on physical devices and emulators,
they provide more fidelity than local unit tests, but they run much more slowly.
Source files for instrumented tests are stored at *module-name/src/androidTest/java/*.
Unit Testing Example
--------------------
This example shows a function that verifies the format of email address and a unit
test for the function.
#. First, create a new project, write a program that verifies the correctness of
email addresses' format, returns true if the email address has the correct address,
otherwise return false.
.. code-block:: kotlin
class EmailValidation {
val emailPatternString ="""[a-zA-Z0-9\+\.\_\%\-]{1,256}\@[a-zA-Z0-9][a-zA-Z0-9\-]{0,64}(\.[a-zA-Z0-9][a-zA-Z0-9\-]{0,25})+"""
fun invalidateEmail(email: String): Boolean = email.matches(Regex(emailPatternString))
}
#. Then create a local unit test class, it should be written as a ``JUnit 4`` test
class. To create a basic JUnit 4 test class, create a class that contains one
or more test methods. A test method begins with the ``@Test`` annotation and
contains the code to exercise and verify a single functionality in the component
that you want to test.
In our example, there are several cases that should be considered:
.. code-block:: kotlin
import org.junit.Assert.*
import org.junit.Test
class EmailValidationTest{
val emailValidator:EmailValidation = EmailValidation()
@Test
fun correctEmail() {
assertTrue(emailValidator.invalidateEmail("name@email.com"))
}
@Test
fun correctEmailSubDomain() {
assertTrue(emailValidator.invalidateEmail("name@email.co.uk"))
}
@Test
fun invalidEmailNoTld() {
assertFalse(emailValidator.invalidateEmail("name@email"))
}
@Test
fun invalidEmailDoubleDot() {
assertFalse(emailValidator.invalidateEmail("name@email..com"))
}
@Test
fun invalidEmailNoUsername() {
assertFalse(emailValidator.invalidateEmail("@email.com"))
}
@Test
fun emptyString() {
assertFalse(emailValidator.invalidateEmail(""))
}
}
#. Now run the test to check if all tests are passed. If a test is passed, the
test will be labeled with a *green tick*, otherwise, it will be labeled *red*.
.. figure:: unit_test.jpg
:scale: 30%
:align: center
#. Here are some methods for verifying results:
``assertEquals(expected, actual)``, verifies whether the value of expected is
the same as actual.
``assertArrayEquals(expected, actual)``, verifies if the actual array is the
same as expected.
``assertFalse(actual)``, verify that the returned value is false.
``assertNotNull(actual)``, verify that the returned value is not null.
``assertNull(actual)``, verify that the returned value is null.
``assertTrue(actual)``, verify that the returned value is true.
``assertThat(actual, matcher)``, verify that returned value meets the conditions
in matcher.
Espresso Tests
==============
Espresso is a framework that allows us to write tests on the user interface.
Espresso Testing works basically in three blocks:
* ViewMatchers – allows to find an item in the view
* ViewActions – allows to execute actions on the elements
* ViewAssertions – validate a view state
This example creates a simple app that a user can type message in a *TextBox*,
click a *button*, then the message entered will be displayed.
#. Add the following dependencies to ``build.gradle`` file.
.. code-block:: groovy
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
androidTestImplementation 'androidx.test:runner:1.1.0'
androidTestImplementation 'androidx.test:rules:1.1.0'
#. Create a simple app where it contains a ``EditText`` for user to type in,
a ``button`` to click, and once the button is clicked, the message will be shown
on the screen in a ``TextView``.
Example code:
.. code-block:: xml
.. code-block:: kotlin
button.setOnClickListener {
textView.text = message.text
}
#. Then create a test class under *module-name/src/androidTest/java/*, and create
a class as following:
.. code-block:: kotlin
package com.example.testing
@RunWith(AndroidJUnit4::class)
class MainActivityTest {
@get:Rule
val activityRule = ActivityTestRule(MainActivity::class.java)
}
#. First, we need to test whether the user can enter message:
.. code-block:: kotlin
@Test
fun user_can_type(){
Espresso.onView(ViewMatchers.withId(R.id.message)).perform(ViewActions.typeText("Hello World"))
}
#. Then, whether the button can be clicked:
.. code-block:: kotlin
@Test
fun user_can_click(){
Espresso.onView(ViewMatchers.withId(R.id.button)).perform(ViewActions.click())
}
#. Next, whether the entered message matches displayed message:
.. code-block:: kotlin
@Test
fun result_match(){
Espresso.onView(ViewMatchers.withId(R.id.message)).perform(ViewActions.typeText("Hello World"))
Espresso.onView(ViewMatchers.withId(R.id.button)).perform(ViewActions.click())
Espresso.onView(ViewMatchers.withId(R.id.textView)).check(ViewAssertions.matches(ViewMatchers.withText("Hello World")))
}
#. Now run the test, all tests will be performed on emulator/device.