Android 테스팅 튜토리얼: 진정한 친환경 Droid와 같은 단위 테스팅
게시 됨: 2022-03-11경험 많은 앱 개발자로서, 우리가 개발하는 애플리케이션이 성숙해짐에 따라 우리는 테스트를 시작할 때라는 직감을 느낍니다. 비즈니스 규칙은 종종 시스템이 다양한 릴리스에서 안정성을 제공해야 함을 의미합니다. 또한 이상적으로는 빌드 프로세스를 자동화하고 애플리케이션을 자동으로 게시하고 싶습니다. 이를 위해 빌드가 예상대로 작동하는지 확인하기 위해 Adnroid 테스트 도구가 필요합니다.
테스트는 우리가 구축하는 것에 대해 추가적인 수준의 자신감을 제공할 수 있습니다. 완벽하고 버그가 없는 제품을 만드는 것은 (불가능하지는 않더라도) 어렵습니다. 따라서 우리의 목표는 애플리케이션에 새로 도입된 버그를 빠르게 찾아낼 수 있는 테스트 제품군을 설정하여 시장에서 성공할 확률을 높이는 것입니다.
Android 및 다양한 모바일 플랫폼의 경우 일반적으로 앱 테스트가 어려울 수 있습니다. 단위 테스트를 구현하고 테스트 주도 개발 또는 이와 유사한 원칙을 따르는 것은 최소한 직관적이지 않게 느껴질 수 있습니다. 그럼에도 불구하고 테스트는 중요하며 당연시하거나 무시해서는 안됩니다. David, Kent 및 Martin은 "TDD는 죽었습니까?"라는 제목의 기사에서 편집된 일련의 대화에서 테스트의 이점과 함정에 대해 논의했습니다. 또한 그곳에서 실제 화상 대화를 찾아 테스트가 개발 프로세스에 적합하고 어느 정도 통합할 수 있는지 지금부터 더 많은 통찰력을 얻을 수 있습니다.
이 Android 테스트 자습서에서는 Android에서 단위 및 수락, 회귀 테스트를 안내합니다. 개발자-QA 피드백 주기를 단축하기 위해 프로세스를 최대한 빠르고 간단하게 만드는 데 중점을 두고 Android에서 테스트 단위의 추상화에 초점을 맞춘 다음 승인 테스트의 예에 중점을 둘 것입니다.
읽어야 하나?
이 자습서에서는 Android 애플리케이션을 테스트할 때 다양한 가능성을 탐색합니다. Android 플랫폼의 현재 테스트 가능성을 더 잘 이해하려는 개발자 또는 프로젝트 관리자는 이 기사에서 언급한 접근 방식을 취하려는 경우 이 튜토리얼을 사용할 수 있습니다. 그러나 이러한 주제와 관련된 논의는 기한, 코드베이스 코드 품질, 시스템 결합 수준, 아키텍처 디자인에 대한 개발자의 선호도, 기능의 예상 수명과 함께 본질적으로 제품마다 다르기 때문에 이것은 만병통치약이 아닙니다. 테스트 등
단위로 생각하기: Android 테스팅
이상적으로는 아키텍처의 하나의 논리적 단위/구성 요소를 독립적으로 테스트하려고 합니다. 이렇게 하면 구성 요소가 우리가 기대하는 입력 집합에 대해 제대로 작동하는지 확인할 수 있습니다. 종속성을 조롱할 수 있으므로 빠르게 실행되는 테스트를 작성할 수 있습니다. 또한 테스트에 제공된 입력을 기반으로 다양한 시스템 상태를 시뮬레이션할 수 있으며 프로세스의 특이한 경우를 다룹니다.
Android 단위 테스트의 목표는 프로그램의 각 부분을 분리하고 개별 부분이 올바른지 보여주는 것입니다. 단위 테스트는 코드 조각이 충족해야 하는 엄격한 서면 계약을 제공합니다. 결과적으로 여러 이점을 제공합니다. —위키피디아
Robolectric
Robolectric은 개발 워크스테이션의 JVM 내부에서 테스트를 실행할 수 있는 Android 단위 테스트 프레임워크입니다. Robolectric은 로드되는 Android SDK 클래스를 다시 작성하고 일반 JVM에서 실행할 수 있도록 하여 테스트 시간을 단축합니다. 또한 Android 기기의 기본 C 코드로 구현된 보기, 리소스 로드 및 기타 항목을 처리하므로 자동화된 테스트를 실행하기 위해 에뮬레이터와 물리적 기기가 필요하지 않습니다.
모키토
Mockito는 자바로 깨끗한 테스트를 작성할 수 있게 해주는 모의 프레임워크입니다. 프로덕션에 사용되는 구성 요소/모듈의 원래 종속성을 대체하는 데 사용되는 테스트 더블(모의) 생성 프로세스를 단순화합니다. StackOverflow 답변은 자세한 내용을 읽기 위해 읽을 수 있는 상당히 간단한 용어로 모의 객체와 스텁의 차이점에 대해 설명합니다.
// 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<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { ArrayList<FitnessEntry> 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를 사용하면 테스트에서 실행하는 특정 요청에 대한 응답을 대기열에 넣을 수 있습니다. 이것은 우리가 서버에서 기대하지만 재현하기 쉽지 않은 모호한 응답을 시뮬레이션할 수 있는 기회를 제공합니다. 약간의 추가 코드를 작성하면서 전체 범위를 보장할 수 있습니다.
MockWebServer의 코드 저장소는 이 라이브러리를 더 잘 이해하기 위해 참조할 수 있는 깔끔한 예제를 제공합니다.
맞춤형 테스트 더블
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 앱 테스트를 실행하면 대상 모듈의 "빌드" 폴더에 테스트된 클래스에 대한 보고서가 생성됩니다. 더욱이 자동화된 빌드 프로세스를 설정하려는 경우 터미널 접근 방식을 사용합니다. Gradle을 사용하면 다음을 실행하여 모든 디버그 버전 테스트를 실행할 수 있습니다.
gradle testDebug
Android Studio 버전에서 소스 세트 "테스트" 액세스
Android Studio 및 Android Gradle 플러그인 버전 1.1은 코드 단위 테스트를 지원합니다. 훌륭한 문서를 읽으면 더 자세히 알아볼 수 있습니다. 이 기능은 실험적이지만 이제 IDE에서 단위 테스트와 계측 테스트 소스 세트 사이를 쉽게 전환할 수 있으므로 훌륭한 포함입니다. IDE에서 플레이버를 전환하는 것과 같은 방식으로 작동합니다.
프로세스 완화
Android 앱 테스트를 작성하는 것은 원래 애플리케이션을 개발하는 것만큼 재미있지 않을 수 있습니다. 따라서 테스트 작성 프로세스를 쉽게 하고 프로젝트를 설정하는 동안 일반적인 문제를 피하는 방법에 대한 몇 가지 팁이 많은 도움이 될 것입니다.
AssertJ Android
AssertJ Android는 이름에서 짐작할 수 있듯이 Android를 염두에 두고 구축된 도우미 함수 집합입니다. 인기 있는 라이브러리 AssertJ의 확장입니다. AssertJ Android에서 제공하는 기능은 "assertThat(view).isGone()"과 같은 간단한 주장에서 다음과 같은 복잡한 것까지 다양합니다.
assertThat(layout).isVisible() .isVertical() .hasChildCount(4) .hasShowDividers(SHOW_DIVIDERS_MIDDLE)
AssertJ Android와 그 확장성을 통해 Android 애플리케이션용 테스트를 작성하기 위한 간단하고 좋은 시작점이 보장됩니다.

Robolectric 및 매니페스트 경로
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 구성
다음을 사용하여 단위 테스트를 위해 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' }
Robolectric 및 놀이 서비스
Google Play 서비스를 사용하는 경우 Robolectric이 이 애플리케이션 구성에서 제대로 작동하려면 Play 서비스 버전에 대해 고유한 정수 상수를 만들어야 합니다.
<meta-data android:name="com.google.android.gms.version" android:value="@integer/gms_version" tools:replace="android:value" />
라이브러리를 지원하기 위한 Robolectric 종속성
또 다른 흥미로운 테스트 문제는 Robolectric이 지원 라이브러리를 제대로 참조할 수 없다는 것입니다. 솔루션은 테스트가 있는 모듈에 "project.properties" 파일을 추가하는 것입니다. 예를 들어 Support-v4 및 AppCompat 라이브러리의 경우 파일에 다음이 포함되어야 합니다.
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
수락/회귀 테스트
수락/회귀 테스트는 실제 100% Android 환경에서 테스트의 마지막 단계의 일부를 자동화합니다. 우리는 이 수준에서 조롱된 Android OS 클래스를 사용하지 않습니다. 테스트는 실제 기기와 에뮬레이터에서 실행됩니다.
이러한 상황은 다양한 물리적 장치, 에뮬레이터 구성, 장치 상태 및 각 장치의 기능 집합으로 인해 프로세스를 훨씬 더 불안정하게 만듭니다. 또한 콘텐츠가 표시되는 방식을 결정하는 것은 운영 체제의 버전과 전화기의 화면 크기에 따라 크게 달라집니다.
다양한 장치를 통과하는 올바른 테스트를 만드는 것은 약간 복잡하지만 항상 그렇듯이 큰 꿈을 꾸되 작게 시작해야 합니다. Robotium을 사용한 테스트 생성은 반복적인 프로세스입니다. 몇 가지 요령으로 많이 단순화할 수 있습니다.
로보티움
Robotium은 2010년 1월부터 존재한 오픈 소스 Android 테스트 자동화 프레임워크입니다. Robotium은 유료 솔루션이지만 공정한 무료 평가판이 제공된다는 점을 언급할 가치가 있습니다.
Robotium 테스트 작성 프로세스의 속도를 높이기 위해 수동 테스트 작성에서 테스트 녹음으로 이동할 것입니다. 균형은 코드 품질과 속도 사이에 있습니다. 사용자 인터페이스를 크게 변경하는 경우 테스트 기록 접근 방식과 새 테스트를 빠르게 기록할 수 있어 많은 이점을 얻을 수 있습니다.
Testdroid Recorder는 사용자 인터페이스에서 수행하는 클릭을 기록할 때 Robotium 테스트를 생성하는 무료 테스트 레코더입니다. 단계별 비디오와 함께 설명서에 설명된 대로 도구를 설치하는 것은 매우 쉽습니다.
Testdroid Recorder는 Eclipse 플러그인이고 이 기사 전체에서 Android Studio를 언급하고 있기 때문에 이상적으로는 문제가 될 것입니다. 그러나 이 경우 APK와 함께 플러그인을 직접 사용하고 이에 대한 테스트를 기록할 수 있으므로 문제가 되지 않습니다.
테스트를 생성하면 Testdroid 레코더에 필요한 모든 종속성과 함께 Android Studio에 복사하여 붙여넣을 수 있습니다. 기록된 테스트는 아래 클래스와 유사합니다.
public class LoginTest extends ActivityInstrumentationTestCase2<Activity> { 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<Activity>) 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; } } }
자세히 보면 얼마나 많은 코드가 직관적인지 알 수 있습니다.
테스트를 기록할 때 "대기" 문을 부족하게 사용하지 마십시오. 대화 상자가 표시되고 활동이 표시되고 텍스트가 표시될 때까지 기다립니다. 이렇게 하면 현재 화면에서 작업을 수행할 때 활동과 보기 계층 구조가 상호 작용할 준비가 됩니다. 동시에 스크린샷을 찍습니다. 자동화된 테스트는 일반적으로 무인 상태이며 스크린샷은 해당 테스트 중에 실제로 어떤 일이 발생했는지 확인할 수 있는 방법 중 하나입니다.
테스트가 통과하든 실패하든 보고서는 가장 친한 친구입니다. 빌드 디렉토리 "module/build/outputs/reports"에서 찾을 수 있습니다.
이론적으로 QA 팀은 테스트를 기록하고 최적화할 수 있습니다. 테스트 케이스를 최적화하기 위한 표준화된 모델에 노력을 기울임으로써 완료될 수 있었습니다. 일반적으로 테스트를 기록할 때 완벽하게 작동하려면 항상 몇 가지를 조정해야 합니다.
마지막으로 Android Studio에서 이러한 테스트를 실행하려면 단위 테스트를 실행하는 것처럼 테스트를 선택하고 실행할 수 있습니다. 터미널에서 한 줄로 표시됩니다.
gradle connectedAndroidTest
테스트 성능
Robolectric을 사용한 Android 단위 테스트는 시스템의 JVM 내에서 직접 실행되기 때문에 매우 빠릅니다. 그에 비해 에뮬레이터 및 물리적 장치에 대한 승인 테스트는 훨씬 느립니다. 테스트하는 흐름의 크기에 따라 테스트 케이스당 몇 초에서 몇 분이 소요될 수 있습니다. 승인 테스트 단계는 지속적 통합 서버에서 자동화된 빌드 프로세스의 일부로 사용해야 합니다.
여러 장치에서 병렬화하여 속도를 향상시킬 수 있습니다. Jake Wharton과 Square http://square.github.io/spoon/에서 이 훌륭한 도구를 확인하십시오. 좋은 보고도 있습니다.
테이크아웃
다양한 Android 테스트 도구가 있으며 생태계가 성숙해짐에 따라 테스트 가능한 환경을 설정하고 테스트를 작성하는 프로세스가 더 쉬워질 것입니다. 아직 해결해야 할 과제가 더 많고 일상적인 문제를 해결하는 광범위한 개발자 커뮤니티와 함께 건설적인 토론과 빠른 피드백을 위한 여지가 많습니다.
이 Android 테스트 튜토리얼에 설명된 접근 방식을 사용하여 여러분 앞에 놓인 문제를 해결하는 데 도움을 받으세요. 문제가 발생하면 이 문서 또는 알려진 문제에 대한 솔루션에 링크된 참조를 다시 확인하십시오.
향후 게시물에서는 병렬화, 빌드 자동화, 지속적인 통합, Github/BitBucket 후크, 아티팩트 버전 관리 및 대규모 모바일 애플리케이션 프로젝트를 더 깊이 있게 관리하기 위한 모범 사례에 대해 논의할 것입니다.