![Page 1: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/1.jpg)
![Page 2: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/2.jpg)
@AlexeyBuzdin
GDGRiga.lv JUG.lv
RigaDevDay.lvCitadele.lv
![Page 3: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/3.jpg)
![Page 4: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/4.jpg)
![Page 5: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/5.jpg)
What does Android need?
![Page 6: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/6.jpg)
UI Tests
Integration Tests
Unit Tests
![Page 7: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/7.jpg)
UI Tests
Integration Tests
Unit Tests
Effort
![Page 8: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/8.jpg)
Effort Cost
UI Tests
Integration Tests
Unit Tests
![Page 9: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/9.jpg)
UI Tests
Integration Tests
Unit Tests
Effort Cost
![Page 10: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/10.jpg)
Unit Test๏ Uses simple JUnit ๏ Runs on JVM, not on a device ๏ Lightning fast (5k test in 10
seconds) ๏ Needs Android SDK Stubs
![Page 11: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/11.jpg)
static protected void markConflicting(ArrayList<ScheduleItem> items) { for (int i=0; i<items.size(); i++) { ScheduleItem item = items.get(i); // Notice that we only care about sessions when checking conflicts. if (item.type == ScheduleItem.SESSION) for (int j=i+1; j<items.size(); j++) { ScheduleItem other = items.get(j); if (item.type == ScheduleItem.SESSION) { if (intersect(other, item, true)) { other.flags |= ScheduleItem.FLAG_CONFLICTS_WITH_PREVIOUS; item.flags |= ScheduleItem.FLAG_CONFLICTS_WITH_NEXT; } else { // we assume the list is ordered by starttime break; } } } } } https://github.com/google/iosched
![Page 12: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/12.jpg)
@Override public void displayData(final SessionFeedbackModel model, final SessionFeedbackQueryEnum query) { switch (query) { case SESSION: mTitle.setText(model.getSessionTitle()); if (!TextUtils.isEmpty(model.getSessionSpeakers())) { mSpeakers.setText(model.getSessionSpeakers()); } else { mSpeakers.setVisibility(View.GONE); } AnalyticsHelper.sendScreenView("Feedback: " + model.getSessionTitle()); break; } }
https://github.com/google/iosched
![Page 13: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/13.jpg)
java.lang.RuntimeException: Method setText in android.widget.TextView not mocked. See http://g.co/androidstudio/not-mocked for details.
at android.widget.TextView.setText(TextView.java) at lv.buzdin.alexey.MainActivityPresenterTest.test(MainActivityPresenterTest.java:39)
@Test public void name() throws Exception { TextView textView = new TextView(null); textView.setText("hello"); }
![Page 14: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/14.jpg)
android { ….
testOptions { unitTests.returnDefaultValues = true } }
![Page 15: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/15.jpg)
Toast.makeText(null, "", Toast.LENGTH_SHORT).show();
![Page 16: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/16.jpg)
Toast.makeText(null, "", Toast.LENGTH_SHORT).show();
Unmockable*
![Page 17: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/17.jpg)
Testable architecture๏ Use MV* Patterns ๏ Dependency Injection is a must ๏ Try to use POJOs where possible ๏ Wrap static Android classes to Services
(Log, Toast, etc) ๏ Avoid highly coupling to android classes in *
code
![Page 18: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/18.jpg)
Model - View - Whatever(Activity/Fragment)
๏ Activity is polluted. (Lifecycle Logic, LayoutInflater, static calls, etc)
๏ We need a POJO
![Page 19: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/19.jpg)
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); BaseApplication.inject(this); setContentView(R.layout.activity_screen);
presenter.initPresenter(this); presenter.initNavigationDrawer(); presenter.openScheduleScreen();
if (presenter.firstApplicationStart()) { presenter.openNavigationDrawer(); } }
![Page 20: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/20.jpg)
public class MainActivityPresenter { … public void initPresenter(ActionBarActivity activity) { this.activity = activity; ButterKnife.inject(this, activity); }
public void initNavigationDrawer() { activity.setSupportActionBar(toolbar); drawerToggle = new ActionBarDrawerToggle(activity, drawerLayout, R.string.app_name, R.string.app_name) { @Override public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); listView.invalidateViews(); //Refresh counter for bookmarks } }; drawerToggle.setDrawerIndicatorEnabled(true); drawerLayout.setDrawerListener(drawerToggle); activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true); activity.getSupportActionBar().setHomeButtonEnabled(true); listView.setAdapter(navigationAdapter); } }
![Page 21: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/21.jpg)
Testable architecture๏ Use MV* Patterns ๏ Dependency Injection is a must ๏ Try to use POJOs where possible ๏ Wrap static Android classes to Services
(Log, Toast, etc) ๏ Avoid highly coupling to android classes in *
code
![Page 22: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/22.jpg)
Dagger 2The fastest Java DI Framework!
https://google.github.io/dagger/
![Page 23: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/23.jpg)
@Singleton public class MainActivityPresenter {
@Inject SocialNetworkNavigationService socialsService; @Inject SharedPrefsService preferences; @Inject NavigationAdapter navigationAdapter; … public boolean firstApplicationStart() { boolean subsequentStart = preferences.getBool(PreferencesConstants.SUBSEQUENT_START); if (!subsequentStart) { preferences.setBool(PreferencesConstants.SUBSEQUENT_START, true); return true; } return false; } }
![Page 24: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/24.jpg)
Testable architecture๏ Use MV* Patterns ๏ Dependency Injection is a must ๏ Try to use POJOs where possible ๏ Wrap static Android classes to Services
(Log, Toast, etc) ๏ Avoid highly coupling to android classes in *
code
![Page 25: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/25.jpg)
public class SharedPrefsService {
@Inject public Context context;
private SharedPreferences getPrefs() { return PreferenceManager.getDefaultSharedPreferences(context); }
public boolean getBool(String key) { return getPrefs().getBoolean(key, false); }
public void setBool(String key, boolean value) { getPrefs().edit().putBoolean(key, value).commit(); } }
![Page 26: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/26.jpg)
Testable architecture๏ Use MV* Patterns ๏ Dependency Injection is a must ๏ Try to use POJOs where possible ๏ Wrap static Android classes to Services
(Log, Toast, etc) ๏ Avoid highly coupling to android classes in *
code
![Page 27: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/27.jpg)
Removes View dependency for Whatever
Button b = (Button)findViewById(R.id.button); b.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { b.setBackgroundColor(Red); } });
![Page 28: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/28.jpg)
@InjectView(R.id.button) Button b;
@OnClick(R.id.button) public void onClick(View v) {
b.setBackgroundColor(Red); }
Removes View dependency for Whatever
![Page 29: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/29.jpg)
RxBus rxBus = new RxBus(); @InjectView(R.id.button) Button b; Observable<Void> clicks = RxView.clicks(b);
public init() { clicks.subscribe(aVoid -> { rxBus.send(new Click()); }); rxBus.toObserverable()
.filter(e -> e instanceof Click)
.subscribe(e -> { b.setBackgroundColor(Red); }); }
https://github.com/JakeWharton/RxBinding
Testable architecture with Rx
![Page 30: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/30.jpg)
@Test public void test() throws Exception { MyFragment fragment = new MyFragment(); fragment.b = new Button(null) { @Override public void setBackgroundColor(int color) { assertEquals(color, Red); } }; fragment.clicks = Observable.just(null); fragment.init(); }
![Page 31: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/31.jpg)
Mockito + PowerMock
Mocking, Spying
![Page 32: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/32.jpg)
Object o = mock(Object.class); doReturn(true).when(o).equals(any());
Object o = spy(“Hi”); doReturn(true).when(o).equals(any()); o.hashCode() -> is real
![Page 33: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/33.jpg)
@RunWith(MockitoJUnitRunner.class)public class MyClassTest {
@InjectMocks MyFragment fragment; @Mock Button b;
@Test public void test() throws Exception { fragment.clicks = Observable.just(null); fragment.init(); verify(b).setBackgroundColor(Red); } }
![Page 34: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/34.jpg)
Toast.makeText(null, "", Toast.LENGTH_SHORT).show();
Unmockable*
![Page 35: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/35.jpg)
Toast.makeText(null, "", Toast.LENGTH_SHORT).show();
Unmockable*
![Page 36: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/36.jpg)
@RunWith(PowerMockRunner.class)@PrepareForTest( { Toast.class })public class PowerMockExample {
@Test public void testPowerMock() throws Exception { Toast mock = mock(Toast.class); mockStatic(Toast.class); when(Toast.makeText(any(), anyString(), anyInt())).thenReturn(mock);
Toast.makeText(null, "1", Toast.LENGTH_SHORT).show(); } }
![Page 37: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/37.jpg)
JUnit Extra FeaturesHelpful for Android Developers
![Page 38: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/38.jpg)
![Page 39: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/39.jpg)
JUnit Lifecyclepublic class RunnerTest { @BeforeClass public static void beforeClass() { out.println("Before Class");} @Before public void before() { out.println("Before");} @Test public void test() { out.println("Test"); } @After public void after() { out.println("After"); } @AfterClass public static void afterClass() { out.println("After Class"); } }
![Page 40: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/40.jpg)
JUnit Lifecyclepublic class RunnerTest { @BeforeClass public static void beforeClass() { out.println("Before Class");} @Before public void before() { out.println("Before");} @Test public void test() { out.println("Test"); } @After public void after() { out.println("After"); } @AfterClass public static void afterClass() { out.println("After Class"); } }Before Class Before Test After After Class Process finished with exit code 0
![Page 41: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/41.jpg)
Custom Runner:BlockJUnit4ClassRunner
or Runner
![Page 42: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/42.jpg)
public class CustomRunner extends BlockJUnit4ClassRunner{
public static void runnerBefore() { System.out.println("Runner Before");} public static void runnerBeforeClass() { System.out.println("Runner Before Class"); } public static void runnerAfter() { System.out.println("Runner After");} public static void runnerAfterClass() { System.out.println("Runner After Class"); }
@Override protected Statement withBefores(FrameworkMethod method, Object t, Statement st) { List<FrameworkMethod> list = getFrameworkMethods("runnerBefore"); return new RunBefores(super.withBefores(method, t, st), list, t); } …. private List<FrameworkMethod> getFrameworkMethods(String methodName) { try { Method runnerBefore = getClass().getDeclaredMethod(methodName); return Collections.singletonList(new FrameworkMethod(runnerBefore)); } catch (Exception e) { throw new RuntimeException(e); } } }
![Page 43: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/43.jpg)
@RunWith(CustomRunner.class)public class RunnerTest { …. }
Runner Before Class Before Class Runner Before Before Test After Runner After After Class Runner After Class Process finished with exit code 0
![Page 44: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/44.jpg)
@RunWith(CustomRunner.class)public class RunnerTest { …. }
Runner Before Class Before Class Runner Before Before Test After Runner After After Class Runner After Class Process finished with exit code 0
AndroidJUnitRunner + MockitoJUnitRunner +Parametrized
![Page 45: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/45.jpg)
![Page 46: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/46.jpg)
JUnit Rules
•Rules allow flexible addition of the behaviour of each test method in a test class
•Base Rules Provided in JUnit:
Temporary Folder Rule; ExternalResource Rule; ErrorCollector Rule; TestName Rule; Timeout Rule;
RuleChain
![Page 47: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/47.jpg)
public class CustomRule implements TestRule { private boolean classRule; public CustomRule(boolean classRule) { this.classRule = classRule; }
@Override public Statement apply(Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { System.out.println(classRule ? "Class Rule Before" : "Rule Before"); try { base.evaluate(); } finally { System.out.println(classRule ? "Class Rule After" : "Rule After”); } } }; } }
![Page 48: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/48.jpg)
@RunWith(CustomRunner.class)public class RunnerTest {
@ClassRule public static CustomRule classRule = new CustomRule(true); @Rule public CustomRule rule = new CustomRule(false);
@BeforeClass public static void beforeClass() { out.println("Before Class");} @Before public void before() { out.println("Before");} @Test public void test() { out.println("Test"); } @After public void after() { out.println("After"); } @AfterClass public static void afterClass() { out.println("After Class"); } }
![Page 49: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/49.jpg)
Class Rule Before Runner Before Class Before Class Rule Before Runner Before Before Test After Runner After Rule After After Class Runner After Class Class Rule After
![Page 50: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/50.jpg)
Class Rule Before Runner Before Class Before Class Rule Before Runner Before Before Test After Runner After Rule After After Class Runner After Class Class Rule After
![Page 51: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/51.jpg)
public static class UseRuleChain { @Rule public RuleChain chain= RuleChain .outerRule(new LoggingRule("outer rule") .around(new LoggingRule("middle rule") .around(new LoggingRule("inner rule");
@Test public void example() { assertTrue(true); } }
starting outer rule starting middle rule starting inner rule finished inner rule finished middle rule finished outer rule
![Page 52: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/52.jpg)
Rules > Runners
![Page 53: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/53.jpg)
Test Specification1. Preparation 2. Testable Action 3. Assertion
![Page 54: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/54.jpg)
Test Specification1. Preparation 2. Testable Action 3. Assertion
https://github.com/hamcrest/JavaHamcrest
assertThat(T object, Matcher<T> matcher)
![Page 55: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/55.jpg)
@Test public void testValidIPAddress() throws InvalidIPAddressException { IPAddress addr = new IPAddress("127.0.0.1"); byte[] octets = addr.getOctets();
assertTrue(octets[0] == 127); assertTrue(octets[1] == 0); assertTrue(octets[2] == 0); assertTrue(octets[3] == 1); }
Bad Test
![Page 56: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/56.jpg)
Examples•assertThat(2, is(2))
•assertThat(“s”, is(nullValue()))
•assertThat(“s”, is(new String(“s”)))
•assertThat(“s”, equalTo(new String(“s”)))
•assertThat(“s”, not(equalTo(“d”)))
•assertThat(“s”, instanceOf(String.class))
http://hamcrest.org/JavaHamcrest/javadoc/1.3/
![Page 57: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/57.jpg)
List matcher
•assertThat(ids, hasItem(10))
•assertThat(ids, contains(5, 8))
•assertThat(ids, containsInAnyOrder(5, 8))
•assertThat(ids, everyItem(greaterThan(3)))
![Page 58: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/58.jpg)
AllOf AnyOf
assertThat(name, anyOf(startsWith(“A”), endsWith(“B”)))
assertThat(ids, allOf(
hasSize(5),
hasItem(10),
everyItem(greaterThan(3))
))
![Page 59: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/59.jpg)
Error messagesassertThat("s", is(nullValue()))
assertThat(true, is(false))
![Page 60: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/60.jpg)
Error messages
assertThat(1, is(allOf(not(1), not(2), not(10))))
![Page 61: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/61.jpg)
Custom matchersprivate Matcher<Foo> hasNumber(final int i) { return new TypeSafeMatcher<Foo>() { @Override public void describeTo(final Description description) { description.appendText("getNumber should return ").appendValue(i); } @Override protected void describeMismatchSafely(final Foo item, final Description mismatchDescription) { mismatchDescription.appendText(" was ").appendValue(item.getNumber()); } @Override protected boolean matchesSafely(final Foo item) { return i == item.getNumber(); } }; }
![Page 62: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/62.jpg)
hamcrest-rxMatcher<TestSubscriber<T>> hasValues(final Matcher<? super List<T>> eventsMatcher) Matcher<TestSubscriber<T>> hasOnlyValues(final Matcher<? super List<T>> values) Matcher<TestSubscriber<T>> hasOnlyValue(final Matcher<? super T> valueMatcher) Matcher<TestSubscriber<T>> hasNoValues() Matcher<TestSubscriber<T>> hasErrors(final Matcher<? super List<Throwable>> err) Matcher<TestSubscriber<T>> hasNoErrors() Matcher<TestSubscriber<T>> hasOnlyErrors(final Matcher<? super List<Throwable>> err)
https://github.com/zalando-incubator/undertaking/blob/master/src/test/java/org/zalando/undertaking/test/rx/hamcrest/TestSubscriberMatchers.java
![Page 63: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/63.jpg)
https://github.com/hertzsprung/hamcrest-json
assertThat( "{\"age\":43, \"friend_ids\":[16, 52, 23]}", sameJSONAs("{\"friend_ids\":[52, 23, 16]}") .allowingExtraUnexpectedFields() .allowingAnyArrayOrdering());
hamcrest-json
![Page 64: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/64.jpg)
Parameterised tests
![Page 65: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/65.jpg)
Parameterised with Name
![Page 66: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/66.jpg)
Square Burst
https://github.com/square/burst
public enum Soda { PEPSI, COKE }public enum Sets { HASH_SET() { @Override public <T> Set<T> create() { return new HashSet<T>(); } }, LINKED_HASH_SET() { … }, TREE_SET() { … } public abstract <T> Set<T> create(); }
![Page 67: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/67.jpg)
https://github.com/square/burst
@RunWith(BurstJUnit4.class) public class DrinkSodaTest { @Burst Soda soda; @Burst Sets sets; @Test public void drinkFavoriteSodas(Soda soda) { // TODO Test drink method with 'soda'... } }
Square Burst
![Page 68: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/68.jpg)
https://github.com/square/burst
@RunWith(BurstJUnit4.class) public class DrinkSodaTest { private final Set<Soda> favorites; public DrinkSodaTest(Sets sets) { favorites = sets.create(); } @Test public void trackFavorites() { // TODO … } @Test public void drinkFavoriteSodas(Soda soda) { //TODO … } }
Square Burst
![Page 69: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/69.jpg)
JUnitParams
https://github.com/Pragmatists/JUnitParams
@RunWith(JUnitParamsRunner.class)public class PersonTest {
@Test @Parameters({"17, false”, "22, true" }) public void personIsAdult(int age, boolean valid) throws Exception { assertThat(new Person(age).isAdult(), is(valid)); }
}
![Page 70: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/70.jpg)
EnclosedProvides a way to use multiple JUnit Runners in a test class
![Page 71: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/71.jpg)
![Page 73: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/73.jpg)
http://junit.org/junit5/
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test;
class FirstJUnit5Tests { @Test void myFirstTest() { assertEquals(2, 1 + 1); } }
![Page 74: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/74.jpg)
JUnit 5 Features
![Page 75: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/75.jpg)
Grouped Assertions
@Test void groupedAssertions() { // In a grouped assertion all assertions are executed, and any // failures will be reported together. assertAll("address", () -> assertEquals("John", address.getFirstName()), () -> assertEquals("User", address.getLastName()) ); }
![Page 76: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/76.jpg)
Throwable Assertions
@Test void exceptionTesting() { Throwable exception = assertThrows(IllegalArgumentException.class, () -> { throw new IllegalArgumentException("a message"); }); assertEquals("a message", exception.getMessage()); }
![Page 77: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/77.jpg)
No assertThat() import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat;
import org.junit.jupiter.api.Test;
class HamcrestAssertionDemo {
@Test void assertWithHamcrestMatcher() { assertThat(2 + 1, is(equalTo(3))); } }
![Page 78: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/78.jpg)
No Runners or Rules > Extensions@ExtendWith(MockitoExtension.class)class MyMockitoTest {
@BeforeEach void init(@Mock Person person) { when(person.getName()).thenReturn("Dilbert"); }
@Test void simpleTestWithInjectedMock(@Mock Person person) { assertEquals("Dilbert", person.getName()); } }
![Page 79: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/79.jpg)
Dynamic Tests
class DynamicTestsDemo { @TestFactory Collection<DynamicTest> dynamicTestsFromCollection() { return Arrays.asList( dynamicTest("1st dynamic test", () -> assertTrue(true)), dynamicTest("2nd dynamic test", () -> assertEquals(4, 2 * 2)) ); }}
Collection, Iterable, Iterator, Stream
![Page 80: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/80.jpg)
in Android
• Not coming soon
• Would require a rewrite for all testing libraries
• Would require Android Studio support
• Would simplify the Extension model
https://github.com/junit-team/junit5/issues/204
![Page 81: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/81.jpg)
Integration Tests
๏ Uses Robolectric framework ๏ Runs on JVM with Shadow Android SDK ๏ Has access to Context and all Android
peripheral
![Page 82: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/82.jpg)
Robolectric@RunWith(RobolectricTestRunner.class)public class MyActivityTest {
@Test public void clickingButton_shouldChangeResultsViewText() throws Exception { MyActivity activity = Robolectric.setupActivity(MyActivity.class);
Button button = (Button) activity.findViewById(R.id.button); TextView results = (TextView) activity.findViewById(R.id.results);
button.performClick(); assertThat(results.getText().toString()).isEqualTo("Robolectric Rocks!"); } }
![Page 83: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/83.jpg)
ActivityController controller = Robolectric.buildActivity(MyAwesomeActivity.class).create().start(); Activity activity = controller.get(); // assert that something hasn't happened activityController.resume(); // assert it happened!
Activity activity = Robolectric.buildActivity(MyAwesomeActivity.class) .create().start().resume().visible().get();
Activity Lifecycle
![Page 84: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/84.jpg)
public class SharedPrefsService {
@Inject public Context context;
private SharedPreferences getPrefs() { return PreferenceManager.getDefaultSharedPreferences(context); }
public boolean getBool(String key) { return getPrefs().getBoolean(key, false); }
public void setBool(String key, boolean value) { getPrefs().edit().putBoolean(key, value).commit(); } }
![Page 85: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/85.jpg)
Robolectric๏ Life saver when complex Android SDK
calls should be tested ๏ Slow compared to Unit Tests ๏Not up to date to the latest SDKs
(API 24 not supported yet)
![Page 86: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/86.jpg)
UI Tests
๏ Runs on actual Android Device ๏ Slower the Unit tests ๏ Brittle and dependant on
device health
![Page 87: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/87.jpg)
public void testRecorded() throws Exception { if (solo.waitForText("Hello!")) { solo.clickOnView(solo.findViewById("R.id.sign_in")); solo.enterText((EditText) solo.findViewById("R.id.login_username"),"username"); solo.enterText((EditText) solo.findViewById("R.id.login_password"),"password"); solo.clickOnView(solo.findViewById("R.id.login_login")); solo.waitForActivity("HomeTabActivity"); } solo.clickOnView(solo.findViewById("R.id.menu_compose_tweet") ); solo.enterText((EditText) solo.findViewById(“R.id.edit"), "Testdroid"); solo.clickOnView(solo.findViewById("R.id.composer_post")); }
Robotium
![Page 88: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/88.jpg)
Android Espresso
@Test public void multiActivityTest() { onView(withId(R.id.date)) .perform(click()); // Loads another activity riiiight here onView(allOf(withId(R.id.date_expanded), withText("SomeRandomDate"))) .check(matches(isDisplayed())) .perform(click()); // Yay! No waiting!}
![Page 89: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/89.jpg)
Espresso comes with Hamcrest integration
@Test public void dateTest() { onView(withId(R.id.date)) .check(matches(withText("2014-10-15"))); }
![Page 90: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/90.jpg)
Robotium vs Espresso
• Espresso faster •Robotium has bigger SDK coverage • Espresso has built in wait mechanism that is optimised for android lifecycle
http://www.stevenmarkford.com/android-espresso-vs-robotium-benchmarks/
![Page 91: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/91.jpg)
Looking into Cross-platform UI Test Automation?
![Page 92: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/92.jpg)
![Page 94: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/94.jpg)
![Page 95: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/95.jpg)
Cross platform tests
If you have dedicated QA team and product on multiple platforms - go Calabash or Appium
• More flacky tests• Less performant speed• Some test reuse• Easier for QA
![Page 96: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/96.jpg)
BFF
BFF
DB
DB
DB
AMQP
![Page 97: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/97.jpg)
How to run UI test
•Don’t initialize run-time dependencies (event tracking, analytics, long-init things like payment solutions) •Don’t hit up real backend, mock out responses • Insert appropriate test data before test starts running
![Page 98: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/98.jpg)
How to mock a Server
- Mock Server through DI - Mock HTTP Server instance on Device - Dev instance of Server
![Page 99: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/99.jpg)
DI: Create a custom Test Runner
public class MockTestRunner extends AndroidJUnitRunner { @Override public Application newApplication(ClassLoader cl, String className, Context ctx) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return super.newApplication(cl, MockDemoApplication.class.getName(), ctx); }}
![Page 100: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/100.jpg)
DI: Create a custom Test Application
public class MockDemoApplication extends DemoApplication { @Override protected DemoComponent createComponent() { return DaggerMainActivityTest_TestComponent.builder().build(); }}
![Page 101: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/101.jpg)
MockServer on Device: AndroidAsync
https://github.com/koush/AndroidAsync
AsyncHttpServer server = new AsyncHttpServer(); server.get("/", new HttpServerRequestCallback() { @Override public void onRequest(AsyncHttpServerRequest request, AsyncHttpServerResponse response) { response.send("Hello!!!"); } }); server.listen(5000);
![Page 102: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/102.jpg)
How to run UI tests on CI?
![Page 103: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/103.jpg)
Android Jenkins Plugin
https://wiki.jenkins-ci.org/display/JENKINS/Android+Emulator+Plugin
![Page 104: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/104.jpg)
Multi-configuration (matrix) job
Android Jenkins Plugin
![Page 105: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/105.jpg)
https://github.com/Genymobile/genymotion-gradle-plugin
1. genymotion { 2. devices { 3. nexus5 { 4. template "Google Nexus 5 - 4.4.4 - API 19 - 1080x1920" 5. } 6. } 7. }
![Page 106: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/106.jpg)
https://medium.com/@Genymotion/android-os-now-available-as-an-amazon-machine-image-72748130436b#.njabkxnih
![Page 107: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/107.jpg)
https://github.com/square/spoonhttps://github.com/stanfy/spoon-gradle-plugin
![Page 109: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/109.jpg)
Conclusion๏ Unit tests are cheap, make them your first
frontier ๏ Adapt code to make it more testable ๏ Structure tests with @Rules and Hamcrest
Matchers ๏ Mockito + Powermock will help to mock
Android else Robolectric will
![Page 110: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/110.jpg)
Conclusion๏ UI tests are harder to write and maintain ๏ If you have a dedicated mobile QA team
think of cross-platform tests ๏ For UI tests have a config for Mocked server
and other integration points ๏ Configure either emulator startup or device
farm on your CI
![Page 111: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/111.jpg)
TESTS ARE MADE TO MAKE YOU FEEL SECURE!
![Page 112: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/112.jpg)
LIKE A LOVELY HUG ♥
![Page 113: Alexey Buzdin "Maslow's Pyramid of Android Testing"](https://reader036.vdocuments.us/reader036/viewer/2022062522/58847e9b1a28ab5e248b7ba3/html5/thumbnails/113.jpg)
Q&AThank You!
@AlexeyBuzdinFollow me at