atec mtg7 unittest
TRANSCRIPT
Testterを叩け!(ユニットテスト的な意味で)
ユニットテストの事例
2011.05.25 ATEC Mtg#7
2011年5月26日木曜日
自己紹介
• Twitter id:@nowsprinting
• 名前:長谷川 孝二
• 職種:ワンマン社長(ワンマンバス的な意味で)
• 仕事:Java, Swing, Do-Ja, iOS(SIer/請負)
2011年5月26日木曜日
Agenda
• 今日お話する範囲の定義(再確認)• JUnitの基礎的なこと
• Android Testing Framework の使用例
• Android Mock の使用例
2011年5月26日木曜日
再確認
2011年5月26日木曜日
©snskさん at ATEC Mtg#12011年5月26日木曜日
©snskさん at ATEC Mtg#1
今日はここだけ
あえて除外(切り分けたい)
2011年5月26日木曜日
具体的には?
2011年5月26日木曜日
受け入れシステム
統合
ユニット
Activity遷移
ユースケース
Http通信
非機能要件
テストすべき内容(観点)の分布
ビジネスロジックLib正常系(Mock)
Lib異常系(Mock)
2011年5月26日木曜日
受け入れシステム
統合
ユニット
Activity遷移
ユースケース
Http通信
非機能要件
テストすべき内容→テスト方法
Robolectric AndroidTest
MonkeyRunner
ビジネスロジックLib正常系(Mock)
Lib異常系(Mock)
God Hand
2011年5月26日木曜日
受け入れシステム
統合
ユニット
Activity遷移
ユースケース
Http通信
非機能要件
テストすべき内容→テスト方法
Robolectric AndroidTest
MonkeyRunner
ビジネスロジックLib正常系(Mock)
Lib異常系(Mock)
God Hand
Android特有の観点・低メモリでActivity開放・移動機を縦→横に・着信→suspend→resume
・電波不安定(そろそろ検討着手を希望!)
2011年5月26日木曜日
受け入れシステム
統合
ユニット
Activity遷移
ユースケース
Http通信
非機能要件
テストすべき内容→テスト方法
Robolectric AndroidTest
MonkeyRunner
ビジネスロジックLib正常系(Mock)
Lib異常系(Mock)
God Hand
今日はここだけ
InstrumentalTestCase系
2011年5月26日木曜日
JUnitの基礎的なこと
• Android Testing FrameworkはJUnit3ベース
• TestCaseからプロダクトコードを呼ぶ
• TestCase内で、Assertionによって正否を判定する。
2011年5月26日木曜日
package jp.group.android.atec.testter.logic;
public class TwitterLogicTest extends AndroidTestCase {}
単純なTestCaseの例
プロダクトコード
テストコード 同じpackage名(が便利)
Class名の後ろに”Test”JUnitのTestCaseを継承
package jp.group.android.atec.testter.logic;
public class TwitterLogic {}
2011年5月26日木曜日
public void testTwitterLogicAuthorization() { Authorization auth4test = ...
TwitterLogic target = new TwitterLogic(auth4test);
assertNotNull("Twitterインスタンスが生成されていること", target.twitter);}
単純なテストメソッドの例“test” + 対象メソッド名 + ケース名
対象メソッド呼び出し
Assertion(用途にあったものを)
第一引数に評価内容を書く(推奨)
2011年5月26日木曜日
実行結果(失敗)失敗!
赤い!
失敗!
assert()の第一引数
2011年5月26日木曜日
実行結果(成功)失敗ゼロ!
グリーン!
2011年5月26日木曜日
Android Testing Frameworkの使用例
2011年5月26日木曜日
Test Project を作る
2011年5月26日木曜日
eclipseの場合、package explorerで製品プロジェクトを選択して右クリックして、
New Test Project... を選択。
2011年5月26日木曜日
Dialog (1/2)TestProject名
製品Project名(テスト対象)
下にスクロール2011年5月26日木曜日
Dialog (2/2)
テストアプリケーションのpackage名
• 製品とテストを同じパッケージ名にしない。個別にインストールできなくなる。
• 各Classのpackage名とは別なので混同しない。
2011年5月26日木曜日
Why?
2011年5月26日木曜日
Android Testing Frameworkは、エミュレータ/実機にインストールされ実行されるアプリケーション ・・なので、
2011年5月26日木曜日
Android Testing Frameworkは、エミュレータ/実機にインストールされ実行されるアプリケーション ・・なので、
実行まで、ちょっと時間がかかります・・・
2011年5月26日木曜日
Android Testing Frameworkは、エミュレータ/実機にインストールされ実行されるアプリケーション ・・なので、
• 製品とテストを同じパッケージ名にしない。個別にインストールできなくなる。
• 各Classのpackage名とは別なので混同しない。こちらは同じpackage名にすることを推奨。
2011年5月26日木曜日
TestCaseを書く
2011年5月26日木曜日
Test Cases (1/2)• AndroidTestCase
• ApplicationTestCase
• LoaderTestCase
• ProviderTestCase2
• ServiceTestCase
※すべて、JUnitのTestCaseのサブクラス。いずれかをテストの 目的によって使い分ける。
2011年5月26日木曜日
Test Cases (1/2)• AndroidTestCase
• ApplicationTestCase
• LoaderTestCase
• ProviderTestCase2
• ServiceTestCase
※すべて、JUnitのTestCaseのサブクラス。いずれかをテストの 目的によって使い分ける。
今のTestterにはこの辺を使う機能がない!
2011年5月26日木曜日
Test Cases (2/2)• InstrumentationTestCase
• ActivityTestCase
• ActivityInstrumentationTestCase2
• ActivityUnitTestCase
• SingleLaunchActivityTestCase
• SyncBaseInstrumentation
2011年5月26日木曜日
AndroidTestCase
2011年5月26日木曜日
AndroidTestCase
• ロジックのテストに使う(非Activity)
• Contextを返す getContext() が利用可能(製品側のContextを得られる)
2011年5月26日木曜日
public class PreferenceLogicTest extends AndroidTestCase {
public void testWriteReadToken() { PreferenceLogic logic = PreferenceLogic.getInstance();
logic.writeToken(getContext(), new AccessToken("11111", "22222")); AccessToken token = logic.readToken(getContext());
assertEquals("11111", token.getToken()); assertEquals("22222", token.getTokenSecret()); }
}
2011年5月26日木曜日
ActivityInstrumentationTestCase2
2011年5月26日木曜日
ActivityInstrumentationTestCase2
• Activityのテストに使う
• Activityの遷移を伴なうテストも可能。(今回は説明しません)
• Contextを返す getContext() が利用可能
• ActivityInstrumentationTestCaseは@Deprecated
2011年5月26日木曜日
package jp.group.android.atec.testter;
public class AuthActivityTest extends ActivityInstrumentationTestCase2<AuthActivity> {
protected void setUp() throws Exception { super.setUp(); setActivityInitialTouchMode(true); mIntent = new Intent(); setActivityIntent(mIntent); mActivity = getActivity(); mEditText = (EditText) mActivity.findViewById(R.id.pinEdit); mButton = (Button) mActivity.findViewById(R.id.registBtn); }
2011年5月26日木曜日
package jp.group.android.atec.testter;
public class AuthActivityTest extends ActivityInstrumentationTestCase2<AuthActivity> {
protected void setUp() throws Exception { super.setUp(); setActivityInitialTouchMode(true); mIntent = new Intent(); setActivityIntent(mIntent); mActivity = getActivity(); mEditText = (EditText) mActivity.findViewById(R.id.pinEdit); mButton = (Button) mActivity.findViewById(R.id.registBtn); }
Activityを生成
コンポーネントを取得製品packageのres
2011年5月26日木曜日
public void testRegistBtnText() {// 登録ボタンのテキストが指定のものであることを確認する assertTrue(mButton.getText().toString() .equalsIgnoreCase("登録"));}
ふつうにAssertionで確認
テストメソッドでは、
2011年5月26日木曜日
コンポーネントの操作も可能※但し、UIスレッドに限る
public void testClickButton() {
mActivity.runOnUiThread(new Runnable() { public void run() { try { mButton.performClick(); assertXxx(...); } catch (RuntimeException e) { assertXxx(...); } } });
}2011年5月26日木曜日
privateを扱う
2011年5月26日木曜日
製品コードの中のprivateなフィールド、メソッド
private final PreferenceLogic preferenceLogic = PreferenceLogic.getInstance();
private Drawable getCacheDrawable(URL url) { (snip)}
2011年5月26日木曜日
リフレクションで取得することは可能
Class<AuthActivity> clazz = AuthActivity.class;Field field = clazz .getDeclaredField("preferenceLogic");
Method method = getClass() .getMethod("getCacheDrawable");method.invoke();
ですが、
2011年5月26日木曜日
素人め、間合いが遠いわ!CV:井芹さん
2011年5月26日木曜日
リフレクションの使用は Fragile Test の一因となる(テストのメンテナンスコストが上がる)ため、
private final PreferenceLogic preferenceLogic = PreferenceLogic.getInstance();
private Drawable getCacheDrawable(URL url) { (snip)}
製品コードをテストしやすく修正する。可視性をdefault(Package Private)にする等。
2011年5月26日木曜日
※実際には、こんなテストをしたかった
Class<View> clazz = View.class;Field field = clazz.getDeclaredField("mOnClickListener");field.setAccessible(true);assertTrue(field.get(mButton) != null);
AndroidSDKの中のClassの、不可視なfield
→このケースは、Mockを使用する?
2011年5月26日木曜日
例外のテスト
2011年5月26日木曜日
プロダクトコード
public AccessToken auth(String pin) { AccessToken accessToken = null; try { (snip) } catch (TwitterException e) { (snip) throw new RuntimeException(e); } return accessToken;}
例外発生==異常系をテストしたい
2011年5月26日木曜日
テストコード
try { target.auth(null); fail("Twitter#getOAuthAccessToken()で 例外が投げられるべき");} catch (RuntimeException e) { assertEquals("catchした例外の中身は正しい", expected, e.getCause());}
例外が発生しなければfail
正しい(想定通りの)例外か確認
2011年5月26日木曜日
Android Mock
2011年5月26日木曜日
Android Mockとは
• Dalvk上で使用できるEasyMockのラッパーです。
• Mockとは、本物のClassの振る舞い(メソッドの戻り値など)をエミュレートすることができる。
2011年5月26日木曜日
製品コード
public AccessToken auth(String pin) { AccessToken accessToken = null; try { if (pin == null || "".equals(pin.trim())) { accessToken = twitter.getOAuthAccessToken(); } else { (snip) } } catch (TwitterException e) { (snip) } (snip)}
Twitter4Jのインスタンス
このメソッドの戻り値を変えたい2011年5月26日木曜日
テストコード(正常系・1/2)
@UsesMocks(Twitter.class)public void testAuth_正常系_PINがnull() { // setup mock AccessToken expectedToken = new AccessToken("testToken", "testSecret");
Twitter twitterMock = AndroidMock.createMock(Twitter.class);
AndroidMock.expect(twitterMock .getOAuthAccessToken()).andReturn(expectedToken);
AndroidMock.replay(twitterMock);
アノテーションで宣言
Twitter型のMockを生成
メソッドに対する戻り値を設定
必要!2011年5月26日木曜日
テストコード(正常系・2/2) // setup test target TwitterLogic target = new TwitterLogic(); target.twitter = twitterMock;
// do test. AccessToken actual = target.auth(null); assertEquals("返されるAccessTokenは正しい", expectedToken, actual);
// 呼ばれたMockメソッドの正当性を検証。 AndroidMock.verify(twitterMock);}
Mockをテスト対象にセット
Mockに定義したメソッドを呼ばれたかチェック
2011年5月26日木曜日
テストコード(異常系)
@UsesMocks(Twitter.class)public void testAuth_異常系_認証失敗() { // setup mock TwitterException expected = new TwitterException("Unauthorized", null, 401); Twitter twitterMock = AndroidMock .createMock(Twitter.class); AndroidMock.expect(twitterMock .getOAuthAccessToken()).andThrow(expected);
AndroidMock.replay(twitterMock);
メソッドが呼ばれたら例外発生
2011年5月26日木曜日
良いテストコードを書こう!
http://www.slideshare.net/goyoki/an-approach-to-improving-the-maintainability-of-unit-tests-xpjugkansai2011
2011年5月26日木曜日
まとめ
• backlogのwiki嫁
• 井芹さんのスライド嫁
2011年5月26日木曜日
ご静聴ありがとうございました!
2011.05.25 20:01
2011年5月26日木曜日