A lot of people in an interview would say “good for unit testing.” However, many programmers are not in the habit of writing unit tests, especially in small startups, because of the amount of coding work that programmers do and the task of testing is handed over to the testing department. Unit testing actually reduces the number of errors and bugs in logic.

1. Logic tests in Presenter

This is only for Presenter tests, which are more important than Mode and View tests. Because the main logical code is written here.

1.1. The logic of Presenter tests

Test the logic of the functional code by writing test cases in the test code that assert possible results. In the MVP architecture, the View is separated from the Model, and a Presenter communicates with the Model. Use assertions to enumerate data that may be present in the Model, verify the logical judgments in Presenter, and then check the methods performed by the View layer. Among them, Junit framework is used for process control, test logic, test cases, etc. The Mockito framework is used for assertion and checking method execution.

1.2. Test framework on which unit testing depends

Presenter is a logical test that relies on the Junit and Mokito frameworks.

// Test correlation testImplementation "junit:junit:4.12" testImplementation "org.mockito:mockito-core:1.10.19"Copy the code

Before unit testing, you must understand the annotations and methods provided by Junit and Mockito, or you cannot test. Such as:


image.png



www.jianshu.com/p/5c8cde7ab…


2. Unit testing in the MVP framework

If you are doing a login function, the login requirements are as follows.

  • 1. The login is successful. (When the service returns a code of 0.)
  • 2. Login fails. (When the service returns a code that is not 0.)
  • 3. Login failed because the user name is empty.
  • 4. Login failed because the password is empty.
2.1 unit testing in non-TTD test-driven development mode

What is TTD test-driven development? As required, the MVP structure is created to do this module. Note: 1. The simplest MVP structure shown here. 2, the network request framework I use is already packaged Rxjava+Retorfit. 3, the API is WanAndroid API)


image.png

Define the interface as required in LoginContract:

Public interface LoginContract {public interface View {// Login succeeded. (When the service returns a code of 0.) void loginSuccess(); // Login failed. (When the service returns a code that is not 0.) void loginFail(); / / login failed because username error void loginFailCauseByErrorUserName (); / / login failed because username error void loginFailCauseByErrorPassword (); } public interface Model { Observable<Response<JSONObject>> login(String userName, String userPwd); } public interface Presenter { void login(String userName, String userPwd); }}Copy the code

Code in Presenter:

*/ public class LoginPresenter implements logincontract.presenter {private loginContract.model model; private LoginContract.View view; private BaseSchedulerProvider schedulerProvider; private CompositeDisposable mDisposable; public LoginPresenter(LoginContract.Model model,LoginContract.View view){ this.view = view; this.model = model; mDisposable = new CompositeDisposable(); schedulerProvider = SchedulerProvider.getInstance(); } /** * This is a constructor built for unit tests because of Rxjava thread switching, The test must be set to execute immediately to pass * @param Model * @param View * @Param schedulerProvider */ public LoginPresenter(loginContract.model model,LoginContract.View view,SchedulerTestProvider schedulerProvider){ this.view = view; this.model = model; mDisposable = new CompositeDisposable(); this.schedulerProvider = schedulerProvider; } @Override public void login(String userName, String userPwd) { if (TextUtils.isEmpty(userName)) { view.loginFailCauseByErrorUserName(); return; } if (TextUtils.isEmpty(userPwd)) { view.loginFailCauseByErrorPassword(); return; } Disposable disposable = model.login(userName, userPwd). compose(ResponseTransformer.handleResult()). compose(schedulerProvider.applySchedulers()) .subscribe(jsonObject -> { view.loginSuccess(); }, throwable -> { view.loginFail(); }); mDisposable.add(disposable); }}Copy the code

It is the logic in the login(String userName, String userPwd) method that is being tested here. The data returned by the server is asserted during testing, and a number of successful and failed logon test cases are enumerated. If these test cases work properly, the logic of the code is working properly.

  • Create a new test class to test Presenter’s logic.


    Creating a Test Class

  • Write the test case as required to initialize the necessary classes to be used by Presenter

Public class LoginPresenterTest {@mock Private loginContract. Model Model; @Mock private LoginContract.View view; private LoginPresenter presenter; @Mock private SchedulerTestProvider schedulerProvider; @Before public void setUp() { MockitoAnnotations.initMocks(this); schedulerProvider = new SchedulerTestProvider(); presenter = new LoginPresenter(model, view,schedulerProvider); @throws Exception */ @test public void loginSuccess() throws Exception {} /** * Failed to login. The server returns an error code * @throws Exception */ @test public void loginFailByServer() throws Exception{} /** * The login fails. Error user name * @throws Exception */ @test public void loginFailByErrorUserName() throws Exception{} /** * The login fails. Incorrect password * @throws Exception */ @test public void loginFailByErrorPwd() throws Exception{}}Copy the code

Here is the test code for loginSuccess () :

@test public void loginSuccess() throws Exception {// 1. Assert that the model.login method returns a correct Response when(model.login("123321","123321")).thenReturn(Observable.just(new Response<>(0,new JSONObject(),""))); Presenter. Login ("123321","123321"); Verify (view).loginSuccess(); }Copy the code

The test logic here is: 1. Assert that model returns the correct data. Presenter calls the login method. Check the method that the view will call at the end.

Then test the method separately.




Click here to test

If it’s green, it works. What the hell? Green.. The green?).




image.png

Change the code to non-zero, which is defined in the ResponseTransformer class of the Web request framework. If the code is non-zero, the request fails.

@test public void loginSuccess() throws Exception {// 1. Assert that the model.login method returns a correct Response when(model.login("123321","123321")).thenReturn(Observable.just(new Response<>(1,new JSONObject(),""))); Presenter. Login ("123321","123321"); Verify (view).loginSuccess(); }Copy the code

If it is not zero, the loginSuccess() method on the View layer will not be called, and the test will definitely fail. So let’s run it.


TIM screenshots 20181013144539. PNG



Wanted but not invoked

The above example is a simple example of unit testing.

2.1 TTD test-driven development mode of unit testing

TTD test driven development is mentioned here. What exactly is TTD test driven development?

Write test code before development, and use test cases to write functional code. At the beginning, the test cases could not pass without functional code, and then the test cases could pass the test one by one by modifying the functional code.

What if this function is done with TTD test driven development?

  • First, the login() method in Presenter should not be coded as an empty method.
  • Then create a Presenter test class to list all the test cases.
  • Write test code for test cases.
  • Write functional code according to the test code and make test cases pass the test one by one.

3. Possible problems

3.1 Android API cannot be used directly, which requires special treatment

In a Presenter, you do only logical operations, not interface processing, so you rarely use the Android API. But this is not absolute. For example, TextUtils makes a non-short call. If this is not handled in a special way, an uncallable error is reported. Solution: Create a new TextUtils class with the same package name in TextUtils.


New TextUtils

3.2, Rxjava thread test processing, here using Rxjava may encounter

This requires changing the thread’s schedulers.trampoline () to force the current task to proceed immediately. Specific look at this article: www.jianshu.com/p/22384556b…

3.3. Static class methods cannot be mocked

Look at this. Blog.csdn.net/hongchangfi…

4. Other tests

Unit tests are not only Presenter tests, but also view layer tests and Model layer tests. I don’t want to explain it here, because I haven’t done the View and Model layer tests, and I know the Presenter layer tests, the other tests should be similar.




Manual funny

Code address: github.com/AxeChen/Mvp…