2011年8月7日日曜日

AndroidのViewのisEnabledあたりをTDDする。

Androidを実際に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);
        }
    }
}


まあ、counter0でなけば、
  • startButtonが触れなくなる。
  • stopButtonが触れるようになる。
  • seekBarが触れなくなる。
ですし、counter0であれば、
  • 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に対して仮の実装はできないので、おもむろに本実装をしてしまいます。

CountDownController
public 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と同じ結果になるようにします。

まずは、counter1の場合。

CountDownControllerTest

public void testCountDown() {
        controller.countDown(1);
        assertFalse(startButton.isEnabled());
        assertTrue(stopButton.isEnabled());
        assertFalse(seekBar.isEnabled());
    }


仮実装(コンパイルを通るだけ)にした上で、テストを実行



もちろん落ちるので、仮実装します。
CountDownController

public void countDown(int counter) {
        setProcessing();
    }


テスト結果


次にcounter0の場合。

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();
        }
    }


そして、テスト

グリーン。

int0とか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ができるので、ぜひとも取り入れてみてはいかがでしょうか?

0 件のコメント:

コメントを投稿