2011年1月25日火曜日

GUI コードのUnitTest の例

こんな感じのしょうもないフォームがあるとする。 DataGridView があり、A列と B列は数値を入力できる列で、C列は A - B の計算結果を表示する。 さて、このフォームには、C列の値が負のとき赤で表示するという振る舞いがあるが、果たしてこれをテスト駆動で書けるだろうか? TDD に慣れた人なら、普通にテストファーストで書くだろうけど、ハナからできないと決め付けてる人も意外と多い。 そんな人が考えるテストはこんな感じ。
  • アプリケーションを起動する。
  • A列、B列に値を入力する。
  • C列の前景色が変わったことを確認する。
まあ普通の手動の動作確認と同じだけど、これを自動化テストコードとしては書けないから UnitTest は無理と言う。 実際にはそういったテストコードも書けないことはないが、TDD の UnitTest ではそんなのは書く必要ないし、第一、テストの観点も目的も手法も違ってるから、むしろ書いてはいけない。 前に書いた HumbleView の作法でやると、例えばこんな風になる。
[TestFixture]
public class Form2PresenterTest
{
    public Form2Presenter presenter;
    public Mock<IForm2> mock;

    [SetUp]
    public void SetUp()
    {
        this.mock = new Mock<IForm2>();
        var view = mock.Object;
        this.presenter = new Form2Presenter(view);
    }
    [Test]
    public void SelectStyle_Plus()
    {
        TestSelectStyle(10, 5, CellStyle.NonNegative);
    }
    [Test]
    public void SelectStyle_Zero()
    {
        TestSelectStyle(10, 10, CellStyle.NonNegative);
    }
    [Test]
    public void SelectStyle_Minus()
    {
        TestSelectStyle(10, 20, CellStyle.Negative);
    }
    public void TestSelectStyle(int a, int b, CellStyle style)
    {
        //arrange
        var rowData = this.presenter.RowDataAt(0);
        rowData.A = a;
        rowData.B = b;
        //act
        this.presenter.Update(0);
        //assert
        mock.Verify(v => v.SetGridStyle(2, 0, style));
    }
}
見ての通り、Update()メソッドで行のインデクス(簡単のため0固定とした)を受け取った Presenter が、更新された A 列とB列の値に基づいてスタイルを選んで、セルの位置とともにビューに返すという流れになる。 これを満たす IForm2 と Presenter のコードはこんな感じ
public interface IForm2
{
    //・・・略・・・
    void GridDataSource(List list);
    void SetGridStyle(int col, int row, CellStyle styleId);
}

public class Form2Presenter
{
    //・・・略・・・
    public void Update(int rowIndex)
    {
        var rowData = this.list[rowIndex];
        var cellStyle = rowData.C < 0 ? 
                CellStyle.Negative : CellStyle.NonNegative;

        this.view.SetGridStyle(2, rowIndex, cellStyle);
    }
}
やり方はいくつかあるが、ここでは色を System.Drawing.Color で直接指定する代わりに、列挙値 CellStyle を定めて使っている。この CellStyle 定数と実際にセルに設定する DataGridViewCellStyle オブジェクトのマッピングを保持しておくクラスも別途必要だが、自明なので省略。 この辺りまで書くとテストも通る。 後は、View と Presenter を関連付けるだけ。
public partial class Form2 : Form, IForm2
{
    //…略…
    private void dataGridView2_CellEndEdit(
        object sender, DataGridViewCellEventArgs e)
    {
        this.presenter.Update(e.RowIndex);
    }
    public void SetGridStyle(int col, int row, CellStyle styleId)
    {
        this.dataGridView2[col, row].Style = CellStyleMap.StyleOf(styleId);
    }
}
前回と同様、View オブジェクト(Form2)は受身で丸投げなクラスで、余りテストの必要性が高くないほんの少量のコードを残して、その他の振る舞いのコードを Presenter に移した。 こんな風に、一見いかにも GUI の最表層みたいな振る舞いでも、フォーム=View を薄く薄くしていく事で、妥当な網羅性を備えた UnitTest が書けるようになる。

0 件のコメント:

コメントを投稿