92
ユニットテスト入門 Androidアプリケーションへのユニットテスト導入

Unit test in android

Embed Size (px)

DESCRIPTION

Unit test in Android using Robolectric.

Citation preview

Page 1: Unit test in android

ユニットテスト入門Androidアプリケーションへのユニットテスト導入

Page 2: Unit test in android

Tatsuya MakiAndroid Application Developer

Page 3: Unit test in android

はなすこと1. ユニットテストの導入

2. 効率的なユニットテスト

3. ユニットテストのTips

Page 4: Unit test in android

ユニットテストの導入

1

Page 5: Unit test in android

Q. ユニットテストとは

Page 6: Unit test in android

A. メソッドなど小さな単位で行うテスト

Page 7: Unit test in android

public static String fizzbuzz(int value) { String message; if (value % 15 == 0) { message = "FizzBuzz"; } else if (value % 5 == 0) { message = "Buzz"; } else if (value % 3 == 0) { message = "Fizz"; } else { message = String.valueOf(value); } return message; }

FizzBuzz

Page 8: Unit test in android

@Test public void fizzbuzzShouldReturnBuzzWhenValueIs10() { // 実行結果 String actualValue = fizzbuzz(10); ! // 期待結果 String expectedValue = "Buzz"; ! // テスト assertThat(actualValue, is(expectedValue)); }

FizzBuzzTest

Page 9: Unit test in android

Androidでの問題点1. デバイスが必須

2. 実行速度が遅い

3. テストしにくい

Page 10: Unit test in android

Robolectrichttp://robolectric.org/

Page 11: Unit test in android
Page 12: Unit test in android

遅い

DalvikVM

必要

JUnit3

速い

JVM

不要

JUnit4

AndroidTestCase

実行速度

VM

デバイス

JUnit

Robolectric

Page 13: Unit test in android
Page 14: Unit test in android

TextView Shadow

getText()

“Hello, world.”

Page 15: Unit test in android

@Implements(TextView.class) class MyShadowTextView extends ShadowView { ! @Implementation public CharSequence getText() { return "Hello, Robolectric!"; } !}

Shadowの例

Page 16: Unit test in android

@Test @Config(shadows = { MyShadowTextView.class }) public void getTextShouldReturnHelloRobolectric() { // SetUp Context context = Robolectric.application.getApplicationContext(); TextView textView = new TextView(context); // Exercise CharSequence actualText = textView.getText(); // Verify assertEquals(actualText, "Hello, Robolectric!"); }

Shadowの利用例

Page 17: Unit test in android

まとめ1. デバイスが不要

2. JVM上で動作する

3. Shadowオブジェクト

Page 18: Unit test in android

効率的なユニットテスト

2

Page 19: Unit test in android

3つのツール1. FEST

2. Mockito

3. EclEmma

Page 20: Unit test in android

FESThttps://code.google.com/p/fest/

Page 21: Unit test in android

Q.アサーションとは

Page 22: Unit test in android

A. 実行結果と期待結果を比較検証する宣言

Page 23: Unit test in android

// 実行結果 int actualValue = 10 / 2; !// 期待結果 int expectedValue = 5; !// "10 / 2"が"5"と等しくなることを表明 assertEquals(actualValue, expectedValue);

アサーションの例

Page 24: Unit test in android

FESTのメリット1. アサーションの記述が容易

2. エラーメッセージが明確

3. Androidと親和性が高い

Page 25: Unit test in android

アサーションの記述が容易

Page 26: Unit test in android

// Exercise List<String> devices = new ArrayList<String>(); devices.add("Nexus 5"); devices.add("Nexus 7"); !!// Verify assertNotNull(devices); assertEquals(2, devices.size()); assertTrue(devices.contains("Nexus 5"));

JUnit

Page 27: Unit test in android

// Exercise List<String> devices = new ArrayList<String>(); devices.add("Nexus 5"); devices.add("Nexus 7"); !!// Verify assertThat(devices, is(notNullValue())); assertThat(devices.size(), is(equalTo(2))); assertThat(devices, hasItem("Nexus 5"));

Hamcrest

Page 28: Unit test in android

// Exercise List<String> devices = new ArrayList<String>(); devices.add("Nexus 5"); devices.add("Nexus 7"); !!// Verify assertThat(devices) .isNotNull() .hasSize(2) .contains("Nexus 5");

FEST

Page 29: Unit test in android

エラーメッセージが明確

Page 30: Unit test in android

// テストコード List<String> devices = new ArrayList<String>(); devices.add("Nexus 5"); devices.add("Nexus 7"); assertTrue(devices.contains("Nexus 4")); !!// エラーメッセージ java.lang.AssertionError !!!!

JUnit

Page 31: Unit test in android

// テストコード List<String> devices = new ArrayList<String>(); devices.add("Nexus 5"); devices.add("Nexus 7"); assertThat(devices, hasItem("Nexus 4")); !!// エラーメッセージ java.lang.AssertionError: Expected: a collection containing "Nexus 4" but: was "Nexus 5", was "Nexus 7” !!

Hamcrest

Page 32: Unit test in android

// テストコード List<String> devices = new ArrayList<String>(); devices.add("Nexus 5"); devices.add("Nexus 7"); assertThat(devices).contains("Nexus 4"); !!// エラーメッセージ java.lang.AssertionError: expecting: <['Nexus 5', 'Nexus 7']> to contain: <['Neuxus 4']> but could not find: <['Neuxus 4']>

FEST

Page 33: Unit test in android

Androidと親和性が高い

Page 34: Unit test in android

// テストコード TextView textView = (TextView) activity .findViewById(R.id.text_view); assertThat(textView.getText()) .isEqualTo(activity.getString(R.string.message)); assertThat(textView.getVisibility()) .isEqualTo(View.VISIBLE); !!// エラーメッセージ org.junit.ComparisonFailure: expected: <[0]> but was: <[8]>

FEST

Page 35: Unit test in android

// テストコード TextView textView = (TextView) activity .findViewById(R.id.text_view); assertThat(textView) .hasText(R.string.message) .isVisible(); !!!// エラーメッセージ java.lang.AssertionError: Expected to be visible but was gone !

FEST Android

Page 36: Unit test in android

Mockitohttp://mockito.org/

Page 37: Unit test in android

Q. モックオブジェクトとは

Page 38: Unit test in android

A. オブジェクトの呼び出しを検証する

Page 39: Unit test in android

class MockInputStream extends InputStream { ! private boolean mIsRead; private boolean mIsClosed; ! @Override public int read() throws IOException { mIsRead = true; return 0; } ! public boolean isRead() { return mIsRead; } ! @Override public void close() throws IOException { mIsClosed = true; } ! public boolean isClosed() { return mIsClosed; } !}

モックオブジェクトの例

Page 40: Unit test in android

Mockitoでできること1. 呼び出しの検証

2. 振る舞いの変更

3. フィールドの変更

Page 41: Unit test in android

// モックオブジェクトの生成 InputStream mocked = mock(InputStream.class); // InputStreamをクローズする mocked.close(); !// closeメソッドの呼び出し検証 verify(mocked).close();

呼び出しの検証

Page 42: Unit test in android

振る舞いの変更

// モックオブジェクトの作成 InputStream mocked = mock(InputStream.class); !// 戻り値の変更 when(mocked.read()).thenReturn(-1); !// 例外の送出 when(mocked.read()).thenThrow(new IOException()); !// ロジックの変更 when(mocked.read()).thenAnswer(new Answer<Integer>() { ! @Override public Integer answer(InvocationOnMock invocation) throws Throwable { int length; ... return length; } });

Page 43: Unit test in android

フィールドの変更

// 内部にInputStreamを保持するクラス MyObject object = new MyObject(); // フィールドの取得 InputStream stream = (InputStream) Whitebox .getInternalState(object, “mStream"); !// スパイオブジェクトの作成 Socket spied = spy(stream); !// フィールドの変更 Whitebox .setInternalState(object, "mStream", spied);

Page 44: Unit test in android

Mockitoでできないこと1. finalクラス/メソッドのモック

2. privateメソッドのモック

3. staticメソッドのモック

Page 45: Unit test in android

EclEmmahttp://www.eclemma.org/

Page 46: Unit test in android

Q. カバレッジとは

Page 47: Unit test in android

A. テストがどれだけ網羅できているか

Page 48: Unit test in android

カバレッジの種類C0: 命令網羅率

C1: 分岐網羅率

C2: 条件網羅率

Page 49: Unit test in android

EclEmmaで測定できるものC0: 命令網羅率

C1: 分岐網羅率

C2: 条件網羅率

Page 50: Unit test in android

EclEmmaで測定できないものC0: 命令網羅率

C1: 分岐網羅率

C2: 条件網羅率

Page 51: Unit test in android

ユニットテストのTips

3

Page 52: Unit test in android

Q. void型メソッドをテストしたい

Page 53: Unit test in android

A. 別のメソッドを使って検証する

Page 54: Unit test in android

List<String> list = new MyList<String>(); list.add("Android"); // 別のメソッドで検証 assertThat(list).hasSize(1);

サンプル

Page 55: Unit test in android

A. メソッドの呼び出しを検証する

Page 56: Unit test in android

InputStream mocked = mock(InputStream.class); IoUtils.close(mocked); !// closeメソッドの呼び出し検証 verify(mocked).close();

サンプル

Page 57: Unit test in android

Q. privateメソッドをテストしたい

Page 58: Unit test in android

A. 諦める

Page 59: Unit test in android

A. package privateに変更する

Page 60: Unit test in android

// privateメソッドなので呼び出せない private String buildMessage() { String message; // do something return message; } !// package privateに変更 String buildMessage() { String message; // do something return message; }

サンプル

Page 61: Unit test in android

Q. 非同期処理をテストしたい

Page 62: Unit test in android

A. CountDownLatchを使う

Page 63: Unit test in android

// timeoutを指定 @Test(timeout = 1000) public void test() throws InterruptedException{ CountDownLatch latch = new CountDownLatch(1); AsyncProcess.execute(new Callback() { @Override public void onComplete() { // 処理完了時にcountDownを呼出 latch.countDown(); } }); ! // 処理完了まで待機 latch.await(); ! ... }

サンプル

Page 64: Unit test in android

Q. HTTP通信の外部依存をなくしたい

Page 65: Unit test in android

A. FakeHttpLayerを使うHttpClientの場合

Page 66: Unit test in android

// 常に同じレスポンスを返却 FakeHttpLayer layer = Robolectric.getFakeHttpLayer(); layer.setDefaultHttpResponse(200, "Hello, World!"); !!// 順番にレスポンスを変更 FakeHttpLayer layer = Robolectric.getFakeHttpLayer(); layer.addPendingHttpResponse(200, "Hello, Nexus 4!"); layer.addPendingHttpResponse(200, "Hello, Nexus 5!"); layer.addPendingHttpResponse(200, "Hello, Nexus 7!"); !!// 動的にレスポンスを変更 FakeHttpLayer layer = Robolectric.getFakeHttpLayer(); layer.addHttpResponseRule(new MyResponseRule());

サンプル

Page 67: Unit test in android

A. URLStreamHandlerを設定するHttpURLConnectionの場合

Page 68: Unit test in android

class StubURLStreamHandler extends URLStreamHandler { @Override protected URLConnection openConnection(URL url) throws IOException { // HttpURLConnectionを作成 return new StuHttpURLConnection(url); } } !class StubURLStreamHandlerFactory implements URLStreamHandlerFactory { @Override public URLStreamHandler createURLStreamHandler( String protocol) { // URLStreamHandlerを作成 return new StubURLStreamHandler(); } } !// URLStreamHandlerFactoryを設定 URL.setURLStreamHandlerFactory( new StubURLStreamHandlerFactory());

サンプル

Page 69: Unit test in android

Q. データベースのスローテストを解消したい

Page 70: Unit test in android

A. インメモリデータベースを使う

Page 71: Unit test in android

// DatabaseMapのinterfaceを実装 class MemoryDatabaseMap implements DatabaseMap { ! @Override public String getDriverClassName() { return JDBC.class.getName(); } ! @Override public String getConnectionString(File file) { // インメモリデータベースを使うように指定 return "jdbc:sqlite::memory:"; } ! @Override public String getMemoryConnectionString() { // インメモリデータベースを使うように指定 return "jdbc:sqlite::memory:"; } ! @Override public int getResultSetType() { return ResultSet.TYPE_FORWARD_ONLY; } ! @Override public String getSelectLastInsertIdentity() { return "SELECT last_insert_rowid() AS id"; } !}

サンプル

Page 72: Unit test in android

// UsingDatabaseMapで指定 @UsingDatabaseMap(MemoryDatabaseMap.class) @RunWith(RobolectricTestRunner.class) class DatabaseHelperTest { ... }

サンプル

Page 73: Unit test in android

Q. Activityをテストしたい

Page 74: Unit test in android

A. ActivityControllerを使う

Page 75: Unit test in android

// ActivityControllerを生成 ActivityController<MainActivity> controller = Robolectric.buildActivity(MainActivity.class); !// Activityの生成 controller.create().start().resume().visible(); !// Activityの取得 MainActivity activity = controller.get(); // Activityの操作 TextView textView = (TextView) activity .findViewById(R.id.text_view); ... !// Activityの破棄 controller.pause().stop().destroy();

サンプル

Page 76: Unit test in android

Q. Serviceを検証したい

Page 77: Unit test in android

A. ライフサイクルに合わせてメソッドを呼び出す

Page 78: Unit test in android

private MyService mService; !@Before public void setUp() { // Serviceを生成 mService = new MyService(); mService.onCreate(); } @Test public void test() { // Serviceを実行 Intent intent = new Intent(Intent.ACTION_SEARCH); intent.putExtra(SearchManager.QUERY, "Hello, World!"); mService.onStartCommand(intent, 0, 0); } !@After public void tearDown() { // Serviceを破棄 mService.onDestroy(); }

サンプル

Page 79: Unit test in android

Q. Widgetのテストをしたい

Page 80: Unit test in android

A. ShadowAppWidgetManagerを使う

Page 81: Unit test in android

// ShadowAppWidgetManagerの生成 Context context = Robolectric.application .getApplicationContext(); AppWidgetManager manager = AppWidgetManager.getInstance(context); ShadowAppWidgetManager shadowManager = Robolectric.shadowOf(manager); // Widgetの生成 int widgetId = shadowManager.createWidget( MyWidgetProvider.class, R.layout.activity_main); // Viewの取得 View widgetView = shadowManager.getViewFor(widgetId); !...

サンプル

Page 82: Unit test in android

Q. ContentProviderのテストをしたい

Page 83: Unit test in android

A. ShadowContentResolverを使う

Page 84: Unit test in android

// ShadowContentResolverの生成 ContentResolver resolver = Robolectric.application.getContentResolver(); ShadowContentResolver shadowResolver = Robolectric.shadowOf(resolver); !// ContentProviderの登録 shadowResolver.registerProvider( “com.example.android.unittest” new MyContentProvider()); ... // INSERTステートメントの取得 List<InsertStatement> insertStatements = shadowResolver.getInsertStatements(); // UPDATEステートメントの取得 List<UpdateStatement> updateStatements = shadowResolver.getUpdateStatements(); // DELETEステートメントの取得 List<DeleteStatement> deleteStatements = shadowResolver.getDeleteStatements(); // notifyChangeで通知されたURIの取得 List<NotifiedUri> notifiedUris = shadowResolver.getNotifiedUris();

サンプル

Page 85: Unit test in android

Q. BroadcastReceiverのテストをしたい

Page 86: Unit test in android

A. ShadowApplicationを使う

Page 87: Unit test in android

// 事前処理 ShadowApplication shadowApplication = Robolectric.getShadowApplication(); Context context = Robolectric.application.getApplicationContext(); // 登録済みBroadcastReceiverの取得 List<Wrapper> registered = shadowApplication.getRegisteredReceivers(); // Intentに該当するBroadcastReceiverの取得 Intent intent = new Intent("MY_ACTION"); List<BroadcastReceiver> receivers = shadowApplication.getReceiversForIntent(intent); // Intentを擬似的に受信 BroadcastReceiver receiver = receivers.get(0); receiver.onReceive(context, intent);

サンプル

Page 88: Unit test in android

まとめ

4

Page 89: Unit test in android

まとめ1. 課題はRobolectricで解消できる

2. 既存のライブラリを使って効率的に

3. 大体テストできるので書いてみよう

Page 90: Unit test in android

Q. おすすめの本はなんですか

Page 91: Unit test in android
Page 92: Unit test in android

https://github.com/t28hub/UnitTestInAndroid/