Так как опытные разработчики приложений По мере того, как разрабатываемые нами приложения становятся зрелыми, у нас возникает интуиция, что пора начинать тестирование. Бизнес-правила часто подразумевают, что система должна обеспечивать стабильность в разных выпусках. В идеале мы также хотим автоматизировать процесс сборки и публиковать приложение автоматически. Для этого нам нужны инструменты тестирования Adnroid, чтобы гарантировать, что сборка работает должным образом.
Тесты могут повысить уровень уверенности в том, что мы создаем. Трудно (если не невозможно) создать идеальный продукт без ошибок. Поэтому наша цель - повысить наши шансы на успех на рынке за счет создания набора тестов, который быстро обнаружит новые ошибки в нашем приложении.
Когда дело доходит до Android и различных мобильных платформ в целом, тестирование приложений может стать проблемой. Внедрение модульных тестов и следование принципам разработки через тестирование и т.п. часто может показаться как минимум не интуитивным. Тем не менее тестирование важно, и его нельзя воспринимать как должное или игнорировать. Дэвид , Кент и Мартин обсудили преимущества и недостатки тестирования в серии бесед между собой, собранных в статье под названием « TDD мертв? ». Вы также можете найти там реальные видеобеседы и получить больше информации о том, подходит ли тестирование вашему процессу разработки и в какой степени вы могли бы его включить, начиная с этого момента.
В этом руководстве по тестированию Android я расскажу вам о модульном и приемочном, регрессионном тестировании Android. Мы сосредоточимся на абстракции модуля тестов на Android, а затем на примерах приемочного тестирования, с упором на то, чтобы сделать процесс максимально быстрым и простым, чтобы сократить циклы обратной связи между разработчиком и контролем качества.
В этом руководстве будут рассмотрены различные возможности тестирования приложений Android. Разработчики или менеджеры проектов, которые хотят лучше понять текущие возможности тестирования платформы Android, могут с помощью этого руководства решить, хотят ли они использовать какой-либо из подходов, упомянутых в этой статье. Тем не менее, это не серебряная пуля, поскольку обсуждение такой темы по своей сути варьируется от продукта к продукту, а также сроки, качество кодовой базы кода, уровень связи системы, предпочтения разработчика в дизайне архитектуры, прогнозируемый срок службы функции до тест и др.
В идеале мы хотим протестировать одну логическую единицу / компонент архитектуры независимо. Таким образом мы можем гарантировать, что наш компонент работает правильно для ожидаемого набора входных данных. Зависимости можно смоделировать, что позволит нам писать тесты, которые выполняются быстро. Кроме того, мы сможем моделировать различные состояния системы на основе предоставленных входных данных для теста, охватывая в процессе экзотические случаи.
Цель Android модульное тестирование состоит в том, чтобы изолировать каждую часть программы и показать, что отдельные части верны. Модульный тест предоставляет строгий письменный контракт, которому должен удовлетворять фрагмент кода. В результате он дает несколько преимуществ. —Википедия
Робоэлектрик - это среда модульного тестирования Android, которая позволяет запускать тесты внутри JVM на вашей рабочей станции разработки. Robolectric перезаписывает классы Android SDK по мере их загрузки и позволяет им работать на обычной виртуальной машине Java, что сокращает время тестирования. Кроме того, он обрабатывает увеличение количества представлений, загрузку ресурсов и многое другое, реализованное в собственном коде C на устройствах Android, что делает ненужными эмуляторы и физические устройства для запуска автоматических тестов.
Mockito это фреймворк для имитации, который позволяет нам писать чистые тесты на java. Это упрощает процесс создания тестовых двойников (моков), которые используются для замены исходных зависимостей компонента / модуля, используемых в производстве. В ответе StackOverflow обсуждается о различиях между mocks и stubs довольно простыми словами, которые вы можете прочитать, чтобы узнать больше.
// you can mock concrete classes, not only interfaces LinkedList mockedList = mock(LinkedList.class); // stubbing appears before the actual execution when(mockedList.get(0)).thenReturn('first'); // the following prints 'first' System.out.println(mockedList.get(0)); // the following prints 'null' because get(999) was not stubbed System.out.println(mockedList.get(999));
Кроме того, с помощью Mockito мы можем проверить, был ли вызван метод:
// mock creation List mockedList = mock(List.class); // using mock object - it does not throw any 'unexpected interaction' exception mockedList.add('one'); mockedList.clear(); // selective, explicit, highly readable verification verify(mockedList).add('one'); verify(mockedList).clear();
Теперь мы знаем, что можем указать пары действие-реакция, которые определяют, что происходит, когда мы выполняем определенное действие над имитируемым объектом / компонентом. Таким образом, мы можем имитировать целые модули нашего приложения и для каждого тестового примера заставить макетированный модуль реагировать по-разному. Различные способы будут отражать возможные состояния пары тестируемых компонентов и фиктивных компонентов.
В этом разделе мы будем предполагать архитектуру MVP (Model View Presenter). Действия и фрагменты - это представления, модели - это уровень репозитория для вызовов базы данных или удаленных служб, а презентатор - это «мозг», который связывает все это вместе, реализуя определенную логику для управления представлениями, моделями и потоком данных через применение.
В этом примере тестирования Android мы будем имитировать представления, модели и компоненты репозитория, а также модульное тестирование презентатора. Это один из самых маленьких тестов, нацеленных на один компонент в архитектуре. Кроме того, мы будем использовать заглушки методов для создания правильной, проверяемой цепочки реакций:
@RunWith(RobolectricTestRunner.class) @Config(manifest = 'app/src/main/AndroidManifest.xml', emulateSdk = 18) public class FitnessListPresenterTest { private Calendar cal = Calendar.getInstance(); @Mock private IFitnessListModel model; @Mock private IFitnessListView view; private IFitnessListPresenter presenter; @Before public void setup() { MockitoAnnotations.initMocks(this); final FitnessEntry entryMock = mock(FitnessEntry.class); presenter = new FitnessListPresenter(view, model); /* Define the desired behaviour. Queuing the action in 'doAnswer' for 'when' is executed. Clear and synchronous way of setting reactions for actions (stubbing). */ doAnswer((new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { ArrayList items = new ArrayList(); items.add(entryMock); ((IFitnessListPresenterCallback) presenter).onFetchAllSuccess(items); return null; } })).when(model).fetchAllItems((IFitnessListPresenterCallback) presenter); } /** Verify if model.fetchItems was called once. Verify if view.onFetchSuccess is called once with the specified list of type FitnessEntry The concrete implementation of ((IFitnessListPresenterCallback) presenter).onFetchAllSuccess(items); calls the view.onFetchSuccess(...) method. This is why we verify that view.onFetchSuccess is called once. */ @Test public void testFetchAll() { presenter.fetchAllItems(false); // verify can be called only on mock objects verify(model, times(1)).fetchAllItems((IFitnessListPresenterCallback) presenter); verify(view, times(1)).onFetchSuccess(new ArrayList(anyListOf(FitnessEntry.class))); } }
Часто бывает удобно иметь возможность имитировать глобальный сетевой уровень. MockWebServer позволяет нам ставить в очередь ответы на конкретные запросы, которые мы выполняем в наших тестах. Это дает нам возможность имитировать непонятные ответы, которые мы ожидаем от сервера, но которые нелегко воспроизвести. Это позволяет нам обеспечить полное покрытие при написании небольшого дополнительного кода.
Репозиторий кода MockWebServer предоставляет изящный пример, к которому вы можете обратиться для лучшего понимания этой библиотеки.
Вы можете написать свою собственную модель или компонент respoistory и внедрить их в тест, предоставив другой модуль графу объектов с помощью Dagger (http://square.github.io/dagger/). У нас есть возможность проверить, было ли обновлено состояние представления должным образом на основе данных, предоставленных имитационным компонентом модели:
/** Custom mock model class */ public class FitnessListErrorTestModel extends FitnessListModel { // ... @Override public void fetchAllItems(IFitnessListPresenterCallback callback) { callback.onError(); } @Override public void fetchItemsInRange(final IFitnessListPresenterCallback callback, DateFilter filter) { callback.onError(); } }
@RunWith(RobolectricTestRunner.class) @Config(manifest = 'app/src/main/AndroidManifest.xml', emulateSdk = 18) public class FitnessListPresenterDaggerTest { private FitnessActivity activity; private FitnessListFragment fitnessListFragment; @Before public void setup() { /* setupActivity runs the Activity lifecycle methods on the specified class */ activity = Robolectric.setupActivity(FitnessActivity.class); fitnessListFragment = activity.getFitnessListFragment(); /* Create the objectGraph with the TestModule */ ObjectGraph localGraph = ObjectGraph.create(TestModule.newInstance(fitnessListFragment)); /* Injection */ localGraph.inject(fitnessListFragment); localGraph.inject(fitnessListFragment.getPresenter()); } @Test public void testInteractorError() { fitnessListFragment.getPresenter().fetchAllItems(false); /* suppose that our view shows a Toast message with the specified text below when an error is reported, so we check for it. */ assertEquals(ShadowToast.getTextOfLatestToast(), 'Something went wrong!'); } @Module( injects = { FitnessListFragment.class, FitnessListPresenter.class }, overrides = true, library = true ) static class TestModule { private IFitnessListView view; private TestModule(IFitnessListView view){ this.view = view; } public static TestModule newInstance(IFitnessListView view){ return new TestModule(view); } @Provides public IFitnessListInteractor provideFitnessListInteractor(){ return new FitnessListErrorTestModel(); } @Provides public IFitnessListPresenter provideFitnessPresenter(){ return new FitnessListPresenter(view); } } }
Вы можете легко щелкнуть правой кнопкой мыши тестовый класс, метод или весь тестовый пакет и запустить тесты из диалогового окна параметров в IDE.
Запуск тестов Android-приложения из терминала создает отчеты для тестируемых классов в папке «build» целевого модуля. Более того, если вы планируете настроить автоматизированный процесс сборки, вы будете использовать терминальный подход. С Gradle вы можете запускать все тесты с отладкой, выполнив следующие действия:
gradle testDebug
Версия 1.1 Android Studio и плагин Android Gradle обеспечивают поддержку модульного тестирования вашего кода. Вы можете узнать больше, прочитав их отличная документация по нему . Эта функция является экспериментальной, но также является отличным дополнением, поскольку теперь вы можете легко переключаться между модульными тестами и исходными наборами инструментальных тестов из среды IDE. Он ведет себя так же, как если бы вы переключили вкусы в среде IDE.
Написание тестов для Android-приложений может быть не таким увлекательным занятием, как разработка исходного приложения. Следовательно, несколько советов о том, как облегчить процесс написания тестов и избежать распространенных проблем при настройке проекта, очень помогут.
AssertJ Android , как вы, наверное, догадались по названию, это набор вспомогательных функций, созданных с учетом требований Android. Это расширение популярной библиотеки AssertJ . Функциональность, предоставляемая AssertJ Android, варьируется от простых утверждений, таких как «assertThat (view) .isGone ()», до таких сложных вещей, как:
assertThat(layout).isVisible() .isVertical() .hasChildCount(4) .hasShowDividers(SHOW_DIVIDERS_MIDDLE)
Благодаря AssertJ Android и его расширяемости вам гарантирована простая и хорошая отправная точка для написания тестов для приложений Android.
При использовании Robolectric вы можете заметить, что вам нужно указать местоположение манифеста, а версия SDK установлена на 18. Вы можете сделать это, добавив аннотацию «Config».
@Config(manifest = 'app/src/main/AndroidManifest.xml', emulateSdk = 18)
Выполнение тестов, требующих наличия Robolectric на терминале, может создать новые проблемы. Например, вы можете увидеть исключения типа «Тема не установлена». Если тесты выполняются правильно из IDE, но не из терминала, возможно, вы пытаетесь запустить его из пути в терминале, где указанный путь манифеста не может быть разрешен. Жестко запрограммированное значение конфигурации для пути манифеста может указывать не на правильное место с точки выполнения команды. Это можно решить с помощью пользовательских бегунов:
public class RobolectricGradleTestRunner extends RobolectricTestRunner { public RobolectricGradleTestRunner(Class testClass) throws InitializationError { super(testClass); } @Override protected AndroidManifest getAppManifest(Config config) { String appRoot = '../app/src/main/'; String manifestPath = appRoot + 'AndroidManifest.xml'; String resDir = appRoot + 'res'; String assetsDir = appRoot + 'assets'; AndroidManifest manifest = createAppManifest(Fs.fileFromPath(manifestPath), Fs.fileFromPath(resDir), Fs.fileFromPath(assetsDir)); return manifest; } }
Вы можете использовать следующее, чтобы настроить Gradle для модульного тестирования. Возможно, вам придется изменить имена и версии зависимостей, необходимые в зависимости от потребностей вашего проекта.
// Robolectric testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.9.5' testCompile 'com.squareup.dagger:dagger:1.2.2' testProvided 'com.squareup.dagger:dagger-compiler:1.2.2' testCompile 'com.android.support:support-v4:21.0.+' testCompile 'com.android.support:appcompat-v7:21.0.3' testCompile('org.robolectric:robolectric:2.4') { exclude module: 'classworlds' exclude module: 'commons-logging' exclude module: 'httpclient' exclude module: 'maven-artifact' exclude module: 'maven-artifact-manager' exclude module: 'maven-error-diagnostics' exclude module: 'maven-model' exclude module: 'maven-project' exclude module: 'maven-settings' exclude module: 'plexus-container-default' exclude module: 'plexus-interpolation' exclude module: 'plexus-utils' exclude module: 'wagon-file' exclude module: 'wagon-http-lightweight' exclude module: 'wagon-provider-api' }
Если вы используете Google Play Services, вам нужно будет создать свою собственную целочисленную константу для версии Play Services, чтобы Robolectric правильно работал в этой конфигурации приложения.
android.library.reference.1=../../build/intermediates/exploded-aar/com.android.support/support-v4/21.0.3 android.library.reference.2=../../build/intermediates/exploded-aar/com.android.support/appcompat-v7/21.0.3
Другая интересно Проблема тестирования заключается в том, что Robolectric не может правильно ссылаться на библиотеки поддержки. Решение состоит в том, чтобы добавить файл «project.properties» в модуль, в котором находятся тесты. Например, для библиотек Support-v4 и AppCompat файл должен содержать:
public class LoginTest extends ActivityInstrumentationTestCase2 { private static final String LAUNCHER_ACTIVITY_CLASSNAME = 'com.toptal.fitnesstracker.view.activity.SplashActivity'; private static Class launchActivityClass; static { try { launchActivityClass = Class.forName(LAUNCHER_ACTIVITY_CLASSNAME); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } private ExtSolo solo; @SuppressWarnings('unchecked') public LoginTest() { super((Class) launchActivityClass); } // executed before every test method @Override public void setUp() throws Exception { super.setUp(); solo = new ExtSolo(getInstrumentation(), getActivity(), this.getClass() .getCanonicalName(), getName()); } // executed after every test method @Override public void tearDown() throws Exception { solo.finishOpenedActivities(); solo.tearDown(); super.tearDown(); } public void testRecorded() throws Exception { try { assertTrue( 'Wait for edit text (id: com.toptal.fitnesstracker.R.id.login_username_input) failed.', solo.waitForEditTextById( 'com.toptal.fitnesstracker.R.id.login_username_input', 20000)); solo.enterText( (EditText) solo .findViewById('com.toptal.fitnesstracker.R.id.login_username_input'), ' [email protected] '); solo.sendKey(ExtSolo.ENTER); solo.sleep(500); assertTrue( 'Wait for edit text (id: com.toptal.fitnesstracker.R.id.login_password_input) failed.', solo.waitForEditTextById( 'com.toptal.fitnesstracker.R.id.login_password_input', 20000)); solo.enterText( (EditText) solo .findViewById('com.toptal.fitnesstracker.R.id.login_password_input'), '123456'); solo.sendKey(ExtSolo.ENTER); solo.sleep(500); assertTrue( 'Wait for button (id: com.toptal.fitnesstracker.R.id.parse_login_button) failed.', solo.waitForButtonById( 'com.toptal.fitnesstracker.R.id.parse_login_button', 20000)); solo.clickOnButton((Button) solo .findViewById('com.toptal.fitnesstracker.R.id.parse_login_button')); assertTrue('Wait for text fitness list activity.', solo.waitForActivity(FitnessActivity.class)); assertTrue('Wait for text KM.', solo.waitForText('KM', 20000)); /* Custom class that enables proper clicking of ActionBar action items */ TestUtils.customClickOnView(solo, R.id.action_logout); solo.waitForDialogToOpen(); solo.waitForText('OK'); solo.clickOnText('OK'); assertTrue('waiting for ParseLoginActivity after logout', solo.waitForActivity(ParseLoginActivity.class)); assertTrue( 'Wait for button (id: com.toptal.fitnesstracker.R.id.parse_login_button) failed.', solo.waitForButtonById( 'com.toptal.fitnesstracker.R.id.parse_login_button', 20000)); } catch (AssertionFailedError e) { solo.fail( 'com.example.android.apis.test.Test.testRecorded_scr_fail', e); throw e; } catch (Exception e) { solo.fail( 'com.example.android.apis.test.Test.testRecorded_scr_fail', e); throw e; } } }
Приемочное / регрессионное тестирование автоматизирует часть заключительного этапа тестирования в реальной 100% -ной среде Android. На этом уровне мы не используем макеты классов ОС Android - тесты проводятся на реальных устройствах и эмуляторах.
Эти обстоятельства делают процесс намного более нестабильным из-за разнообразия физических устройств, конфигураций эмуляторов, состояний устройств и наборов функций каждого устройства. Кроме того, решение о том, как будет отображаться контент, во многом зависит от версии операционной системы и размера экрана телефона.
Немного сложно создать правильный тест, который проходит на широком спектре устройств, но, как всегда, вы должны мечтать о большом, но начинать с малого. Создание тестов с Robotium - это итеративный процесс. С помощью нескольких уловок это можно значительно упростить.
роботы - это среда автоматизации тестирования Android с открытым исходным кодом, которая существует с января 2010 года. Стоит отметить, что Robotium является платным решением, но поставляется с справедливой бесплатной пробной версией.
Чтобы ускорить процесс написания тестов Robotium, мы перейдем от написания тестов вручную к записи тестов. Компромисс между качеством кода и скоростью. Если вы вносите серьезные изменения в свой пользовательский интерфейс, вы получите большую пользу от подхода к записи тестов и возможности быстро записывать новые тесты.
Регистратор Testdroid это бесплатная программа для записи тестов, которая создает тесты Robotium по мере того, как записывает щелчки, которые вы выполняете в пользовательском интерфейсе. Установить инструмент очень просто, так как описаны в их документации в сопровождении пошагового видео.
Поскольку Testdroid Recorder является подключаемым модулем Eclipse, и в этой статье мы ссылаемся на Android Studio, в идеале это могло бы стать поводом для беспокойства. Однако в этом случае это не проблема, так как вы можете использовать плагин напрямую с APK и записывать тесты для него.
Создав тесты, вы можете скопировать и вставить их в Android Studio вместе с любыми зависимостями, которые требует рекордер Testdroid, и все готово. Записанный тест будет выглядеть примерно так:
gradle connectedAndroidTest
Если вы присмотритесь, вы заметите, какая часть кода довольно прямолинейна.
Записывая тесты, не упускайте из виду «подождите». Дождитесь появления диалогов, действий, текстов. Это гарантирует, что действие и иерархия представлений будут готовы к взаимодействию, когда вы выполните действие на текущем экране. Заодно сделайте скриншоты. Автоматические тесты обычно не требуют вмешательства, и снимки экрана - один из способов увидеть, что на самом деле произошло во время этих тестов.
Независимо от того, пройдены ли тесты или нет, отчеты - ваш лучший друг. Вы можете найти их в каталоге сборки «module / build / output / reports»:
Теоретически команда QA может записывать тесты и оптимизировать их. Это можно было сделать, приложив усилия к стандартизированной модели для оптимизации тестовых случаев. Когда вы обычно записываете тесты, вам всегда нужно настроить пару вещей, чтобы они работали безупречно.
Наконец, чтобы запустить эти тесты из Android Studio, вы можете выбрать их и запустить так же, как и модульные тесты. Из терминала это однострочный:
|_+_|
Модульное тестирование Android с помощью Robolectric происходит чрезвычайно быстро, потому что оно выполняется непосредственно в JVM на вашем компьютере. По сравнению с этим приемочное тестирование эмуляторов и физических устройств намного медленнее. В зависимости от размера потоков, которые вы тестируете, это может занять от нескольких секунд до нескольких минут на один тестовый пример. Фаза приемочного тестирования должна использоваться как часть автоматизированного процесса сборки на сервере непрерывной интеграции.
Скорость можно повысить за счет распараллеливания на нескольких устройствах. Оцените этот отличный инструмент от Джейк Уортон и ребята на площади http://square.github.io/spoon/ . Там тоже есть хорошие репортажи.
Доступно множество инструментов для тестирования Android, и по мере развития экосистемы процесс создания тестируемой среды и написания тестов станет проще. Есть еще много проблем, которые нужно решить, а широкое сообщество разработчиков, работающих над повседневными проблемами, оставляет много возможностей для конструктивного обсуждения и быстрой обратной связи.
Используйте подходы, описанные в этом руководстве по тестированию Android, чтобы помочь вам решить стоящие перед вами задачи. Если и когда вы столкнетесь с проблемами, просмотрите эту статью или ссылки, связанные с ней, чтобы найти решения известных проблем.
В одном из будущих постов мы обсудим распараллеливание, автоматизацию сборки, непрерывную интеграцию, перехватчики Github / BitBucket, управление версиями артефактов и лучшие практики для более глубокого управления крупными проектами мобильных приложений.