Build fast and reliable UI tests in Android

preface

Let me see how I V á n Carlo and his team wrote 250 UI tests using espresso, mockito and dagger 2 and ran them successfully in only three minutes.

In this article, we will explore how to use mockito and dagger 2 to create fast and reliable Android UI tests. If you are starting to write UI tests in Android or developers who want to improve the performance of existing tests, this article is worth reading.

The first time I used UI automated testing in Android applications was a few years ago when I used robotium. I think the more realistic the test environment, the better. In the final test, you should act like Superman, be able to click any position quickly and report no errors, right? I think the mocking test is terrible. Why do we need to change the behavior of the application when testing? Isn't that cheating? A few months later, we had about 100 test cases that took 40 minutes to run. They are so unstable that even if there is no error in the function of the application, there is usually a more than half chance that it will fail. We spent a lot of time writing them, but these test cases didn't help us find any problems.

But as John Dewey said, failure is instructive. Failure is instructive. A wise man can always learn as much from failure as from success.

We did learn. We recognize that it is a bad practice to rely on real API interfaces in testing. Because you lose control of the returned data results, you can't preprocess your tests. In other words, network errors and external API interface errors will lead to your test errors. If your WiFi goes wrong, you certainly don't want your test to go wrong. You certainly want the UI test to run successfully. If you still rely on external API interfaces, you are completely doing integration tests, and you will not get the results we expect.

Mock test formal solution

(Mocking is the solution)

Mock testing is to replace a real object with a mock object to facilitate testing. It is mainly used to write unit tests, but it can also be very useful in UI testing. You can refer to different methods to simulate Java objects, but using mockito is really a simple and effective solution. In the following example, you can see a simulated userapi class, and stub (stub mainly appears in the process of integration test, which is used as an alternative to the following program when integrating from top to bottom. It can be understood as pre-processing the method to achieve the effect of modification. It will not be translated below), Therefore, it always returns a static array of usernames.

Once you create a mock object, you need to ensure that the simulated object is used when applying the test, and the real object is used when running. This is also a difficulty. If your code is not built to be test friendly, the process of replacing real objects will become extremely difficult or even impossible. Also note that the code you want to simulate must be independent of a separate class. For example, if you use httpurlconnection directly from your activity to call the rest API for data access (I hope you don't), the operation process will be very difficult to simulate.

Consider the system architecture before testing. Poor system architecture often makes it difficult to write test cases and mock tests, and mock tests will become unstable.

An easy to test architecture

A test friendly architecture

There are many ways to build an easy to test architecture. Here I will use the architecture used in Ribot as an example. You can also apply this architecture to any architecture. Our architecture is based on MVP mode. We decide to mock the whole model layer in UI testing, so we can make more operability of data and write more valuable and reliable tests.

MVP architecture

Datamanager is the only class in the model layer that exposes data to the presenter layer. Therefore, in order to test the model layer, we only need to replace it with a simulated datamanger.

Injecting simulated datamanager using Dagger

Using Dagger to inject a mock DataManager

Once we know what objects we need to simulate, it's time to consider how to replace the real objects in the test. We solve this problem through dagger2 (a dependency injection framework in Android). If you haven't contacted dagger, I suggest you read using dagger2 for dependency injection before continuing reading. Our application contains at least one dagger module and component. It is usually called applicationcomponent and applicationmodule. You can see below a simplified version of a class that provides only datamanger instances. Of course, you can also use the second method, using the @ inject annotation on the constructor of the datamanager. Here I directly provide a method for understanding. Note: the two classes applicationcomponent and applicationmodule are written together for intuitive understanding

The applicationcomponent of the application is initialized in the application class:

If you have used dagger2, you may have the same configuration steps. The current practice is to create the module and component required for a test

The above testapplicationmodule uses mockito to provide a simulated datamanger object. Testcomponent is an inherited class of applicationcomponent and uses testapplicationmodule as a module instead of applicationmodule. This means that if we initialize testcomponent in our application class, we will use the simulated datamanager object.

Create JUnit and set testcomponent

Creating a JUnit rule that sets the TestComponent

To ensure that the testcomponent is set in the application class before each test, we can create the testrule of JUnit 4

Testcomponentrule will create an instance object of testcomponent, which will override the apply method and return a new statement. The new statement will:

1 set testcomponent to the component object of application class.

2 call the evaluate () method of the statement of the base class (this is executed during test)

3 set the component field of the application to be empty, which will restore it to its initial state. In this way, we can prevent the interaction between test cases. Through the above code, we can obtain the simulated datamanager object through the getmockdatamanager () method. This allows us to get the datamanager object and stub its methods. It should be noted that this is only valid when the providedatamanger method of testapplicationcomponent uses the @ singleton annotation. If it is not specified as a singleton, the instance object we get through the getmockdatamanager method will be different from the instance object used by the application. Therefore, it is impossible for us to stub it.

Write test cases

Writing the tests

Now that we have the correct configuration of dagger and the testcomponentrule can be used, we still have one more thing to do, that is to write test cases. We use espresso to write UI tests. It's not perfect, but it's a fast and reliable Android testing framework. Before writing test cases, we need an app to test. If we have a very simple app, load the user name from the rest API and display it on the recyclerview. Then datamanger will look like this:

The loadusername () method uses retrofit and rxjava to load the data of the rest API. It returns a single object and sends a string. We also need an activity to display the user name usernames to the recyclerview. We assume that this activity is called usernamesactivity. If you follow the MVP mode, you will also have a corresponding presenter, but for intuitive understanding, the presenter operation is not done here.

Now we want to test this simple activity. There are at least three situations to test:

If the API returns a valid user name list data, they will be displayed on the list. 2 if the API returns empty data, the interface will display "empty list". 3 if the API request fails, the interface will display "failed to load user name"

Here are three tests in turn:

Through the above code, we use testcomponentrule and the activitytestrule provided by the official Android testing framework. The activitytestrule will let us start the usernamesactivity from the test. Note that we use rulechain to ensure that testcomponentrule always runs before activitytestrule. This is also to ensure that the testcomponent is set in the application class before any activity runs.

You may have noticed that the three test cases follow the same construction method:

1 pass when (xxx) Thenreturn (YYY) sets the precondition. This is achieved through the stub loadusernames () method. For example, the precondition for the first test is a valid list of user names. 2 through main Launchactivity (null) runs the activity. 3. Check (matches (isdisplayed()); Check the presentation of the view and show the expected value of the corresponding preconditions.

This is a very effective solution that allows you to test different scenarios because you have absolute control over the initial state of the entire application. If you do not use mock to write the above three use cases, it is almost impossible to achieve this effect, because the real API interface will always return the same data.

If you want to see a complete example of using this test method, you can view the project Ribot Android boilerplate or Ribot app in GitHub Of course, this solution also has some flaws. First of all, the stub is very cumbersome before each test. Complex interfaces may require 5-10 stubs before each test. It is useful to move some stubs into the initialization setup () method, but often different tests require different stubs. The second problem is the coupling between UI testing and potential implementation, which means that if you refactor the datamanager, you also need to modify the stub.

Nevertheless, we have also applied this UI testing method in several applications of Ribot, and it has been proved that this method is also beneficial. For example, in our recent Android application, 250 UI tests can run successfully in three minutes. There are also 380 unit tests of model layer and presenter layer.

Well, I hope this article will help you understand UI testing and write better test code.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>