2012年2月18日土曜日
Developers Summit 2012 17-D-7 実践Androidデベロッパーズテストにて講演してきました。
講演資料はスライドシェアにあります。
こちらの26ページから56ページを担当させて頂きました。
また、その期間のツイートはtogetterにまとめてあります。
なお、今回の講演にあたってサンプルで作成していたアプリケーション「お小遣い・ログ」のソースコードはGitHubにて公開しています。
Twitter上で私をフォローしていただいている方はご存じの方も多いかもしれませんが、最近の私のTwitterのプロファイルは「冬眠中」となっています。
実は、これは文字通り意味していまして、若干過眠症気味な状態になっています。
そういったわけで、今回資料を作成するにあたっては、メンバーの皆さん @snskさん、@nowsprintingさん、@ussy00さんには大変ご迷惑をおかけいたしました。
ここで感謝を表したいと思います。
また、今回の資料を作成するにあたって、何度か会社を休んでしまいました。
相談に乗っていただいた株式会社トップゲートの加藤社長ならびに同僚の皆様にも大変感謝しております。
2012年2月13日月曜日
AndroidのUnit Testをするなら読んでおきたいコード
単なるリンクメモ
Activityのテスト関連
android.test.ActivityInstrumentationTestCase2<T>Activityのテストを書く時に継承するクラス。有無を言わず読んどけ。android.test.ActivityTestCaseActivityInstrumentationTestCase2<T>が継承しているクラス。とりあえず、すこしだけ読んどいたほうがいいかも。InstrumentationTestCaseActivityInstrumentationTestCase2<T>が最も依存しているクラス。より高度な知識を得たい時には読んどいたほうがよい。android.test.InstrumentationTestRunner- テストを起動する
Activityと同等のもの。もしJUnitと同等のレポートを出力する場合には、このクラスを改造するので読んでおいたほうがよい。 android.app.InstrumentationInstrumentationTestRunnerの基底クラス。JUnitと同等のレポート出力するために読んでおきたい。
2012年1月1日日曜日
Summary of 2011 and objective of 2012
About 2011
From my impression, changes are ...
- Found new jobs at TopGate Inc.
- Attracted Google's technologies.
- Attracted Groovy programming language.
- Trying to do off-shore work in Vietnam.
About No.2
I just started to study Android technologies at a community named Saitama Android Board.
As I learned Android technologies, I found that there are limitation of Android to create an interesting application without any server or any service.
So I had become tending to learn cloud technologies like Google App Engine.
About No.1
And it was good time to learn Google App Engine because my former company gave me a time to learn Google technologies. But in my former company there is some wasting and boring procedure to search and to learn new technologies. So I decided to change my job, to get a new environment. And luckily vvakame, one of my twitter friend, told me to work together in TopGate Inc where there are some programmers familiar with Google technologies. And I changed my employment from former one to TopGate Inc.
About No.3
At the same time to change my job, I was introduced to Groovy programming language. It was kimkou_26, one of my twitter friend, that told me the most powerful and exciting language. Groovy works on Java Virtual Machine with supporting almost all of Java technologies and with light syntax. Groovy attracted me very much. And with Groovy I met some programmers on groovy community.
If you ask me to decide which gave more impact on me, Google or Groovy, it is too difficult for me to decide. Because the objective is different and I love both technologies.
About No.4
Working foreign country become common to programmers in Japan. Because Japanese Yen is becoming so strong among these days that it costs higher to employ Japanese than to employ Chinese or Vietnamese for them to do same thing. In my project it finished in failure. But I'll get success on off-shore projects this year.
Next 2012
I'd like to work as listed bellow.
- To work on Google Apps Installation
- Create TopGate's additional value
- To get success on off-shore project
About No.3
As I mentioned before, I'd like to get success in off-shore projects. From the view of the cost of project to work outside Japan cannot be ignored.
About No.1
This is my objective to work installation Google Apps to a customer. It seems Google considers Google Apps as their most successful cloud technology. And I think the tendency of companies to move from owning their server to using existing service becomes more popular. So it is business chance to become major company.
About No.2
TopGate Inc. is the re-seller of Google technologies. But I want to add additional G technology, I call it Yet Another G Technology. I mean Groovy and its co-production, like Grails, Griffon, Gradle, with these production we can offer some solutions faster than ordinal technologies. And this may cause create a value for our customer. By becoming familiar with these technologies, we -- TopGate Inc. -- will be able to establish our excellent brand image.
To reader of this blog and to another person, thank you this year!
2011年8月13日土曜日
2011年8月8日月曜日
仕様化テストの話をしようか
今日は仕様化テストのお話
先日、Androidテスト祭りで取り上げましたが、仕様化テストというテスト技法(?)があります。
簡単に仕様化テストとは何かをまとめるとこうなります。
ソフトウェアの仕様を調べ、その結果に基づいてテストを書くこと
マイケル・C・フェザーズ著、ウルシステムズ株式会社監訳、『レガシーコード改善ガイド』(翔泳社、2009年)、p.200
これは仕様のよくわからないアプリケーションないしはメソッドに対して仕様を明らかにすることができる技法なので、確実に覚えておきたい技法です。
というわけで、今回は画面サイズというなんかAndroidではちょっと嫌な部分について仕様化テストを書いてみようと思います。
まずは
Activityを作ります。これはHello, Android状態のアプリです。
MikeCountDown.java
public class MikeCountDown extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mike_countdown);
}
}
レイアウト頑張ります。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:id="@+id/view_position"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:layout_marginLeft="5dip"
android:layout_marginRight="5dip"
android:layout_weight="3"
android:orientation="vertical">
<!-- 省略 -->
<SeekBar
android:id="@+id/seekBar"
android:max="15"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="25dip"
android:paddingRight="25dip"
android:layout_marginBottom="25dip"
android:thumb="@drawable/thumb"/>
<!-- 省略 -->
</LinearLayout>
</LinearLayout>
さて、ここでお題
SeekBarの横幅の大きさを求めよ
ここで『リファクタリング―プログラムの体質改善テクニック』の一言が思い浮かぶわけです。
デバッグは最悪、テストはまだいい
微妙に間違っていると思いますが…
つまり、やつの大きさを求めるためにデバッグモードで起動するのは最悪ということです。
ではわからないものにどう対処するか?
わからないものには、テストを書きましょう。
MikeCountDownTest.java
public class MikeCountDownTest extends ActivityTestCase<MikeCountDown> {
private Activity activity;
public MikeCountDownTest() {
super("org.mikeneck.android.countdown", MikeCountDown.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
activity = getActivity();
}
public void testGetWidth() {
View view = activity.findViewById(R.id.seekBar);
assertEquals(1, view.getWidth());
}
}
テスト結果
落ちたので、大きさを取得することができました。
というわけで、これが通るようにテストを書きなおします。
MikeCountDownTest.java
public void testGetWidth() {
View view = activity.findViewById(R.id.seekBar);
assertEquals(464, view.getWidth());
}
テスト結果
はい、見事にテスト通過しました。
これでエミュレーターの仕様を知ることができました。
それと同時に、動かすことのできる仕様を手に入れることができました。
このような感じで現状の仕様にあわせてテストを書いていって、仕様書にしてしまう手法を仕様化テストといいます。
今テストがないのに、どうやってテストを入れればいいのかお悩みの皆さん、この手法でテストを挟み込んでいきましょう。
2011年8月7日日曜日
AndroidのViewのisEnabledあたりをTDDする。
お題のアプリは『作りながら覚えるAndroidプログラミング』(ソフトバンク・クリエイティブ)という本のStep.6のカウントダウンタイマーを作るというやつです。
本に記載されているコードはこんな感じです。
public class CountdownTimer extends Activity {
// 一部省略
Button startButton;
Button stopButton;
SeekBar seekBar;
public static void countdown(int counter){
// 一部省略
if(counter != 0) {
stopButton.setEnable(true);
startButton.setEnable(false);
seekBar.setEnabled(false);
} else {
stopButton.setEnable(false);
startButton.setEnable(false);
seekBar.setEnabled(true);
}
}
}
まあ、
counterが0でなけば、startButtonが触れなくなる。stopButtonが触れるようになる。seekBarが触れなくなる。
counterが0であれば、startButtonが触れなくなる。stopButtonが触れなくなる。seekBarが触れるようになる。
残年なところはこれ
Activityの子クラスなので、テストが半端なくめんどくさいのです。というわけで、こういった操作を行うAndroidPOJOなクラスがあると考えてテストしながら作ってみたいと思います。
いや、そもそもViewってテストできるんだっけ?
これは非常に不安なので、不安を解消するためにテストを書いてみました。
public class CountDownControllerTest extends AndroidTestCase {
public void testCanViewBeToggle() {
Button button = new Button(getContext());
assertTrue(button.isEnabled());
button.setEnabled(false);
assertFalse(button.isEnabled());
}
}
テスト結果
グリーンですね。
大丈夫そうです。
ではおもむろに、期待する振る舞いを書いてみましょう。
CountDownControllerTest
private CountDownControllerTest controller;
private Button startButton;
private Button stopButton;
private SeekBar seekBar;
@Override
public void setUp() thrwos Exception {
super.setUp();
controller = new CountDownController();
startButton = new Button(getContext());
stopButton = new Button(getContext());
seekBar = new SeekBar(getContext());
controller.startButton(startButton);
controller.stopButton(stopButton);
controller.seekBar(seekBar);
}
public void testSetProceeding() {
controller.setProceeding();
assertFalse(startButton.isEnabled());
assertTrue(stopButton.isEnabled());
assertFalse(seekBar.isEnabled());
}
CountDownControllerというものがあって、それが画面のViewの操作に関する責務を担うものとします。それらのViewは外(ここではActivityを想定)から与えられるものとします。そして、操作メソッドはsetProcessing()とします。最初の段階の
CountDownControllerの実装は次のとおりです。CountDownController
public class CountDownController {
public void startButton(Button startButton) {
}
public void stopButton(Button stopButton) {
}
public void seekBar(SeekBar seekBar) {
}
public void setProgressing() {
}
}
テスト結果
まあ、赤ですよ。
そりゃそうですね。何もしていませんから。
ViewというかButtonとか、SeekBarに対して仮の実装はできないので、おもむろに本実装をしてしまいます。CountDownControllerpublic class CountDownController {
private View startButton;
private View stopButton;
private View seekBar;
public void startButton(Button startButton) {
this.startButton = startButton;
}
public void stopButton(Button stopButton) {
this.stopButton = stopButton;
}
public void seekBar(SeekBar timeBar) {
this.seekBar = seekBar;
}
public void setProgressing() {
startButton.setEnabled(false);
stopButton.setEnabled(true);
seekBar.setEnabled(false);
}
}
テスト結果
おっ!グリーン!きましたね。
では、もうひとつのケースを追加しちゃいましょう。
CountDownControllerTest
public void testEndProcessing() {
controller.endProgressing();
assertFalse(startButton.isEnabled());
assertFalse(stopButton.isEnabled());
assertTrue(seekBar.isEnabled());
}
もちろん、仮実装します。コンパイルエラーをなくすための。
CountDownController
public void endProcessing() {
}
テスト結果
ハイ♪ハイ♪ハイ♪ハイ♪ハ~イ♪レッドですよ~♪
というわけで、おもむろに本実装をします。
CountDownController
public void endProcessing() {
startButton.setEnabled(false);
stopButton.setEnabled(false);
seekButton.setEnabled(true);
}
では、テスト実行。
いいね、いいね!グリーン。いいね。
じゃあ、今度は仕様に即してテスト書こうか。
counterという値に1を設定した場合はsetProcessingと同じ結果になるように、counterという値に0を設定した場合はendProcessingと同じ結果になるようにします。まずは、
counterが1の場合。CountDownControllerTest
public void testCountDown() {
controller.countDown(1);
assertFalse(startButton.isEnabled());
assertTrue(stopButton.isEnabled());
assertFalse(seekBar.isEnabled());
}
仮実装(コンパイルを通るだけ)にした上で、テストを実行
もちろん落ちるので、仮実装します。
CountDownController
public void countDown(int counter) {
setProcessing();
}
テスト結果
次に
counterが0の場合。CountDownControllerTest
public void testCountDownOnEnd() {
controller.countDown(0);
assertFalse(startButton.isEnabled());
assertFalse(stopButton.isEnabled());
assertTrue(seekBar.isEnabled());
}
テスト結果
落ちるので、本格的かつシンプルに実装する。
CountDownController
public void countDown(int counter) {
if(counter == 0) {
endProcessing();
} else {
setProcessing();
}
}
そして、テスト
グリーン。
intで0とか1というきわどい値をやっているので、-1をやらないわけがない。CountDownControllerTest
public void testCountDownOnException() {
try{
controller.countDown(-1);
fail();
} catch(IllegalArgumentException e) {
assertTrue(true);
}
}
そしてテスト
すぐさま期待する動作になるように実装。
CountDownController
public void countDown(int counter) throws IllegalArgumentException {
if(counter == 0) {
endProcessing();
} else if(counter > 0) {
setProcessing();
} else {
throw new IllegalArgumentException();
}
}
そして、すぐテスト
グリーン!( ゚Д゚ノノ☆パチパチパチパチ
このあと、ユーザーコード(つまり開発者が使うコード)のためにインターフェースを作成したいのだけど、それはみなさん考えてみてください。
こういう感じでAndroidでもTDDができるので、しかもAndroidPOJOでもTDDができるので、ぜひとも取り入れてみてはいかがでしょうか?
Androidテスト祭りでTDD晒してきたよ
Androidテスト祭りが開催されました。
ご来場いただいた皆様、USTをご覧になった皆様、および講演を引き受けてくださった皆様、大変ありがとうございました。
この場を借りて御礼申し上げます。
また、忙しい中、準備に奔走したスタッフの皆様お疲れさまでした。
当日の模様ですが、Togetterなどで少しまとめられております。
@sakura_bird1 のAndoroid テスト祭り実況
あと、手前味噌ですがこちらでもまとめています。
さて、恥ずかしながら、私も拙いスキルを晒して参りました。
プチライブコーディングとかはABC2011Summerにて晒していたし、Android埼玉支部の支部会でも晒してきたので、それほど緊張はしていないつもりでしたが、やはり70人、UST150人を前にしてのTDDはプレッシャーがありますね。全然スピードが悪かったと思います。
まあ、お題はいつものアレです。
そう、ボーリングですね。
比較的面倒なロジックな割にはルールは結構知られているという面白い題材です。
まあ、実際にはTDD以外にも仕様化テストの話もして参りました。
仕様化テストの話は明日にでも書きます。
資料は次のとおりです。
きっと、いろいろな方もブログを書かれると思いますので、私なりに簡単にまとめました。
まあ、こんな感じですが、こんごともAndroidテスト部よろしくお願いします。
2011年7月18日月曜日
Android Bazaar Conference 2011 Summer に行ってきました。
私自身は一般参加として登録しましたが、Androidテスト部の展示のお手伝いをしてきました。
当初、monkeyrunnerでテスト部で作成したTwitterクライアントを実機上で走らせるデモを行う予定だったのですが、
ネタはいつものアレです。
お題:ボウリングのスコアを算出するプログラムを作成せよ。
- 条件
- テストコードとソースコードを作成する。
- 入力は配列もしくは
java.util.List<Integer>の形で与えられる。 - ストライクの後には0が設定されるものとする。
- ガーター、ミスはともに0が設定されるものとする。
というやつです。
これを声を大きくしながら実演すること6~7回。
さすがに疲れましたが、
- TDDというのを聞いたことがあったが、実際にこうやるものだと思わなかった。
- 非常に面白かった。
- 今度会社でやってみようと思う。
実はこれ、Androidというプラットフォームに関係の無いネタだったのです。
しかし、ソフトウェアの特性上、何らかの判断などを行うので、表示機能やセンサー周りでないモデル・ロジックの部分での開発にTDDを組み込んでいくというのは避けて通れません。
その点でTDDにより常にフィードバックを受けて、自信を持って開発していけると、最終的に(保守的にも新規機能追加に関しても)品質の高いソフトウェアを開発できると思います。
個人としてはAndroid Developers' Club (デ部)のゆかいな仲間たちによる「Android3.0/3.13.2 HoneycombとAPI解説」を聞いてきました。
最近人気のあるArduinoなどのサポートが強化されておりますし、各種コントローラーをつなげることもできるようになっているあたりは非常に面白いと思います。
また、3.0以降に追加された
Fragmentについても簡単ですが解説されていて、実際触ってみたいと思いました。…エミュレーターが重くなければなw
2011年7月16日土曜日
今すぐフォローしておきたいAndroid テストクラスタ
- @miyatay
Android テスト部の部長です。
エンタープライズでAndroidアプリを開発していて、JenkinsとかMavenとかも活用しているらしいです。テストを書かないでコミットすると以下略。
- @ussy00
「ぬ」社所属のクールなプログラマ
淡々とチケットを登録して、タスクを消化していく様がとてもカッコイイ。コミットにチケットが関連付けられていないと気持ち悪いらしい。
- @nowsprinting
Android テスト部 ソースカツ課の頼れるリーダー
Androidテスト部公認(?!)アプリ(リリースされていませんがw)testterの最もコミット数の多いリーダーです。testterのコンポーネントに関して胸ぐらつかみ合いの大げんかをして、「テストってなんだろう?」と海を見つめながら語った記憶が…(嘘)
- @sassy_watson
ダッフィー好きなプログラマー
Android用くるくる回るブラウザを作ったり、Java初心者プログラマーにJUnitテストを丁寧に教えるイケメンプログラマー。イケメンと煽ると反応が(ry
- @oota_ken
テストについて語らせると、とどまることを知らないテストプロフェッショナル
TDDに関しての研究や実績があるAndroidテスト部最強のソルジャー。実はAndroidよりWP7の方が(お察し下さい)。
- @myb1126
Android 横浜PF部の人で、相当なギーク
Javaは知らない(自称)らしいですが、Jythonでmonkeyrunnerを走らせるのが得意。効率悪い仕事をするともれなく突っ込まれる。ほむほむと呼ぶとアイコンが変わるというもっぱらの噂。
- @bols_blue
大学院でgccポーティングオレオレコンパイラーを作った猛者
JUnit~monkeyrunnerまで幅広くカバーするAndroidテスト部のきのこ。オレと住んでいるところが近いため、センドロにてただいま休戦協定中。著書多数。
- @dicea
所謂上流テストツールを作っている会社の結構偉い人
Android、DS、iPhone、などの操作をシミュレートするツールを製作している会社の偉い人です。ガンダムマニアなので、ガンダムのネタを振ると食いついてきます。
- @kandayasu
chromeに詳しい頼りになるプログラマー
Chromeの操作に迷ったら何でも教えてくれる、頼りになるプログラマー。Eclipseでのテスト実行の遅さを嘆き、Antでテストしていく強者。
オレあまりAndroidクラスタのコアな人達知らんので、これくらいしか挙げられんっす。情弱者ですいません。
番外編
Androidというよりはテストの人たち。- @snsk
Androidテスト部の副部長
マックとお酒とデシジョンテーブルを愛するテストマネージメントプロフェッショナルな勉強家。行動力もあって、流石副部長というお方です。
- @goyoki
組み込みTDD王子
TDDの研究・普及に活躍するテスト会のイケメン王子。@snsk副部長のTDDの師匠であり、@oota_kenのよきパートナーである。テストでわからないことがあれば、迷わず質問すべきである。
オレ?
しがないエロオヤジですよ。
2011年6月27日月曜日
ちょっとやりたいアプリメモ
- 恋ゲー的情報処理試験アプリ
- Chrome Extension : ページ内の一部の文章を選択したら、その部分と、ページのタイトルと、URLをTwitterに投稿する
- 英語、フランス語、スペイン語から日本語への翻訳英会話Androidアプリ - 大学で三ヶ国語(英語、スペイン語、ラテン語)をやっていたオレにしかできないアプリ
今んとこはこれくらいかな
しかし、昨日、英語での音声出力ができなかったのがショックで、3.は頓挫中。
だれかオレにアドバイスを~(涙)
2011年6月21日火曜日
Androidでも流れるインターフェース
まずはアプリの仕様から。
非常に単純な作りですね。
ちなみにこれは次の記事でテストを書くのでよく覚えておいてね。
で、百聞は一見にしかずで、これまた画面の動きをハードコピーしました。
この画面で「push me!」を押すと、
この画面になります。
多分、Androidをやっている人たちなら、この画面簡単に作れちゃうでしょう。
いや実際オレも30分くらいで完成しました。
では、これからオレがdisるコード例を示します。
class MainActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
View view = findViewById(R.id.button);
view.setOnClickListener(
new OnclickListener(){
@Override
public void onClick(View v){
Intent intent = new Intent(this,
NextActivity.class);
startActivity(intent);
}
}
);
}
}
読みづらいですね。
まだ、
OnClickListenerがひとつだからいいものの、複数のViewにボタンを設定するとなったら、発狂しそうですね。というわけで、オレが実装したのは次のようなコードです。
class MainActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Create.activity(NextActivity.class)
.from(this)
.listenOn(R.id.button);
}
}
インターフェースを単純につくっています。
まずはインターフェースから。
interface MoveActivity extends OnClickListener{
public MoveActivity from(Activity preActivity);
public MoveActivity to(Class<? extends Activity> nextActivity);
public MoveActivity listenOn(int view);
}
インターフェースはこれだけです。
前のエントリとほとんど変わりません。変わったのは
listenOn(int view)を付け加えたあたりですね。したがって、これを実装したクラスのテスト性もほとんど変わりません。
これの実装が頑張りどころです。
class Create implements MoveActivity {
private Activity preActivity;
private Class<? extends Activity> nextActivity;
public static MoveActivity activity(
Class<? extends Activity> newActivity){
return new Create()
.to(newActivity);
}
@Override
public MoveActivity from(Activity preActivity){
this.preActivity = preActivity;
return this;
}
@Override
public MoveActivity to(Class<? extends Activity> nextActivity){
this.nextActivity = nextActivity;
return this;
}
@Override
public MoveActivity listenOn(int view){
preActivity.findViewById(view).setOnClickListener(this);
return this;
}
@Override
public void onClick(View view){
Intent intent = new Intent(preActivity, nextActivity);
preActivity.startActivity(intent);
}
}
ここのポイントは普通なら
getInstance(Class<? extends Activity>)とやるメソッド名を、activity(Class<? extends Activity>)としている所です。Javaに慣れている方には若干読みづらく感じますが、あまりJavaに詳しくない人でもこれならActivityを組み立てることできますね。あともうひとつのポイントは返す型はかならずインターフェースにすること。
これはDevLove Hangar Flight で矢野さんがおっしゃっていた、Javaの型とはクラスではなく、インターフェースのことだというインターフェース原理主義に即しています。これなら、Androidのバージョンが変わってAPIが変更されてもActivity部分は変えなくて済みますからね。
2011年6月20日月曜日
日本Androidの会 6月定例会に行ってきたよ
表題の件について、駒場に行ってきました。
19時頃の駒場東大前駅の西口はいいですね。
JKが(以下自粛)
では、レポートを…
オレ的にはChromeOSの話が聞ければ満足だったので、まあ、参加してよかったと思います。
2011年6月19日日曜日
Android埼玉でJUnitのセッションをしてきました。
まず、その前に、Android埼玉、オレ遅刻してきましたorz
ひどいですね。発表者なのに…
進藤さんご迷惑をおかけいたしました。
埼玉Androidの会の方は藤原さん(@zegenvs)さんが主催するモクモク会をメインに参加していて、定例会の方は実は二回目でした。
というのも、埼玉Androidの定例会はたいていテスト部MTGとかぶることが多くて、テスト部メインなオレは泣く泣く埼玉Androidに参加できなかったのです。(半分ウソつきました。)
以下、今回セッションの時間をいただけた経緯です。
- 社内用にJUnitの資料をGoogle Docsで作成
- 特に秘匿にする必要性を感じなかったので、Twitterでアドレス付きでつぶやいた
- 埼玉支部長の進藤さんから埼玉Androidの会でぜひと…
30分時間をいただけるなんて光栄です。
ありがとうございます。
あと、資料の誤りを指摘してくださった、千葉Androidの会(#chibandroid)のnouzui2007さんありがとうございます。
で、他の方の発表ですが、一人未完の資料をどう持っていくか作戦を練っていて
モクモクしていやがったので、左耳から入って右耳から出て行っててあまり覚えていません。
あいかわらず失礼なやつだ、オレ。
一応、記憶に残っているプレゼンで以下の二つがあります。
そいで、私のプレゼンですが、
いつものTwitter口調でずっとやっていました。
そして、人生初のライブコーディングしましたが、あれは、練習が必要ですね。
内容はすでに頭の中にあるんですけど、手が追いつかないのと、クビが痛いのと、焦るのとで、、、いい経験させてもらいました。
資料はこんな感じです。
オレとしては珍しく、懇親会に参加しました。
土曜日サイコー!
主だった話は埼玉Androidの会でロゴマークを作るということで、
おざわ(@osa030)さんのロゴの話題になりました。
おざわさん曰く、
タマといえば「○んたま」でしょ!
かなり大爆笑でした。
愉快な方達がいる埼玉Androidの会、とても楽しかったです。
埼玉Androidの皆様、今後とも宜しくです。
2011年6月13日月曜日
簡単なActivityInstrumentationTestCase2の書き方
しかし、テストとなるとあまり情報が多くありません。
というわけで、
簡単な
ActivityInstrumentationTestCase2<T extends Activity>の書き方を記事にしました。
今回のテスト対象のアプリケーションはこんな感じです。
まあ、百聞は一見にしかずなので、画面イメージ出しましょう。
これが最初の画面です。
そして、これに文字を入れて、ボタンを押すとこうなります。
では、早速テストを書きましょう。
まずは、コンストラクターと
setUp()から。
class HelloAndroidTest
extends ActivityInstrumentationTestCase2<HelloActivity>{
private Activity activity;
private Instrumentation instrumentation;
public HelloAndroidTest(){
super(HelloActivity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
activity = getActivity();
instrumentaion = getInstrumentation();
setActivityInitialTouchMode(false);
}
}
まず、ここのポイントは
- コンストラクター
setUp()中にあるActivityInstrumentationTestCase2#getActivity()- 同様に
setUp()中にあるActivityInstrumentationTestCase2#getInstrumentation()
コンストラクターではテスト対象のアクティビティ・クラスを
ActivityInstrumentationTestCase2に渡してやります。「総称型(ジェネリクス)でクラスを指定しているんだから、なんでわざわざクラスを通知してやる必要があるの?」という声も聞こえそうですね。すこしだけ解説すると、タイプパラメーター<T>というのは、ただただタイプとして機能するだけで、java.lang.Class<?>オブジェクトのような機能や、役割を一切持ちません。したがって、
Class clz = T.class;
とか、
boolean isInstance = object instanceof T;
とか、
T object = T.newInstance();
などはすべてコンパイルエラーになります。
これよりも詳しい説明は私が説明するよりも他にもっと優れた解説があります。Java総称型メモなどを参考にしてください。
話を元に戻すと、
ActivityInstrumentationTestCase2で、テスト対象のアクティビティを起動するために、そのクラスの情報が必要なわけですね。さて、これジェネリクスで指定したクラスと異なるクラスを渡したらどうなるのか?わかりませんねぇ。あとでやってみましょう。まぁ、予想できる結果として、この後の解説で述べる#getActivity()でjava.lang.ClassCastExceptionが発生するように思います。setUp()中にあるActivityInstrumentationTestCase2#getActivity()はActivityを起動して、参照を返すメソッドです。これにより、テストコードはテスト対象のアクティビティをそのコンテクスト上で起動することが可能になります。テストはそれ自体でひとつのアプリケーションですが、テストアプリケーションとは別のスレッド上にテスト対象のアプリケーションが起動するのです。これをsetUp()に置くことで、毎回のテストがアクティビティを起動した状態で実行できます。最後に、
setUp()中にあるActivityInstrumentationTestCase2#getInstrumentation()です。このInstrumentationは、アクティビティの起動とは何の関係もありませんが、アクティビティのUIスレッドと同期をとるような場合や、UIに対して操作を行う場合に必要になるオブジェクトです。今回のテストアプリケーションでも使いますので、最初にその参照を取得しておきます。では、テストコードをどうぞ。
public void testPressButton() {
// EditText にフォーカスを当てる ---- (1)
EditText editText = (EditText)activity.findViewById(
orz.mikeneck.hello.R.id.edit_text);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
editText.requestFocus();
}
});
// UIとの同期をはかる ---- (2)
instrumentation.waitForIdleSync();
// UIにキーを送る ---- (3)
sendKeys(KEYCODE_SHIFT_LEFT);
sendKeys(KEYCODE_A);
sendKeys(KEYCODE_N);
sendKeys(KEYCODE_D);
sendKeys(KEYCODE_R);
sendKeys(KEYCODE_O);
sendKeys(KEYCODE_I);
sendKeys(KEYCODE_D);
// 前提条件の確認 ---- (4)
assertEquals("Android", editText.getText().toString());
// Button をクリックする ---- (5)
Button button = (Button)activity.findViewById(
orz.mikeneck.hello.R.id.button);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
button.performClick();
}
});
// UIとの同期をはかる ---- (6)
mInstrumentaion.waitForIdleSync();
// TextView の値をテストする ---- (7)
TextView textView = (TextView)findViewById(
orz.mikeneck.hello.R.id.edit_text);
assertEquals("Hello,Android", textView.getText());
}
大雑把な解説ですが、
EditTextにフォーカスを当てます。これはUI上の操作に当たるので、Activity#runOnUiThread(java.lang.Runnable)を使用します。- UIとの同期をはかります。別スレッド(アクティビティ)との同期は
InstrumentationwaitForIdleSync()を用います。 - UIにキーを送ります。
android.view.KeyEventをstatic importしておいてください。 EditTextにキーを送ったので、それが反映されていることをテストします。想定される値は「Android」です。Buttonをクリックします。これはUI上の操作に当たるので、Activity#runOnUiThread(java.lang.Runnable)を使用します。- UIとの同期をはかります。別スレッド(アクティビティ)との同期は
InstrumentationwaitForIdleSync()を用います。 Buttonをクリックしたので、仕様通りにTextViewのテキストが変更されているかテストします。想定される値は「Hello,Android」です。
ポイントは
- UI上の操作は必ず
Activity#runOnUiThread(java.lang.Runnable)を使用する。 - UI操作後は
InstrumentationwaitForIdleSync()を用いる。
#runOnUiThread(java.lang.Runnable)を使わないとUIが操作できないのは、他のアプリケーションからいくらでも値を変更するなどの改変が行われてしまうからというセキュリティ的な側面があるのかと思われます。(Javaそんなに詳しいわけではないので、そのあたりを突っ込んでくれる人、大募集)二番目の
InstrumentationwaitForIdleSync()がActivityInstrumentationTestCase2の真骨頂です。UIスレッドとの同期をはかりスムーズにテストを実行出来るようなオブジェクト類が提供されており、それがActivityInstrumentationTestCase2です。別スレッドのUIと同期するこの能力はテストの実行においてかなり強力な機能です。こんな感じで、非常に簡単なテストでしたが、みなさまもUI上で不具合を発見したら、なるべくテストコードを書いて、それらを再現できるようにしてみてください。きっと、素晴らしいUIをもつアプリケーションが作成できると思います。
2011年6月9日木曜日
会社でJUnit勉強会をやってみたよ
会社でJUnit勉強会なるものを開いてみました。
JUnit勉強会と言っても、なんか高度なことをやる意図は持ってなく、単純にJUnitの使い方、実際のプログラムへの適用、TDDのやり方の紹介みたいな感じでやってみました。
資料はここを参照。
で、結果ですが、
かなり失敗だった
と思います。
原因
- JUnitの動作を図解したほうが分かりやすかったと思われる。
- 最初のテストをHello Worldにしたのがまずった。
- さらにボウリングスコアの計算というTDD定番のネタで研修したあたりがまずった。
1.については対象者がJavaを始めて1ヶ月というAさんと、入社3年目でJava経験2年のBさんであり、まだ、Javaのmainメソッドからの起動を追いかけるというレベルでのsetUpなどの動きを伝えるには、やや高度であったあたりが、厳しかったという感じです。
2.についても同様で、Aさんはもちろんですが、レガシープロジェクトで育ったBさんにも、インターフェースを使ったプログラムの記述を説明するのはなかなか厳しかった。(もちろんこれは覚えてもらわないといけないと思っていますが…)
3.についてはTDDの考え方、というか動き方、これを説明しておかなければならなかったというのがあります。
とはいっても、みなさんテストコードの重要性は理解していただいているので、(毎日オレがうるさくテストコード、テストコードと言っているうちに、テストコードマンセイになった)徐々に実力を向上させていきたいと思う次第であります。
それと同時にIntefaceを介在させることの説明について、
まだまだ自分がうまく説明出来ていないというのを
発見できたので、今後自分ももっと噛み砕いて説明できるようになりたいと思う次第であります。
というわけで、明日はより高度なAndroidのActivityInstrumentationTestCase2<? extends Activity>の
勉強会をやりたいと思います。
つ~か、ActivityInstrumentationTestCase2<? extends Activity>はジェネリクスを使っているあたりで、説明が頓挫しそうな予感がする。
さらに、JUnit勉強会資料を公開した瞬間に@ryu_compin(日本Androidの会埼玉支部、支部長)さんから、埼玉Androidの会でセッションやってというオファーが来ました。30~40分枠をいただけるということで、なんとも光栄の至りでございます。
ん~、でも皆さん、あくまで単なるJUnitでございますよ…
2011年5月27日金曜日
Androidテスト部の宣伝してきました。
たまたま日本Androidの会にてLT参加者募集ということでしたし、
実は『Androidテスト祭り』実行委員にも関わらず何のタスクもなかった
(できるものが何もなかった)ので、
宣伝がてら参加することにしました。
いや、こういう書き方すると、
イヤイヤ参加しているように読めますが、
実際は目立ちたがり屋なので、
喜んで参加している次第です。
全体的な印象ですが、やはり管理者層の方が多く、
ワイヤレスジャパンでAndroidの情報収集をしている人は
これからAndroid開発でのビジネスを考えている方のようでした。
そんなわけで町田支部の@ngsw_taroさんのAndroidアプリ開発LTは
熱心にメモを取られている方が多かったように思います。
で、私のLTですが、
タイトルは『Androidテスト祭り~僕とテストしてRIA充になろうよ』。
で、これがその資料です。
結果的にはすべりまくりでした。
- 「ねえ、僕と契約して魔法少女になってよ」というネタはプログラマーには受けるネタですが、管理者層の人にはさっぱり伝わらないこと
- ワイヤレスジャパンでAndroidの情報を収集している人は既にリア充(いや、そういうわけでもないだろうけど)なので「RIA充」と言っても意味が通じないこと
- 「王子で王子が語る」というベタなネタは意外と受けが良いこと
こんな感じで、あとで司会の関西の方に突っ込まれまくりました…
ただ、まあありがたい事に私のイベントの周知もメモをとってくれている方を
ちらほら見かけましたので、
宣伝としては成功したのかもしれないような気がします。
2011年4月3日日曜日
AndroidのOnClickListenerをテストしよう
本屋でもJavaのコーナーよりも場所が広かったりします。
で、それらに掲載されているコードに文句をつけるつもりは毛頭ございませんが、
書いてあるのをそのまま参考にすると、テストで痛い目を見ます。
では、まずダメなアプリの例から。
package orz.mikeneck;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class FirstActivity extends Activity {
/**
* @see android.app.Activity#onCreate(android.os.Bundle)
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
// Setting layout to a display.
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Adding onClickListener to a button.
Button button = (Button) findViewById(R.id.button_id);
button.setOnClickListener(
new View.onClickListener() {
public void onClick(View v){
// If this button is pressed, new activity will be launched.
Intent intent = new Intent(this, NextActivity.class);
startActivity(intent);
}
}
);
}
}
ダメであるという理由は、次のテストが実施できないからです。
・ボタンを押したときに、指定したActivityが起動すること
なぜテストが実施できないかというと、
オブジェクト
ButtonというよりはViewには、setOnClickListener(android.view.View.OnClickListener)はあっても、getOnClickListener()がないため、ボタン(View)を押すという操作をテストから実行することができないからです。
一般的な開発工程のあり方としては、単体テストでバグを摘出するほうが、結合試験以降でバグを抽出することよりもコストが低いと言われています。
結合テストまでこのバグが潜んでしまっていた場合、どのクラスによって発生したのか切り分ける必要があるため、念入りに調査することが必要になります。
プログラマーで勘の良い人はだいたいどの辺にバグがあるのかを検出するのが速いわけですが、誰しもがそうではないわけで、こんな単純なバグが原因だったのかと呆れてしまうこともあったりします。
私もSIに務めているので、そのようなことはよくあったりします。
なんか画面のボタンを押したときに、条件を限定しているわけではないのに、なにか限定された結果が得られて、どのような条件でどのような操作をするとバグが発生するのか、詳細にバグ票に書かなければならないので、結構しんどかったりします。
で、その結果、実はボタンを押したあとの挙動にハードコーディングがしてあったとかよくありました。
…最近は上流のテストはやっていないので、そんなことはなくなったのですが…
話を戻すと、
View.onClickListenerはせっかくinterfaceなのだから、それを有効活用しない手はありません。というわけで、以下がまだましな例。
FirstActivity.java
package orz.mikeneck;
import android.app.Activity;
import android.os.Bundle;
import android.view.View.OnClickListener;
import android.widget.Button;
public class FirstActivity extends Activity {
/**
* @see android.app.Activity#onCreate(android.os.Bundle)
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
// Setting layout to a display.
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Getting instance of OnClickListener.
View.onClickListener listen = new OnClickListenerImpl();
listener.setPreContext(this);
listener.setNextContext(NextActivity.class);
// Adding onClickListener to a button.
Button button = (Button) findViewById(R.id.button_id);
button.setOnClickListener(listener);
}
}
ここで、先程の例ではActivity内にあった
FirstActivityの中にあった、View.onClickListenerの実装が外に出されました。で、これが外に抽出されたクラス
OnClickListenerImpl.javaです。
package orz.mikeneck;
import android.content.Context;
import android.content.Intent;
import android.view.View;
import android.view.View.OnClickListener;
public class OnClickListenerImpl implements OnClickListener {
/**
* Previous Context listening user motion.
*/
private Context preContext;
/**
* Next Context to be launched.
*/
private Class nextContext;
/**
* If the button is pressed, the next Activity will be launched by this method.
* And, this method can be called by external classes.
*/
@Override
public void onClick(View v) {
Intent intent = new Intent(preContext, nextContext);
preContext.startActivity(intent);
}
public void setPreviousContext(Context preContext) {
this.preContext = preContext;
}
public void setNextContext(Class nextContext) {
this.nextContext = nextContext;
}
}
そうすると、これである特定の画面から、次の画面を呼び出すという挙動のテストができるようになります。
以下はテストコード。
OnClickListenerImplTest.java
package orz.mikeneck.test;
import android.content.Intent;
import android.test.mock.MockContext;
import junit.framework.TestCase;
import orz.mikeneck.OnClickListenerImpl;
public class OnClickListenerImplTest extends TestCase {
private OnClickListenerImpl listener;
private boolean isTestOnClickPassed;
@Override
public void setUp(){
this.isTestOnClickPassed = false;
listener = new OnClickListenerImpl();
PreContext preContext = new PreContext();
listener.setPreviousContext(preContext);
listener.setNextContext(NextContext.class);
}
/**
* Click the button(fake).
*/
public void testOnClick(){
listener.onClick(null);
assertTrue(isTestOnClickPassed);
}
private class PreContext extends MockContext{
/**
* @see android.test.mock.MockContext#startActivity(android.content.Intent)
*/
@Override
public void startActivity(Intent intent) {
super.startActivity(intent);
isTestOnClickPassed = true;
}
}
private class NextContext extends MockContext{
}
}
実は
View.onClickListenerのメソッドonClick(View v)は、パブリックメソッドなので、クラスの外から呼び出すことが可能です。
したがって、テストクラスでは堂々と
listener.onClick(null)と呼び出していますね。ただし、ここからは私のうっかりミスが発覚します。
これをそのままテスト実行すると、もれなく
java.lang.UnsupportedOperationException
at android.test.mock.MockContext.getPackageName(MockContext.java:101)
at android.content.ComponentName.(ComponentName.java:75)
at android.content.Intent.(Intent.java:2551)
at orz.mikeneck.OnClickListenerImpl.onClick(OnClickListenerImpl.java:52)
at orz.mikeneck.test.OnClickListenerImplTest.setUp(OnClickListenerImplTest.java:28)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:169)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:154)
at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:430)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1447)
とかなります。
う~ん、前々から気にはしていたんですがね、
android.test.mock.MockContextは、どうも暴れん坊大将軍様で、呼び出すメソッドすべてが
UnSupportedExceptionを返してくれるという素敵仕様になっております。
したがって、テストを通すまでかなりの道のりを経なくてはならない化物なのでございます。
で、今回は
Exceptionの発生しているメソッドのオーバーライドもしました。
private class PreContext extends MockContext{
/**
* @see android.test.mock.MockContext#startActivity(android.content.Intent)
*/
@Override
public void startActivity(Intent intent) {
isTestOnClickPassed = true;
}
/**
* Added method.
* @see android.test.mock.MockContext#getPackageName()
*/
@Override
public String getPackageName() {
return "orz.mikeneck.test";
}
}
これで、見事にテストが通りました。
というわけで、ある特定のActivityから指定したActivityを起動することが出来ているという確認が取れるようになりました。
はい、妙に長ったらしい解説でしたが、
Activity内部でinterfaceの実装を記述しないこと
が今回言いたかったことです。
書いていたら、22時過ぎてしまった…orz
Androidアプリ用のイメージ
アプリを作っていて、凝ったアプリではないのでアイコンもそれほど凝らなくてもいいようなアプリを作りたいときがあります。
そういうときはネット上の素材に頼るのがよいようです。
というわけで、簡単にアプリ用のイメージをまとめてみました。
Open Source Icons -- Linux系のアイコンがいっぱいある。これはかなり使いやすい。LGPLだったり、GPLだったりするので、ライセンス関連を気にする人は要注意。
Button Maker -- 簡単な文字の入ったアイコンをつくるのに適したサイト。自分でキーボードを作るときにも便利ですね。
PhotoShifter -- Windows用ですが、gif形式のファイルをpngに変換したり、リサイズしたりできます。コマンドラインでも利用可能だそうです。
ちなみに、
res/drawable-hdpiのアイコンのサイズは72x72res/drawable-hdpiのアイコンのサイズは36x36res/drawable-hdpiのアイコンのサイズは48x48です。
















