Automatyczne testowanie aplikacji Android Arkadiusz Konior! 4developers!! Warszawa 7 kwietnia 2014
Agenda Testowanie Android Testing Framework Robotium Espresso monkey monkeyrunner UIAutomator Robolectric Porówanie
Android Testing Framework monkeyrunner monkey uiautomator Espresso Robotium Robolectric Spoon Mockito FEST Android android-mock Selenium NativeDriver Appium MonkeyTalk Selendroid Scirocco Bot-Bot Switchboard Calabash BoundBox RoboSpock
Android Testing Framework monkeyrunner monkey uiautomator Espresso Robotium Robolectric Spoon Mockito FEST Android android-mock Selenium NativeDriver Appium MonkeyTalk Selendroid Scirocco Bot-Bot Switchboard Calabash BoundBox RoboSpock
Cel i koszty Zysk lepsza jakość aplikacji bezpieczeństwo zmian Koszty napisanie utrzymanie nauka narzędzi
ZYSK > KOSZT
Testowanie manualne czy automatyczne kiedy wdrożyć? prototyp? wersja beta? Testy proste, przejrzyste niezawodne, deterministyczne @FlakyTest(tolerance=3)! trwałe - odporne na drobne refaktoryzacje
Testy black-box vs white-box źródła vs. apk JVM vs. device czas wykonania kto pisze? developer? tester? jedna aplikacja czy wiele? recorder
TESTY URZĄDZENIE JVM
TESTY URZĄDZENIE JVM INSTRUMENTACJA Android testing framework Espresso Robolectric Robotium monkey monkeyrunner UIAutomator
Instrumentacja natywny mechanizm testowy systemu Android dostępne od API 1 jeden proces, różne wątki dwa projekty, dwa apk gradle plugin JUnit 3 zarządzamy cyklem życia testowanej aplikacji wymaga instalacji na urządzeniu/ emulatorze jednostkowe i funkcjonalne symulacja eventów z systemu i od użytkownika
public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {! private MainActivity activity;! public MainActivityTest() { super(mainactivity.class); }! @Override protected void setup() throws Exception { super.setup(); setactivityinitialtouchmode(false); activity = getactivity(); }! public void testclick() { TextView viewbyid = (TextView) activity.findviewbyid(r.id.text); assertequals("hello world!", viewbyid.gettext());! Button button = (Button) activity.findviewbyid(r.id.button); TouchUtils.clickView(this, button); assertequals("clicked", viewbyid.gettext()); } }
Developer machine application.apk application-test.apk
Android device/emulator Developer machine process application.apk app.apk application-test.apk certyfikat zarządza app-test.apk am istrument
Budowanie projektu testowego
Uruchamianie testów
adb
Robotium testy funkcjonalne (black-box) rozszerzenie testów instrumentacyjnych brak źródeł aplikacji możliwość nagrywania testów - Robotium Recorder tekstowe znajdowanie elementów UI synchronizacja poprzez sleep/retry interakcja z UI ale nie z kodem aplikacji click +30 metod
Solo solo = new Solo(getInstrumentation(), getactivity()); solo.sendkey(solo.menu); solo.clickontext("more"); solo.clickontext("preferences"); solo.clickontext("edit File Extensions"); Assert.assertTrue(solo.searchText("rtf"));
Espresso http://gotgelato.com/espresso/ powstał w Google cienka warstwa nad instrumentacją (600 linii) wspiera wersje Android od 2.2 szybki, przejrzysty, łatwo rozszerzalny ręczna integracja Leave your waits, syncs, sleeps, and polls behind Hamcrest matchers
Espresso tylko to co może użytkownik - brak getview, getcurrentactivity contentdescription pokrycie testami 95% on view do stuff check result
public void testsayhello() { onview(withid(r.id.name_field)).perform(typetext("steve"));!! } onview(withid(r.id.greet_button)).perform(click()); onview(withtext("hello Steve!")).check(matches(isDisplayed()));
monkey command-line tool losowy strumień eventów adb shell monkey -p com.package -v 2000
monkey
monkeyrunner nie mylić z monkey natywny mechanizm testy funkcjonalne zapisane w skrypcie python monkeyrunner API: MonkeyRunner - połącznie z urządzeniem MonkeyDevice - instalacja, startowanie activity, keyboard, touch events MonkeyImage - screenshot
monkeyrunner asercje - porównanie zrzutów ekranu sameas click x,y jednocześnie wiele urządzeń testy regresyjne sterowane z komputera nie przez adb wrażliwe na zmianę wyglądu
from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice device = MonkeyRunner.waitForConnection() device.installpackage('myproject/bin/myapplication.apk') package = 'com.example.android.myapplication' activity = 'com.example.android.myapplication.mainactivity' runcomponent = package + '/' + activity device.startactivity(component=runcomponent) device.press('keycode_menu', MonkeyDevice.DOWN_AND_UP) result = device.takesnapshot() result.writetofile('myproject/shot1.png','png')
UIAutomator DEVICE App1 App2 UIAutomator test Android framework InputManager AccessibilityService
UIAutomator dostępne w Android SDK od API 16 testy funkcjonalne (black-box) oparte na JUnit 3 test jako java jar działa jako niezależny program Accessibility Manager, Input Manager testowanie wielu aplikacji uiautomatorviewer - lepiej niż monkeyrunner android:contentdescription integracja z instrumentacją
public class LaunchSettings extends UiAutomatorTestCase {! public void testdemo() throws UiObjectNotFoundException { getuidevice().presshome(); UiObject allappsbutton = new UiObject(new UiSelector().description("Apps")); allappsbutton.clickandwaitfornewwindow(); UiObject appstab = new UiObject(new UiSelector().text("Apps")); appstab.click(); UiScrollable appviews = new UiScrollable(new UiSelector().scrollable(true)); appviews.setashorizontallist(); UiObject settingsapp = appviews.getchildbytext(new UiSelector().className(android.widget.TextView.class.getName()), "Settings"); settingsapp.clickandwaitfornewwindow(); UiObject settingsvalidation = new UiObject(new UiSelector().packageName("com.android.settings")); asserttrue("unable to detect Settings", settingsvalidation.exists()); } }
uiautomator
Robolectric emulator jest wolny dex, build, copy - to nie jest TDD biblioteka mock ująca Android framework - android.jar hierarchia shadow - ShadowViewGroup -> ShadowView - zgodne sygnatury metod możliwość uruchamiania testów na JVM bez emulatora/urządzenia - JUnit4, Spock szybkość prawdziwe testy jednostkowe
@RunWith(RobolectricTestRunner.class) public class MyActivityTest {! @Test public void clickingbutton_shouldchangeresultsviewtext() throws Exception { Activity activity = Robolectric.buildActivity(MyActivity.class).create().get();! Button pressmebutton = (Button) activity.findviewbyid(r.id.press_me_button); TextView results = (TextView) activity.findviewbyid(r.id.results_text_view);! pressmebutton.performclick(); String resultstext = results.gettext().tostring(); assertthat(resultstext, equalto("testing Android Rocks!")); } }
monkey UIAutomator Espresso Robotium Robolectric Porównanie Instrumentation monkeyrunner oficjalne wsparcie Google nie wymaga urządzenia/emulatora wspiera stare sdk możliwość nagrywania testu możliwość testowania bez źródeł aplikacji asercje wewnętrznego stanu
Które narzędzie wybrać? Jak duży projekt? Etap projektu Ilość logiki biznesowej, złożoność widoku Krytyczność błędów Uruchamiane w IDE czy w CI? Wspomaganie rozwoju czy zapobieganie regresji?
Q & A
Dziękuję za uwagę akonior@gmail.com http://www.linkedin.com/in/arkadiuszkonior