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 Selenium NativeDriver Appium MonkeyTalk Selendroid Scirocco Bot-Bot Switchboard Spoon Mockito Calabash FEST Android BoundBox android-mock 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
Robotium
monkey
monkeyrunner
UIAutomator
Robolectric
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 {
! 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
application-test.apk
app.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!")); } }
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
Robolectric
Robotium
Espresso
UIAutomator
monkey
monkeyrunner
Instrumentation
Porównanie
✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔
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ę
[email protected] http://www.linkedin.com/in/arkadiuszkonior