SlideShare a Scribd company logo
The real beginner’s guide to
Android testing
Eric Nguyen
About myself
● Software Engineer at Grab
● DragonBall fan (Vegeta fan)
● @ericntd (LinkedIn, Medium, Github, StackoverFlow, Twitter)
● Send me your resume eric.nguyen at grab.com - https://grab.careers/
#droidconvn
My journey with unit testing
Before unit tests With unit tests
#droidconvn
@ericntd
Agenda
1. Why write tests
2. Types of tests
3. Introduction to unit testing
4. Challenge to unit testing with Android
5. The solution
6. Steps to your first Android unit tests
7. MVP architecture limitations
8. Q & A
@ericntd
1) Why write tests?
● Improve and maintain product quality with less QA’s manual effort
● Increase confidence when shipping
● Perform routine exhaustive checks humans can’t perform, fast
● Help you write more modular code
@ericntd
2) Different of type of tests
Unit tests
Component/
Integration Tests
End-to-
end
tests
20-30 mins to run
100++ component UI
tests
<10 mins to run 4000 tests
Fast to run,
easy to write,
run and to
measure
coverage
Slow to run,
difficult to
write, run and
to measure
coverage
#droidconvn
@ericntd
3) Unit testing crash course
public class Calculator {
/**
* @param input an integer in the range of
[Integer.MIN_VALUE/2, Integer.MAX_VALUE/2]
* @return -1 if the input is too big or too small, otherwise the
input times 2
*/
public int timesTwo(int input) {
if (input > Integer.MAX_VALUE / 2 || input <
Integer.MIN_VALUE / 2) {
return -1;
}
return input * 2;
}
}
● We have a Calculator class with a
single timesTwo method
● The timesTwo method returns the
input times 2, except for in the
case of overflow, it returns -1
@ericntd
3) Unit testing crash course
dependencies {
//...
testImplementation 'junit:junit:4.12'
testImplementation "org.mockito:mockito-core:2.12.0"
}
app/build.gradle
#droidconvn
public class CalculatorTest {
private Calculator calculator = new Calculator();
@Test
public void plusTwo() {
Assert.assertEquals(0, calculator.timesTwo(0));
Assert.assertEquals(2, calculator.timesTwo(1));
}
}
app/src/test/java/your.package.name
@ericntd
3) Unit testing crash course
public class CalculatorTest {
private Calculator calculator = new Calculator();
@Test
public void plusTwo() {
Assert.assertEquals(0, calculator.timesTwo(0));
Assert.assertEquals(2, calculator.timesTwo(1));
Assert.assertEquals(-2, calculator.timesTwo(-1));
Assert.assertEquals(-1, calculator.timesTwo(Integer.MAX_VALUE));
Assert.assertEquals(-1, calculator.timesTwo(Integer.MIN_VALUE));
Assert.assertEquals(2147483646, calculator.timesTwo(Integer.MAX_VALUE /
2));
Assert.assertEquals(-1, calculator.timesTwo(Integer.MAX_VALUE / 2 + 1));
Assert.assertEquals(-2147483648, calculator.timesTwo(Integer.MIN_VALUE /
2));
Assert.assertEquals(-1, calculator.timesTwo(Integer.MIN_VALUE / 2 - 1));
}
}
@ericntd
3) Unit testing crash course
● Our test passes!
@ericntd
3) Unit testing crash course
● Unit tests help our app’s stability
@ericntd
3) Unit testing crash course
Unit tests also force us to write modular code
“The first rule of functions is that they should be small. The second
rule of functions is that they should be smaller than that. Functions
should not be 100 lines long. Functions should hardly ever be 20
lines long.” - Clean Code
@ericntd
4) Challenge of unit testing in Android
● Consider a simple activity:
○ A TextView display a number, starting from 1
○ A Button named “Time Two”
● The doubling logic is from our Calculator’s
timesTwo
@ericntd
4) Challenge of unit testing in Android
public class CalculatorActivity extends AppCompatActivity {
`
public Calculator calculator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_calculator);
calculator = new Calculator();
final TextView tvNumber = findViewById(R.id.tv_number);
Button ctaTimesTwo = findViewById(R.id.cta_times_two);
ctaTimesTwo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
updateNumber(tvNumber);
}
});
}
public void updateNumber(TextView tvNumber) {
tvNumber.setText(calculator.timesTwo(Integer.valueOf(tvNumber.getText().toString())));
}
}
CalculatorActivity is the
Controller in a Model-View-
Controller
@ericntd
4) Challenge of unit testing in Android
public class CalculatorActivity extends AppCompatActivity {
public Calculator calculator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_calculator);
calculator = new Calculator();
final TextView tvNumber = findViewById(R.id.tv_number);
Button ctaTimesTwo = findViewById(R.id.cta_times_two);
ctaTimesTwo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
updateNumber(tvNumber);
}
});
}
public void updateNumber(TextView tvNumber) {
tvNumber.setText(calculator.timesTwo(Integer.valueOf(tvNumber.getText().toString())));
}
}
Doubling logic is based on
Calculator’s timesTwo
@ericntd
4) Challenge of unit testing in Android
public class CalculatorActivityTest {
private CalculatorActivity activity = new CalculatorActivity();
@Test
public void updateNumber() {
// Preparation
TextView tvNumber = Mockito.mock(TextView.class);
Mockito.doReturn("1").when(tvNumber).getText();
// Trigger
activity.updateNumber(tvNumber);
// Validation
Assert.assertEquals("2", tvNumber.getText().toString());
Mockito.verify(tvNumber).setText(2);
}
}
● Prepare any dependency incl.
Android-specific easily
● Verify a method is called on a
mocked object
@ericntd
4) Challenge of unit testing in Android
We run CalculatorActivityTest and we
get a NullPointerException.
Explanation:
The Calculator object was instantiated inside the
Activity’s onCreate method.
In our JUnit test, we have no control over the
CalculatorActivity’s onCreate method
Conclusion:
We are unable to unit test our business logics
inside an Android Activity
@ericntd
4) Challenge of unit testing in Android
Possible Workarounds:
● Instantiate a Calculator object inside
updateNumber method itself
○ Inefficiency - multiple
Calculator objects instead of
reusing one
● Create a setter in the the activity:
setCalculator(Caculator)
○ Side effects e.g. race
condition, unexpected
behaviours
@ericntd
5) Solution for unit testing in Android
● We need to move our business logics outside of the Activity or Fragment
● We need to refactor our app from MVC to a better architecture such as
Model-View-Presenter (MVP)
@ericntd
5) Solution for unit testing in Android - Architectures
#droidconvn
● Difficult to test logics within Activity/
Fragment
● Activity/ Fragment are bloated
Model-View-Presenter
View (Fragment/
Activity)
Presenter
Activity/ Fragment
Model-View-Controller
Controller
View
● Business logics easily tested
● Activity/ Fragment much thinner without
business logics
@ericntd
6) Steps to your first Android unit tests
A. Refactor your app to MVP
B. Write tests for your Presenter (contains business logics)
C. Profit!
#droidconvn
@ericntd
Sample app: GitHub Search
https://github.com/ericntd/Github-Search
@ericntd
A)MVC => MVP
+
#droidconvn
public class MainActivity extends AppCompatActivity {
//...
private void searchGitHubRepos(GitHubApi gitHubApi, String query) {
gitHubApi.searchRepos(query).enqueue(new
Callback<SearchResponse>() {
@Override
public void onResponse(Call<SearchResponse> call,
Response<SearchResponse> response) {
handleResponse(response);
}
@Override
public void onFailure(Call<SearchResponse> call,
Throwable t) {
handleError("E103 - System error");
}
});
}
}
Original MainActivity in a MVC
settings. 2 business logics
methods:
● searchGitHubRepos
● handleSearchGitHubResp
onse
We will move these 2 methods
into the Presenter class
@ericntd
A)MVC => MVP
public class SearchPresenter implements SearchPresenterContract,
GitHubRepository
.GitHubRepositoryCallback {
private final SearchViewContract viewContract;
private final GitHubRepository repository;
//...
@Override
public void searchGitHubRepos(@Nullable final String query) {
if (query != null && query.length() > 0) {
repository.searchRepos(query, this);
}
}
}
The Presenter now contains
the 2 business logics methods:
● searchGitHubRepos
● handleSearchGitHubResp
onse
@ericntd
A)MVC => MVP
public class MainActivity extends AppCompatActivity implements SearchViewContract {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//...
}
@Override
public void displaySearchResults(@NonNull List<SearchResult> searchResults,
@Nullable Integer totalCount) {
rvAdapter.updateResults(searchResults);
tvStatus.setText(String.format(Locale.US, "Number of results: %d", totalCount));
}
@Override
public void displayError() {
Toast.makeText(this, "some error happened", Toast.LENGTH_SHORT).show();
}
@Override
public void displayError(String s) {
Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
}
Our new Activity is much
cleaner with only ~50 lines of
code
@ericntd
B) Write tests for Presenter - Setup
package tech.ericntd.githubsearch.search;
public class SearchPresenterTest {
private SearchPresenter presenter;
@Mock
private GitHubRepository repository;
@Mock
private SearchViewContract viewContract;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);// required for the "@Mock" annotations
// Make presenter a mock while using mock repository and viewContract
created above
presenter = Mockito.spy(new SearchPresenter(viewContract, repository));
}
// The tests
}
dependencies {
//...
testImplementation 'junit:junit:4.12'
testImplementation "org.mockito:mockito-core:2.12.0"
}
app/build.gradle app/src/test/java/
● Pure JUnit test
#droidconvn
@ericntd
B) Write test for Presenter - Example
package tech.ericntd.githubsearch.search;
public class SearchPresenterTest {
@Test
public void searchGitHubRepos() {
String searchQuery = "some query";
// Trigger
presenter.searchGitHubRepos(searchQuery);
// Validation
Mockito.verify(repository,
Mockito.times(1)).searchRepos(searchQuery,
presenter);
}
}
package tech.ericntd.githubsearch.search;
public class SearchPresenter implements
SearchPresenterContract,
GitHubRepository.GitHubRepositoryCallback {
@Override
public void searchGitHubRepos(@Nullable final String
query) {
if (query != null && query.length() > 0) {
repository.searchRepos(query, this);
}
}
}
#droidconvn
@ericntd
B) Write test for Presenter - Example
package tech.ericntd.githubsearch.search;
public class SearchPresenterTest {
@Test
public void searchGitHubRepos() {
String searchQuery = "some query";
// Trigger
presenter.searchGitHubRepos(searchQuery);
// Validation
Mockito.verify(repository,
Mockito.times(1)).searchRepos(searchQuery,
presenter);
}
}
package tech.ericntd.githubsearch.search;
public class SearchPresenter implements
SearchPresenterContract,
GitHubRepository.GitHubRepositoryCallback {
@Override
public void searchGitHubRepos(@Nullable final String
query) {
if (query != null && query.length() > 0) {
repository.searchRepos(query, this);
}
}
}
#droidconvn
@ericntd
B) Write test for Presenter - Example
package tech.ericntd.githubsearch.search;
public class SearchPresenterTest {
@Test
public void searchGitHubRepos() {
String searchQuery = "some query";
// Trigger
presenter.searchGitHubRepos(searchQuery);
// Validation
Mockito.verify(repository,
Mockito.times(1)).searchRepos(searchQuery,
presenter);
}
}
package tech.ericntd.githubsearch.search;
public class SearchPresenter implements
SearchPresenterContract,
GitHubRepository.GitHubRepositoryCallback {
@Override
public void searchGitHubRepos(@Nullable final String
query) {
if (query != null && query.length() > 0) {
repository.searchRepos(query, this);
}
}
}
#droidconvn
@ericntd
B) Write test for Presenter - Example
package tech.ericntd.githubsearch.search;
public class SearchPresenterTest {
@Test
public void searchGitHubRepos() {
String searchQuery = "some query";
// Trigger
presenter.searchGitHubRepos(searchQuery);
// Validation
Mockito.verify(repository,
Mockito.times(1)).searchRepos(searchQuery,
presenter);
}
}
package tech.ericntd.githubsearch.search;
public class SearchPresenter implements
SearchPresenterContract,
GitHubRepository.GitHubRepositoryCallback {
@Override
public void searchGitHubRepos(@Nullable final String
query) {
if (query != null && query.length() > 0) {
repository.searchRepos(query, this);
}
}
}
app/src/test/java/ app/src/main/java/
#droidconvn
@ericntd
C) Run test & Profit!
#droidconvn
@ericntd
C) Run test & Profit!
package tech.ericntd.githubsearch.search;
public class SearchPresenterTest {
@Test
public void searchGitHubRepos_noQuery() {
String searchQuery = null;
// Trigger
presenter.searchGitHubRepos(searchQuery);
// Validation
Mockito.verify(repository,
Mockito.never()).searchRepos(searchQuery, presenter);
}
}
package tech.ericntd.githubsearch.search;
public class SearchPresenter implements
SearchPresenterContract,
GitHubRepository.GitHubRepositoryCallback {
@Override
public void searchGitHubRepos(@Nullable final String
query) {
if (query != null && query.length() > 0) {
repository.searchRepos(query, this);
}
}
}
@ericntd
C) Run test & Profit!
@Test
public void handleGitHubResponse_Failure() {
Response response = Mockito.mock(Response.class);
Mockito.doReturn(false).when(response).isSuccessful();
// Trigger
presenter.handleGitHubResponse(response);
// Validation
Mockito.verify(viewContract,
Mockito.times(1)).displayError("E101 - System error");
}
@ericntd
@Test
public void handleGitHubResponse_EmptyResponse() {
Response response = Mockito.mock(Response.class);
Mockito.doReturn(true).when(response).isSuccessful();
Mockito.doReturn(null).when(response).body();
// Trigger
presenter.handleGitHubResponse(response);
// Validation
Mockito.verify(viewContract,
Mockito.times(1)).displayError("E102 - System error");
}
C) Run test & Profit!
@ericntd
@ericntd
7) MVP architecture limitations
public class MainActivity extends AppCompatActivity
implements SearchViewContract {
// ...
@Override
public void displaySearchResults(@NonNull
List<SearchResult> searchResults,
@Nullable Integer totalCount) {
rvAdapter.updateResults(searchResults);
tvStatus.setText(String.format(Locale.US, "Number of
results: %d", totalCount));
}
}
It looks like logic, but
why is it in the View/
Activity?
#droidconvn
@ericntd
7) MVP architecture limitations
● Real 70-line-function in
production code
● 4 functions like this in the same
Activity
● 200+ lines of “setText” and
“setVisibility” and co.
● Hundreds of lines of code
required in test class
● Dozens of Mockito mocks
required in test class ⇒
significantly higher test run time
#droidconvn
@ericntd
7) MVP architecture limitations
Introducing Model-View-ViewModel
Model-View-ViewModel
View (.xml layout)
ViewModel
Data
Binding
@ericntd
7) MVP architecture limitations
// no more logic here
MainActivity
<?xml version="1.0" encoding="utf-8"?>
<layout >
<data>
<variable
name="vm"
type="tech.ericntd.githubsearch.search.SearchViewModel" />
</data>
<android.support.constraint.ConstraintLayout>
<TextView
android:id="@+id/tv_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:text="@{vm.status}"
app:layout_constraintTop_toBottomOf="@id/et_search_query"
tools:text="Number of results: 1000000" />
</android.support.constraint.ConstraintLayout>
</layout>
activity_main.xml
@ericntd
7) MVP architecture limitations
public class SearchViewModel implements GitHubRepository
.GitHubRepositoryCallback {
public ObservableField<String> status = new
ObservableField<>();
@Override
public void handleGitHubResponse(@NonNull final
Response<SearchResponse> response) {
if (response.isSuccessful()) {
SearchResponse searchResponse = response.body();
if (searchResponse != null &&
searchResponse.getSearchResults() != null) {
renderSuccess(searchResponse);
}
}
}
private void renderSuccess(SearchResponse searchResponse) {
status.set(String.format(Locale.US, "Number of results: %d",
searchResponse
.getTotalCount()));
}
}
SearchViewModel
public class SearchViewModelTest {
@Test
public void renderSuccess() {
Response response = Mockito.mock(Response.class);
SearchResponse searchResponse =
Mockito.mock(SearchResponse.class);
Mockito.doReturn(true).when(response).isSuccessful();
Mockito.doReturn(searchResponse).when(response).body();
Mockito.doReturn(1001).when(searchResponse).getTotalCount();
// Trigger
viewModel.handleGitHubResponse(response);
// Validation
Assert.assertEquals("Number of results: 1001",
viewModel.status.get());
}
}
SearchViewModelTest
@ericntd
7) MVP architecture limitations
● Notes on MVVM
○ Your XML layout is not tested, so avoid complex logic
#droidconvn
@ericntd
Takeaways
Architecture your app for testing
Android testing articles series
Part 1: A beginner’s guide to automated (unit) testing (today’s talk)
Part 2: Better tests with MVVM + data binding
Part 3: Component UI tests with Espresso from 0 to 1
#droidconvn
@ericntd
References
https://github.com/googlesamples/android-architecture
https://codelabs.developers.google.com/codelabs/android-testing/#0
#droidconvn
@ericntd
Q & A
#droidconvn
@ericntd
Thank you
@ericntd
● @ericntd (LinkedIn, Medium, Github, StackoverFlow, Twitter)
● Send me your resume eric.nguyen at grab.com -
https://grab.careers/

More Related Content

What's hot

JUnit 5 - The Next Generation
JUnit 5 - The Next GenerationJUnit 5 - The Next Generation
JUnit 5 - The Next Generation
Kostadin Golev
 
Date Processing Attracts Bugs or 77 Defects in Qt 6
Date Processing Attracts Bugs or 77 Defects in Qt 6Date Processing Attracts Bugs or 77 Defects in Qt 6
Date Processing Attracts Bugs or 77 Defects in Qt 6
Andrey Karpov
 
Test or Go Fishing - a guide on how to write better Swift for iOS
Test or Go Fishing - a guide on how to write better Swift for iOSTest or Go Fishing - a guide on how to write better Swift for iOS
Test or Go Fishing - a guide on how to write better Swift for iOS
Paul Ardeleanu
 
JUnit Presentation
JUnit PresentationJUnit Presentation
JUnit Presentation
priya_trivedi
 
Advanced Dagger talk from 360andev
Advanced Dagger talk from 360andevAdvanced Dagger talk from 360andev
Advanced Dagger talk from 360andev
Mike Nakhimovich
 
Typescript barcelona
Typescript barcelonaTypescript barcelona
Typescript barcelona
Christoffer Noring
 
Unit test
Unit testUnit test
Why Unit Testingl
Why Unit TestinglWhy Unit Testingl
Why Unit Testingl
priya_trivedi
 
Junit With Eclipse
Junit With EclipseJunit With Eclipse
Junit With Eclipse
Sunil kumar Mohanty
 
RPG Program for Unit Testing RPG
RPG Program for Unit Testing RPG RPG Program for Unit Testing RPG
RPG Program for Unit Testing RPG
Greg.Helton
 
Testing and Mocking Object - The Art of Mocking.
Testing and Mocking Object - The Art of Mocking.Testing and Mocking Object - The Art of Mocking.
Testing and Mocking Object - The Art of Mocking.
Deepak Singhvi
 
An Introduction to JUnit 5 and how to use it with Spring boot tests and Mockito
An Introduction to JUnit 5 and how to use it with Spring boot tests and MockitoAn Introduction to JUnit 5 and how to use it with Spring boot tests and Mockito
An Introduction to JUnit 5 and how to use it with Spring boot tests and Mockito
shaunthomas999
 
Sony C#/.NET component set analysis
Sony C#/.NET component set analysisSony C#/.NET component set analysis
Sony C#/.NET component set analysis
PVS-Studio
 
Overview of Android Infrastructure
Overview of Android InfrastructureOverview of Android Infrastructure
Overview of Android Infrastructure
Alexey Buzdin
 
Test or Go Fishing - a guide on how to write better Swift for iOS
Test or Go Fishing - a guide on how to write better Swift for iOSTest or Go Fishing - a guide on how to write better Swift for iOS
Test or Go Fishing - a guide on how to write better Swift for iOS
Paul Ardeleanu
 
It's complicated, but it doesn't have to be: a Dagger journey
It's complicated, but it doesn't have to be: a Dagger journeyIt's complicated, but it doesn't have to be: a Dagger journey
It's complicated, but it doesn't have to be: a Dagger journey
Thiago “Fred” Porciúncula
 
Guide to the jungle of testing frameworks
Guide to the jungle of testing frameworksGuide to the jungle of testing frameworks
Guide to the jungle of testing frameworks
Tomáš Kypta
 
A fresh eye on Oracle VM VirtualBox
A fresh eye on Oracle VM VirtualBoxA fresh eye on Oracle VM VirtualBox
A fresh eye on Oracle VM VirtualBox
PVS-Studio
 
Testing of javacript
Testing of javacriptTesting of javacript
Testing of javacript
Lei Kang
 
Test or Go Fishing - A guide on how to write better Swift for iOS
Test or Go Fishing - A guide on how to write better Swift for iOSTest or Go Fishing - A guide on how to write better Swift for iOS
Test or Go Fishing - A guide on how to write better Swift for iOS
Paul Ardeleanu
 

What's hot (20)

JUnit 5 - The Next Generation
JUnit 5 - The Next GenerationJUnit 5 - The Next Generation
JUnit 5 - The Next Generation
 
Date Processing Attracts Bugs or 77 Defects in Qt 6
Date Processing Attracts Bugs or 77 Defects in Qt 6Date Processing Attracts Bugs or 77 Defects in Qt 6
Date Processing Attracts Bugs or 77 Defects in Qt 6
 
Test or Go Fishing - a guide on how to write better Swift for iOS
Test or Go Fishing - a guide on how to write better Swift for iOSTest or Go Fishing - a guide on how to write better Swift for iOS
Test or Go Fishing - a guide on how to write better Swift for iOS
 
JUnit Presentation
JUnit PresentationJUnit Presentation
JUnit Presentation
 
Advanced Dagger talk from 360andev
Advanced Dagger talk from 360andevAdvanced Dagger talk from 360andev
Advanced Dagger talk from 360andev
 
Typescript barcelona
Typescript barcelonaTypescript barcelona
Typescript barcelona
 
Unit test
Unit testUnit test
Unit test
 
Why Unit Testingl
Why Unit TestinglWhy Unit Testingl
Why Unit Testingl
 
Junit With Eclipse
Junit With EclipseJunit With Eclipse
Junit With Eclipse
 
RPG Program for Unit Testing RPG
RPG Program for Unit Testing RPG RPG Program for Unit Testing RPG
RPG Program for Unit Testing RPG
 
Testing and Mocking Object - The Art of Mocking.
Testing and Mocking Object - The Art of Mocking.Testing and Mocking Object - The Art of Mocking.
Testing and Mocking Object - The Art of Mocking.
 
An Introduction to JUnit 5 and how to use it with Spring boot tests and Mockito
An Introduction to JUnit 5 and how to use it with Spring boot tests and MockitoAn Introduction to JUnit 5 and how to use it with Spring boot tests and Mockito
An Introduction to JUnit 5 and how to use it with Spring boot tests and Mockito
 
Sony C#/.NET component set analysis
Sony C#/.NET component set analysisSony C#/.NET component set analysis
Sony C#/.NET component set analysis
 
Overview of Android Infrastructure
Overview of Android InfrastructureOverview of Android Infrastructure
Overview of Android Infrastructure
 
Test or Go Fishing - a guide on how to write better Swift for iOS
Test or Go Fishing - a guide on how to write better Swift for iOSTest or Go Fishing - a guide on how to write better Swift for iOS
Test or Go Fishing - a guide on how to write better Swift for iOS
 
It's complicated, but it doesn't have to be: a Dagger journey
It's complicated, but it doesn't have to be: a Dagger journeyIt's complicated, but it doesn't have to be: a Dagger journey
It's complicated, but it doesn't have to be: a Dagger journey
 
Guide to the jungle of testing frameworks
Guide to the jungle of testing frameworksGuide to the jungle of testing frameworks
Guide to the jungle of testing frameworks
 
A fresh eye on Oracle VM VirtualBox
A fresh eye on Oracle VM VirtualBoxA fresh eye on Oracle VM VirtualBox
A fresh eye on Oracle VM VirtualBox
 
Testing of javacript
Testing of javacriptTesting of javacript
Testing of javacript
 
Test or Go Fishing - A guide on how to write better Swift for iOS
Test or Go Fishing - A guide on how to write better Swift for iOSTest or Go Fishing - A guide on how to write better Swift for iOS
Test or Go Fishing - A guide on how to write better Swift for iOS
 

Similar to The real beginner's guide to android testing

Design for Testability
Design for TestabilityDesign for Testability
Design for Testability
Stefano Dalla Palma
 
Mutation testing Bucharest Tech Week
Mutation testing Bucharest Tech WeekMutation testing Bucharest Tech Week
Mutation testing Bucharest Tech Week
Paco van Beckhoven
 
Test driven development
Test driven developmentTest driven development
Test driven development
christoforosnalmpantis
 
Improve unit tests with Mutants!
Improve unit tests with Mutants!Improve unit tests with Mutants!
Improve unit tests with Mutants!
Paco van Beckhoven
 
Unit testing with JUnit
Unit testing with JUnitUnit testing with JUnit
Unit testing with JUnit
Pokpitch Patcharadamrongkul
 
Unit Testing in Flutter - From Workflow Essentials to Complex Scenarios
Unit Testing in Flutter - From Workflow Essentials to Complex ScenariosUnit Testing in Flutter - From Workflow Essentials to Complex Scenarios
Unit Testing in Flutter - From Workflow Essentials to Complex Scenarios
Flutter Agency
 
Tdd is not about testing (OOP)
Tdd is not about testing (OOP)Tdd is not about testing (OOP)
Tdd is not about testing (OOP)
Gianluca Padovani
 
Agile mobile
Agile mobileAgile mobile
Agile mobile
Godfrey Nolan
 
Grails unit testing
Grails unit testingGrails unit testing
Grails unit testing
pleeps
 
Security Testing
Security TestingSecurity Testing
Security Testing
Kiran Kumar
 
Unit & Automation Testing in Android - Stanislav Gatsev, Melon
Unit & Automation Testing in Android - Stanislav Gatsev, MelonUnit & Automation Testing in Android - Stanislav Gatsev, Melon
Unit & Automation Testing in Android - Stanislav Gatsev, Melon
beITconference
 
How to write clean tests
How to write clean testsHow to write clean tests
How to write clean tests
Danylenko Max
 
31b - JUnit and Mockito.pdf
31b - JUnit and Mockito.pdf31b - JUnit and Mockito.pdf
31b - JUnit and Mockito.pdf
gauravavam
 
GlobalLogic Test Automation Online TechTalk “Test Driven Development as a Per...
GlobalLogic Test Automation Online TechTalk “Test Driven Development as a Per...GlobalLogic Test Automation Online TechTalk “Test Driven Development as a Per...
GlobalLogic Test Automation Online TechTalk “Test Driven Development as a Per...
GlobalLogic Ukraine
 
Android Test Automation Workshop
Android Test Automation WorkshopAndroid Test Automation Workshop
Android Test Automation Workshop
Eduardo Carrara de Araujo
 
Mutation Analysis for JavaScript Web Applicaiton Testing SEKE2013
Mutation Analysis for JavaScript Web Applicaiton Testing  SEKE2013Mutation Analysis for JavaScript Web Applicaiton Testing  SEKE2013
Mutation Analysis for JavaScript Web Applicaiton Testing SEKE2013
nkazuki
 
Unit testing
Unit testingUnit testing
Unit testing
Pooya Sagharchiha
 
Describe's Full of It's
Describe's Full of It'sDescribe's Full of It's
Describe's Full of It's
Jim Lynch
 
谷歌 Scott-lessons learned in testability
谷歌 Scott-lessons learned in testability谷歌 Scott-lessons learned in testability
谷歌 Scott-lessons learned in testability
drewz lin
 
Fundamentals of unit testing
Fundamentals of unit testingFundamentals of unit testing
Fundamentals of unit testing
Chamoda Pandithage
 

Similar to The real beginner's guide to android testing (20)

Design for Testability
Design for TestabilityDesign for Testability
Design for Testability
 
Mutation testing Bucharest Tech Week
Mutation testing Bucharest Tech WeekMutation testing Bucharest Tech Week
Mutation testing Bucharest Tech Week
 
Test driven development
Test driven developmentTest driven development
Test driven development
 
Improve unit tests with Mutants!
Improve unit tests with Mutants!Improve unit tests with Mutants!
Improve unit tests with Mutants!
 
Unit testing with JUnit
Unit testing with JUnitUnit testing with JUnit
Unit testing with JUnit
 
Unit Testing in Flutter - From Workflow Essentials to Complex Scenarios
Unit Testing in Flutter - From Workflow Essentials to Complex ScenariosUnit Testing in Flutter - From Workflow Essentials to Complex Scenarios
Unit Testing in Flutter - From Workflow Essentials to Complex Scenarios
 
Tdd is not about testing (OOP)
Tdd is not about testing (OOP)Tdd is not about testing (OOP)
Tdd is not about testing (OOP)
 
Agile mobile
Agile mobileAgile mobile
Agile mobile
 
Grails unit testing
Grails unit testingGrails unit testing
Grails unit testing
 
Security Testing
Security TestingSecurity Testing
Security Testing
 
Unit & Automation Testing in Android - Stanislav Gatsev, Melon
Unit & Automation Testing in Android - Stanislav Gatsev, MelonUnit & Automation Testing in Android - Stanislav Gatsev, Melon
Unit & Automation Testing in Android - Stanislav Gatsev, Melon
 
How to write clean tests
How to write clean testsHow to write clean tests
How to write clean tests
 
31b - JUnit and Mockito.pdf
31b - JUnit and Mockito.pdf31b - JUnit and Mockito.pdf
31b - JUnit and Mockito.pdf
 
GlobalLogic Test Automation Online TechTalk “Test Driven Development as a Per...
GlobalLogic Test Automation Online TechTalk “Test Driven Development as a Per...GlobalLogic Test Automation Online TechTalk “Test Driven Development as a Per...
GlobalLogic Test Automation Online TechTalk “Test Driven Development as a Per...
 
Android Test Automation Workshop
Android Test Automation WorkshopAndroid Test Automation Workshop
Android Test Automation Workshop
 
Mutation Analysis for JavaScript Web Applicaiton Testing SEKE2013
Mutation Analysis for JavaScript Web Applicaiton Testing  SEKE2013Mutation Analysis for JavaScript Web Applicaiton Testing  SEKE2013
Mutation Analysis for JavaScript Web Applicaiton Testing SEKE2013
 
Unit testing
Unit testingUnit testing
Unit testing
 
Describe's Full of It's
Describe's Full of It'sDescribe's Full of It's
Describe's Full of It's
 
谷歌 Scott-lessons learned in testability
谷歌 Scott-lessons learned in testability谷歌 Scott-lessons learned in testability
谷歌 Scott-lessons learned in testability
 
Fundamentals of unit testing
Fundamentals of unit testingFundamentals of unit testing
Fundamentals of unit testing
 

Recently uploaded

Unlock the Future of Search with MongoDB Atlas_ Vector Search Unleashed.pdf
Unlock the Future of Search with MongoDB Atlas_ Vector Search Unleashed.pdfUnlock the Future of Search with MongoDB Atlas_ Vector Search Unleashed.pdf
Unlock the Future of Search with MongoDB Atlas_ Vector Search Unleashed.pdf
Malak Abu Hammad
 
5th LF Energy Power Grid Model Meet-up Slides
5th LF Energy Power Grid Model Meet-up Slides5th LF Energy Power Grid Model Meet-up Slides
5th LF Energy Power Grid Model Meet-up Slides
DanBrown980551
 
How to use Firebase Data Connect For Flutter
How to use Firebase Data Connect For FlutterHow to use Firebase Data Connect For Flutter
How to use Firebase Data Connect For Flutter
Daiki Mogmet Ito
 
GraphRAG for Life Science to increase LLM accuracy
GraphRAG for Life Science to increase LLM accuracyGraphRAG for Life Science to increase LLM accuracy
GraphRAG for Life Science to increase LLM accuracy
Tomaz Bratanic
 
Building Production Ready Search Pipelines with Spark and Milvus
Building Production Ready Search Pipelines with Spark and MilvusBuilding Production Ready Search Pipelines with Spark and Milvus
Building Production Ready Search Pipelines with Spark and Milvus
Zilliz
 
Energy Efficient Video Encoding for Cloud and Edge Computing Instances
Energy Efficient Video Encoding for Cloud and Edge Computing InstancesEnergy Efficient Video Encoding for Cloud and Edge Computing Instances
Energy Efficient Video Encoding for Cloud and Edge Computing Instances
Alpen-Adria-Universität
 
UiPath Test Automation using UiPath Test Suite series, part 6
UiPath Test Automation using UiPath Test Suite series, part 6UiPath Test Automation using UiPath Test Suite series, part 6
UiPath Test Automation using UiPath Test Suite series, part 6
DianaGray10
 
How to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdf
How to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdfHow to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdf
How to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdf
Chart Kalyan
 
Let's Integrate MuleSoft RPA, COMPOSER, APM with AWS IDP along with Slack
Let's Integrate MuleSoft RPA, COMPOSER, APM with AWS IDP along with SlackLet's Integrate MuleSoft RPA, COMPOSER, APM with AWS IDP along with Slack
Let's Integrate MuleSoft RPA, COMPOSER, APM with AWS IDP along with Slack
shyamraj55
 
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial IntelligenceAI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
IndexBug
 
Monitoring and Managing Anomaly Detection on OpenShift.pdf
Monitoring and Managing Anomaly Detection on OpenShift.pdfMonitoring and Managing Anomaly Detection on OpenShift.pdf
Monitoring and Managing Anomaly Detection on OpenShift.pdf
Tosin Akinosho
 
Cosa hanno in comune un mattoncino Lego e la backdoor XZ?
Cosa hanno in comune un mattoncino Lego e la backdoor XZ?Cosa hanno in comune un mattoncino Lego e la backdoor XZ?
Cosa hanno in comune un mattoncino Lego e la backdoor XZ?
Speck&Tech
 
HCL Notes und Domino Lizenzkostenreduzierung in der Welt von DLAU
HCL Notes und Domino Lizenzkostenreduzierung in der Welt von DLAUHCL Notes und Domino Lizenzkostenreduzierung in der Welt von DLAU
HCL Notes und Domino Lizenzkostenreduzierung in der Welt von DLAU
panagenda
 
June Patch Tuesday
June Patch TuesdayJune Patch Tuesday
June Patch Tuesday
Ivanti
 
Introduction of Cybersecurity with OSS at Code Europe 2024
Introduction of Cybersecurity with OSS  at Code Europe 2024Introduction of Cybersecurity with OSS  at Code Europe 2024
Introduction of Cybersecurity with OSS at Code Europe 2024
Hiroshi SHIBATA
 
Digital Marketing Trends in 2024 | Guide for Staying Ahead
Digital Marketing Trends in 2024 | Guide for Staying AheadDigital Marketing Trends in 2024 | Guide for Staying Ahead
Digital Marketing Trends in 2024 | Guide for Staying Ahead
Wask
 
WeTestAthens: Postman's AI & Automation Techniques
WeTestAthens: Postman's AI & Automation TechniquesWeTestAthens: Postman's AI & Automation Techniques
WeTestAthens: Postman's AI & Automation Techniques
Postman
 
Taking AI to the Next Level in Manufacturing.pdf
Taking AI to the Next Level in Manufacturing.pdfTaking AI to the Next Level in Manufacturing.pdf
Taking AI to the Next Level in Manufacturing.pdf
ssuserfac0301
 
HCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAUHCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAU
panagenda
 
How to Get CNIC Information System with Paksim Ga.pptx
How to Get CNIC Information System with Paksim Ga.pptxHow to Get CNIC Information System with Paksim Ga.pptx
How to Get CNIC Information System with Paksim Ga.pptx
danishmna97
 

Recently uploaded (20)

Unlock the Future of Search with MongoDB Atlas_ Vector Search Unleashed.pdf
Unlock the Future of Search with MongoDB Atlas_ Vector Search Unleashed.pdfUnlock the Future of Search with MongoDB Atlas_ Vector Search Unleashed.pdf
Unlock the Future of Search with MongoDB Atlas_ Vector Search Unleashed.pdf
 
5th LF Energy Power Grid Model Meet-up Slides
5th LF Energy Power Grid Model Meet-up Slides5th LF Energy Power Grid Model Meet-up Slides
5th LF Energy Power Grid Model Meet-up Slides
 
How to use Firebase Data Connect For Flutter
How to use Firebase Data Connect For FlutterHow to use Firebase Data Connect For Flutter
How to use Firebase Data Connect For Flutter
 
GraphRAG for Life Science to increase LLM accuracy
GraphRAG for Life Science to increase LLM accuracyGraphRAG for Life Science to increase LLM accuracy
GraphRAG for Life Science to increase LLM accuracy
 
Building Production Ready Search Pipelines with Spark and Milvus
Building Production Ready Search Pipelines with Spark and MilvusBuilding Production Ready Search Pipelines with Spark and Milvus
Building Production Ready Search Pipelines with Spark and Milvus
 
Energy Efficient Video Encoding for Cloud and Edge Computing Instances
Energy Efficient Video Encoding for Cloud and Edge Computing InstancesEnergy Efficient Video Encoding for Cloud and Edge Computing Instances
Energy Efficient Video Encoding for Cloud and Edge Computing Instances
 
UiPath Test Automation using UiPath Test Suite series, part 6
UiPath Test Automation using UiPath Test Suite series, part 6UiPath Test Automation using UiPath Test Suite series, part 6
UiPath Test Automation using UiPath Test Suite series, part 6
 
How to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdf
How to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdfHow to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdf
How to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdf
 
Let's Integrate MuleSoft RPA, COMPOSER, APM with AWS IDP along with Slack
Let's Integrate MuleSoft RPA, COMPOSER, APM with AWS IDP along with SlackLet's Integrate MuleSoft RPA, COMPOSER, APM with AWS IDP along with Slack
Let's Integrate MuleSoft RPA, COMPOSER, APM with AWS IDP along with Slack
 
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial IntelligenceAI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
 
Monitoring and Managing Anomaly Detection on OpenShift.pdf
Monitoring and Managing Anomaly Detection on OpenShift.pdfMonitoring and Managing Anomaly Detection on OpenShift.pdf
Monitoring and Managing Anomaly Detection on OpenShift.pdf
 
Cosa hanno in comune un mattoncino Lego e la backdoor XZ?
Cosa hanno in comune un mattoncino Lego e la backdoor XZ?Cosa hanno in comune un mattoncino Lego e la backdoor XZ?
Cosa hanno in comune un mattoncino Lego e la backdoor XZ?
 
HCL Notes und Domino Lizenzkostenreduzierung in der Welt von DLAU
HCL Notes und Domino Lizenzkostenreduzierung in der Welt von DLAUHCL Notes und Domino Lizenzkostenreduzierung in der Welt von DLAU
HCL Notes und Domino Lizenzkostenreduzierung in der Welt von DLAU
 
June Patch Tuesday
June Patch TuesdayJune Patch Tuesday
June Patch Tuesday
 
Introduction of Cybersecurity with OSS at Code Europe 2024
Introduction of Cybersecurity with OSS  at Code Europe 2024Introduction of Cybersecurity with OSS  at Code Europe 2024
Introduction of Cybersecurity with OSS at Code Europe 2024
 
Digital Marketing Trends in 2024 | Guide for Staying Ahead
Digital Marketing Trends in 2024 | Guide for Staying AheadDigital Marketing Trends in 2024 | Guide for Staying Ahead
Digital Marketing Trends in 2024 | Guide for Staying Ahead
 
WeTestAthens: Postman's AI & Automation Techniques
WeTestAthens: Postman's AI & Automation TechniquesWeTestAthens: Postman's AI & Automation Techniques
WeTestAthens: Postman's AI & Automation Techniques
 
Taking AI to the Next Level in Manufacturing.pdf
Taking AI to the Next Level in Manufacturing.pdfTaking AI to the Next Level in Manufacturing.pdf
Taking AI to the Next Level in Manufacturing.pdf
 
HCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAUHCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAU
 
How to Get CNIC Information System with Paksim Ga.pptx
How to Get CNIC Information System with Paksim Ga.pptxHow to Get CNIC Information System with Paksim Ga.pptx
How to Get CNIC Information System with Paksim Ga.pptx
 

The real beginner's guide to android testing

  • 1. The real beginner’s guide to Android testing Eric Nguyen
  • 2. About myself ● Software Engineer at Grab ● DragonBall fan (Vegeta fan) ● @ericntd (LinkedIn, Medium, Github, StackoverFlow, Twitter) ● Send me your resume eric.nguyen at grab.com - https://grab.careers/ #droidconvn
  • 3. My journey with unit testing Before unit tests With unit tests #droidconvn @ericntd
  • 4. Agenda 1. Why write tests 2. Types of tests 3. Introduction to unit testing 4. Challenge to unit testing with Android 5. The solution 6. Steps to your first Android unit tests 7. MVP architecture limitations 8. Q & A @ericntd
  • 5. 1) Why write tests? ● Improve and maintain product quality with less QA’s manual effort ● Increase confidence when shipping ● Perform routine exhaustive checks humans can’t perform, fast ● Help you write more modular code @ericntd
  • 6. 2) Different of type of tests Unit tests Component/ Integration Tests End-to- end tests 20-30 mins to run 100++ component UI tests <10 mins to run 4000 tests Fast to run, easy to write, run and to measure coverage Slow to run, difficult to write, run and to measure coverage #droidconvn @ericntd
  • 7. 3) Unit testing crash course public class Calculator { /** * @param input an integer in the range of [Integer.MIN_VALUE/2, Integer.MAX_VALUE/2] * @return -1 if the input is too big or too small, otherwise the input times 2 */ public int timesTwo(int input) { if (input > Integer.MAX_VALUE / 2 || input < Integer.MIN_VALUE / 2) { return -1; } return input * 2; } } ● We have a Calculator class with a single timesTwo method ● The timesTwo method returns the input times 2, except for in the case of overflow, it returns -1 @ericntd
  • 8. 3) Unit testing crash course dependencies { //... testImplementation 'junit:junit:4.12' testImplementation "org.mockito:mockito-core:2.12.0" } app/build.gradle #droidconvn public class CalculatorTest { private Calculator calculator = new Calculator(); @Test public void plusTwo() { Assert.assertEquals(0, calculator.timesTwo(0)); Assert.assertEquals(2, calculator.timesTwo(1)); } } app/src/test/java/your.package.name @ericntd
  • 9. 3) Unit testing crash course public class CalculatorTest { private Calculator calculator = new Calculator(); @Test public void plusTwo() { Assert.assertEquals(0, calculator.timesTwo(0)); Assert.assertEquals(2, calculator.timesTwo(1)); Assert.assertEquals(-2, calculator.timesTwo(-1)); Assert.assertEquals(-1, calculator.timesTwo(Integer.MAX_VALUE)); Assert.assertEquals(-1, calculator.timesTwo(Integer.MIN_VALUE)); Assert.assertEquals(2147483646, calculator.timesTwo(Integer.MAX_VALUE / 2)); Assert.assertEquals(-1, calculator.timesTwo(Integer.MAX_VALUE / 2 + 1)); Assert.assertEquals(-2147483648, calculator.timesTwo(Integer.MIN_VALUE / 2)); Assert.assertEquals(-1, calculator.timesTwo(Integer.MIN_VALUE / 2 - 1)); } } @ericntd
  • 10. 3) Unit testing crash course ● Our test passes! @ericntd
  • 11. 3) Unit testing crash course ● Unit tests help our app’s stability @ericntd
  • 12. 3) Unit testing crash course Unit tests also force us to write modular code “The first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that. Functions should not be 100 lines long. Functions should hardly ever be 20 lines long.” - Clean Code @ericntd
  • 13. 4) Challenge of unit testing in Android ● Consider a simple activity: ○ A TextView display a number, starting from 1 ○ A Button named “Time Two” ● The doubling logic is from our Calculator’s timesTwo @ericntd
  • 14. 4) Challenge of unit testing in Android public class CalculatorActivity extends AppCompatActivity { ` public Calculator calculator; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_calculator); calculator = new Calculator(); final TextView tvNumber = findViewById(R.id.tv_number); Button ctaTimesTwo = findViewById(R.id.cta_times_two); ctaTimesTwo.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { updateNumber(tvNumber); } }); } public void updateNumber(TextView tvNumber) { tvNumber.setText(calculator.timesTwo(Integer.valueOf(tvNumber.getText().toString()))); } } CalculatorActivity is the Controller in a Model-View- Controller @ericntd
  • 15. 4) Challenge of unit testing in Android public class CalculatorActivity extends AppCompatActivity { public Calculator calculator; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_calculator); calculator = new Calculator(); final TextView tvNumber = findViewById(R.id.tv_number); Button ctaTimesTwo = findViewById(R.id.cta_times_two); ctaTimesTwo.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { updateNumber(tvNumber); } }); } public void updateNumber(TextView tvNumber) { tvNumber.setText(calculator.timesTwo(Integer.valueOf(tvNumber.getText().toString()))); } } Doubling logic is based on Calculator’s timesTwo @ericntd
  • 16. 4) Challenge of unit testing in Android public class CalculatorActivityTest { private CalculatorActivity activity = new CalculatorActivity(); @Test public void updateNumber() { // Preparation TextView tvNumber = Mockito.mock(TextView.class); Mockito.doReturn("1").when(tvNumber).getText(); // Trigger activity.updateNumber(tvNumber); // Validation Assert.assertEquals("2", tvNumber.getText().toString()); Mockito.verify(tvNumber).setText(2); } } ● Prepare any dependency incl. Android-specific easily ● Verify a method is called on a mocked object @ericntd
  • 17. 4) Challenge of unit testing in Android We run CalculatorActivityTest and we get a NullPointerException. Explanation: The Calculator object was instantiated inside the Activity’s onCreate method. In our JUnit test, we have no control over the CalculatorActivity’s onCreate method Conclusion: We are unable to unit test our business logics inside an Android Activity @ericntd
  • 18. 4) Challenge of unit testing in Android Possible Workarounds: ● Instantiate a Calculator object inside updateNumber method itself ○ Inefficiency - multiple Calculator objects instead of reusing one ● Create a setter in the the activity: setCalculator(Caculator) ○ Side effects e.g. race condition, unexpected behaviours @ericntd
  • 19. 5) Solution for unit testing in Android ● We need to move our business logics outside of the Activity or Fragment ● We need to refactor our app from MVC to a better architecture such as Model-View-Presenter (MVP) @ericntd
  • 20. 5) Solution for unit testing in Android - Architectures #droidconvn ● Difficult to test logics within Activity/ Fragment ● Activity/ Fragment are bloated Model-View-Presenter View (Fragment/ Activity) Presenter Activity/ Fragment Model-View-Controller Controller View ● Business logics easily tested ● Activity/ Fragment much thinner without business logics @ericntd
  • 21. 6) Steps to your first Android unit tests A. Refactor your app to MVP B. Write tests for your Presenter (contains business logics) C. Profit! #droidconvn @ericntd
  • 22. Sample app: GitHub Search https://github.com/ericntd/Github-Search @ericntd
  • 23. A)MVC => MVP + #droidconvn public class MainActivity extends AppCompatActivity { //... private void searchGitHubRepos(GitHubApi gitHubApi, String query) { gitHubApi.searchRepos(query).enqueue(new Callback<SearchResponse>() { @Override public void onResponse(Call<SearchResponse> call, Response<SearchResponse> response) { handleResponse(response); } @Override public void onFailure(Call<SearchResponse> call, Throwable t) { handleError("E103 - System error"); } }); } } Original MainActivity in a MVC settings. 2 business logics methods: ● searchGitHubRepos ● handleSearchGitHubResp onse We will move these 2 methods into the Presenter class @ericntd
  • 24. A)MVC => MVP public class SearchPresenter implements SearchPresenterContract, GitHubRepository .GitHubRepositoryCallback { private final SearchViewContract viewContract; private final GitHubRepository repository; //... @Override public void searchGitHubRepos(@Nullable final String query) { if (query != null && query.length() > 0) { repository.searchRepos(query, this); } } } The Presenter now contains the 2 business logics methods: ● searchGitHubRepos ● handleSearchGitHubResp onse @ericntd
  • 25. A)MVC => MVP public class MainActivity extends AppCompatActivity implements SearchViewContract { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //... } @Override public void displaySearchResults(@NonNull List<SearchResult> searchResults, @Nullable Integer totalCount) { rvAdapter.updateResults(searchResults); tvStatus.setText(String.format(Locale.US, "Number of results: %d", totalCount)); } @Override public void displayError() { Toast.makeText(this, "some error happened", Toast.LENGTH_SHORT).show(); } @Override public void displayError(String s) { Toast.makeText(this, s, Toast.LENGTH_SHORT).show(); } Our new Activity is much cleaner with only ~50 lines of code @ericntd
  • 26. B) Write tests for Presenter - Setup package tech.ericntd.githubsearch.search; public class SearchPresenterTest { private SearchPresenter presenter; @Mock private GitHubRepository repository; @Mock private SearchViewContract viewContract; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this);// required for the "@Mock" annotations // Make presenter a mock while using mock repository and viewContract created above presenter = Mockito.spy(new SearchPresenter(viewContract, repository)); } // The tests } dependencies { //... testImplementation 'junit:junit:4.12' testImplementation "org.mockito:mockito-core:2.12.0" } app/build.gradle app/src/test/java/ ● Pure JUnit test #droidconvn @ericntd
  • 27. B) Write test for Presenter - Example package tech.ericntd.githubsearch.search; public class SearchPresenterTest { @Test public void searchGitHubRepos() { String searchQuery = "some query"; // Trigger presenter.searchGitHubRepos(searchQuery); // Validation Mockito.verify(repository, Mockito.times(1)).searchRepos(searchQuery, presenter); } } package tech.ericntd.githubsearch.search; public class SearchPresenter implements SearchPresenterContract, GitHubRepository.GitHubRepositoryCallback { @Override public void searchGitHubRepos(@Nullable final String query) { if (query != null && query.length() > 0) { repository.searchRepos(query, this); } } } #droidconvn @ericntd
  • 28. B) Write test for Presenter - Example package tech.ericntd.githubsearch.search; public class SearchPresenterTest { @Test public void searchGitHubRepos() { String searchQuery = "some query"; // Trigger presenter.searchGitHubRepos(searchQuery); // Validation Mockito.verify(repository, Mockito.times(1)).searchRepos(searchQuery, presenter); } } package tech.ericntd.githubsearch.search; public class SearchPresenter implements SearchPresenterContract, GitHubRepository.GitHubRepositoryCallback { @Override public void searchGitHubRepos(@Nullable final String query) { if (query != null && query.length() > 0) { repository.searchRepos(query, this); } } } #droidconvn @ericntd
  • 29. B) Write test for Presenter - Example package tech.ericntd.githubsearch.search; public class SearchPresenterTest { @Test public void searchGitHubRepos() { String searchQuery = "some query"; // Trigger presenter.searchGitHubRepos(searchQuery); // Validation Mockito.verify(repository, Mockito.times(1)).searchRepos(searchQuery, presenter); } } package tech.ericntd.githubsearch.search; public class SearchPresenter implements SearchPresenterContract, GitHubRepository.GitHubRepositoryCallback { @Override public void searchGitHubRepos(@Nullable final String query) { if (query != null && query.length() > 0) { repository.searchRepos(query, this); } } } #droidconvn @ericntd
  • 30. B) Write test for Presenter - Example package tech.ericntd.githubsearch.search; public class SearchPresenterTest { @Test public void searchGitHubRepos() { String searchQuery = "some query"; // Trigger presenter.searchGitHubRepos(searchQuery); // Validation Mockito.verify(repository, Mockito.times(1)).searchRepos(searchQuery, presenter); } } package tech.ericntd.githubsearch.search; public class SearchPresenter implements SearchPresenterContract, GitHubRepository.GitHubRepositoryCallback { @Override public void searchGitHubRepos(@Nullable final String query) { if (query != null && query.length() > 0) { repository.searchRepos(query, this); } } } app/src/test/java/ app/src/main/java/ #droidconvn @ericntd
  • 31. C) Run test & Profit! #droidconvn @ericntd
  • 32. C) Run test & Profit! package tech.ericntd.githubsearch.search; public class SearchPresenterTest { @Test public void searchGitHubRepos_noQuery() { String searchQuery = null; // Trigger presenter.searchGitHubRepos(searchQuery); // Validation Mockito.verify(repository, Mockito.never()).searchRepos(searchQuery, presenter); } } package tech.ericntd.githubsearch.search; public class SearchPresenter implements SearchPresenterContract, GitHubRepository.GitHubRepositoryCallback { @Override public void searchGitHubRepos(@Nullable final String query) { if (query != null && query.length() > 0) { repository.searchRepos(query, this); } } } @ericntd
  • 33. C) Run test & Profit! @Test public void handleGitHubResponse_Failure() { Response response = Mockito.mock(Response.class); Mockito.doReturn(false).when(response).isSuccessful(); // Trigger presenter.handleGitHubResponse(response); // Validation Mockito.verify(viewContract, Mockito.times(1)).displayError("E101 - System error"); } @ericntd @Test public void handleGitHubResponse_EmptyResponse() { Response response = Mockito.mock(Response.class); Mockito.doReturn(true).when(response).isSuccessful(); Mockito.doReturn(null).when(response).body(); // Trigger presenter.handleGitHubResponse(response); // Validation Mockito.verify(viewContract, Mockito.times(1)).displayError("E102 - System error"); }
  • 34. C) Run test & Profit! @ericntd
  • 36. 7) MVP architecture limitations public class MainActivity extends AppCompatActivity implements SearchViewContract { // ... @Override public void displaySearchResults(@NonNull List<SearchResult> searchResults, @Nullable Integer totalCount) { rvAdapter.updateResults(searchResults); tvStatus.setText(String.format(Locale.US, "Number of results: %d", totalCount)); } } It looks like logic, but why is it in the View/ Activity? #droidconvn @ericntd
  • 37. 7) MVP architecture limitations ● Real 70-line-function in production code ● 4 functions like this in the same Activity ● 200+ lines of “setText” and “setVisibility” and co. ● Hundreds of lines of code required in test class ● Dozens of Mockito mocks required in test class ⇒ significantly higher test run time #droidconvn @ericntd
  • 38. 7) MVP architecture limitations Introducing Model-View-ViewModel Model-View-ViewModel View (.xml layout) ViewModel Data Binding @ericntd
  • 39. 7) MVP architecture limitations // no more logic here MainActivity <?xml version="1.0" encoding="utf-8"?> <layout > <data> <variable name="vm" type="tech.ericntd.githubsearch.search.SearchViewModel" /> </data> <android.support.constraint.ConstraintLayout> <TextView android:id="@+id/tv_status" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingEnd="16dp" android:paddingStart="16dp" android:text="@{vm.status}" app:layout_constraintTop_toBottomOf="@id/et_search_query" tools:text="Number of results: 1000000" /> </android.support.constraint.ConstraintLayout> </layout> activity_main.xml @ericntd
  • 40. 7) MVP architecture limitations public class SearchViewModel implements GitHubRepository .GitHubRepositoryCallback { public ObservableField<String> status = new ObservableField<>(); @Override public void handleGitHubResponse(@NonNull final Response<SearchResponse> response) { if (response.isSuccessful()) { SearchResponse searchResponse = response.body(); if (searchResponse != null && searchResponse.getSearchResults() != null) { renderSuccess(searchResponse); } } } private void renderSuccess(SearchResponse searchResponse) { status.set(String.format(Locale.US, "Number of results: %d", searchResponse .getTotalCount())); } } SearchViewModel public class SearchViewModelTest { @Test public void renderSuccess() { Response response = Mockito.mock(Response.class); SearchResponse searchResponse = Mockito.mock(SearchResponse.class); Mockito.doReturn(true).when(response).isSuccessful(); Mockito.doReturn(searchResponse).when(response).body(); Mockito.doReturn(1001).when(searchResponse).getTotalCount(); // Trigger viewModel.handleGitHubResponse(response); // Validation Assert.assertEquals("Number of results: 1001", viewModel.status.get()); } } SearchViewModelTest @ericntd
  • 41. 7) MVP architecture limitations ● Notes on MVVM ○ Your XML layout is not tested, so avoid complex logic #droidconvn @ericntd
  • 43. Android testing articles series Part 1: A beginner’s guide to automated (unit) testing (today’s talk) Part 2: Better tests with MVVM + data binding Part 3: Component UI tests with Espresso from 0 to 1 #droidconvn @ericntd
  • 46. Thank you @ericntd ● @ericntd (LinkedIn, Medium, Github, StackoverFlow, Twitter) ● Send me your resume eric.nguyen at grab.com - https://grab.careers/

Editor's Notes

  1. Ask me a question at the end for 70k GrabPay credit They say money can’t buy happiness, I’m pretty sure it can
  2. E.g. currency formatter
  3. Language being used here is Java
  4. Setting up JUnit dependency is easy with Android Studio and Gradle Your production code is under app/src/main/java/ folder. Whereas, your test code is under app/src/test/java folder. Once you create your test class there and write at least 1 test method (will show you in 1 second), Android Studio gives you a nice little green Play icon to run your test. To mark a method as a test method, simply annate it with @Test
  5. The inputs we use are 0, 1, -1 for example. Also, we need a big positive and a big negative numbers to test the overflow scenarios. For each input, we use the assertEquals method from JUnit’s Assert class to check whether our function’s actual outputs matches expected values.
  6. It gives us the confidence that our code works as expected It’s ok to get failing tests It’s half of the fun
  7. Say some teammate or yourself dislikes -1 as the output in overflow case and decides to change it to 1 later Our app will crash or misbehave We should instead run our unit tests regularly, every git push for a pull request We can catch the bug and fix it early
  8. I’m certain you will find some huge functions in your code. Try writing unit test for them, you will see that I mean. Can’t show my code due to confidentiality issue. It wouldn’t make much sense If I censored everything or showed you some dummy code.
  9. Let’s naively do everything in the Activity A hint here, architecture is the key to Android unit testing here, will expand later
  10. Similar to how we did CalculatorTest, we will create a class CalculatorActivityTest under src/test/java folder We will naively try to test the updateNumber() method with the help of Mockito What is Mockito? Mockito is a popular Java library to assist unit testing One could always use Robolectric to stub the TextView here However, it’s slower and may have side effects As much as they try, Robolectric’s stubs are not the same s Android’s objects Use Robolectric sparingly
  11. MVP is simplest to start with
  12. There are MVVM, Clean architecture and RIB in the market, check them out when you have time MVP is the simplest architecture that separate business logics from Activity/ Fragment We will improve our architecture later
  13. As said, we’ll start with MVP, the simplest architecture ha
  14. This app albeit simple, it fetches data from GitHub API similar to many of your actual apps This should relates to most of your apps right, communicating with a REST API on your servers or third party servers
  15. Repository class in Data Layer fetches data from GitHub API using Retrofit searchGitHubRepos calls Repository class to fetch data Repository class a callback handleSearchGitHubResponse handles the data returned through that callback
  16. Not Robolectric
  17. Let’s write tests Create a SearchPresenterTest class First, let’s test searchGitHubRepos method in a happy scenario i.e. a non-null non empty query is passed to it
  18. Switch side is confusing Reconsider
  19. This time, we run test with coverage This is how you quantify your work with unit testing We’ll start today at 0 We’ll soon be at 50 Let’s strive for 80 coverage, you know the 80/20 principle? It’s important to test the unhappy paths and error handling Let’s add those tests
  20. Don’t just test the happy path, it’s important to test the edge cases and error cases two The unhappy scenario here is when search query is null Here we use Mockito.never() to verify that repository.searchRepos is never called
  21. We’ll also test method handleGitHubReponse’s error handling
  22. Run the tests with coverage again This time, we 100% test coverage!
  23. To link to next slide
  24. Most variables are reusable, so the 200 lines of setText and setVisibility in my production code will become like 10 lines of code in SearchViewModel
  25. Consider removing this
  26. To put at the end