Home日記コラム書評HintsLinks自己紹介
 

フィンローダのあっぱれご意見番 第160回「テストしてみないと安心できない」

← 前のをみる | 「フィンローダのあっぱれご意見番」一覧 | 次のをみる →

ソフトウェアの開発プロジェクトに参加する場合に、 メンバーに必要なスキルは、 大きく分けて2つである。 プログラミングに関する能力と、コミュニケーションに関する能力だ。

 

※ スキルと技術は違うということをこれを書いた時にはよく理解していなかったが、 今思えば、理解してもあまり影響なかったような気がする。

プログラミングに関する能力については、今更説明する必要はないかもしれないが、 どの程度のスキルが必要になるのか。 とある本には、ある言語に対して平均的な技能を持つようになるまでの期間は、 3~6か月程度、と書いてあった。 これはつまり、半年もあれば、知らない言語に対して何かしらプログラムが書ける程度にはなるだろう、という話だろう。 これに対して、一人前のプログラマーになるには、どの程度かかるのか、 という話は「ソフトウェア職人気質」という本によれば、 少なくとも15年以上かかるという数値が提示されている。 私の感触としては、10年程度かなと思っていたので、 かなり差があるのだが、 そのあたりは法則でかっちり出てくる値ではないし、 いずれにしても、少なくとも10年未満ということはなさそうだ。

6か月と10年というのは、ずいぶんな差がある。 その差は何なのかというと、 プログラミングに対する技が一定のレベルに到達した、ということだ。 RPG と比較すると分かりやすい。 ある程度の戦い方とか、魔法の出し方とか、そのようなプレイ方法に慣れるには、1週間もあればokだろう。 しかし、パーティを育て上げて、世界のどこに行っても戦って勝てるという所に到達するには、その10倍以上の時間がかかるのだ。 まあそういう話だ。

  

もう一つ、私が好きなたとえ話がある。 木を切ったり、釘を打つ、というような作業は、一度もやったことがなくても、 やり方を教わって1週間も訓練すれば、できるようになるだろう。 では、そういう人を100人集めて家を建てさせたら、一体何ができるか?

 

※ あるいは、寿司を握るというのでもいい。 「握ればいい」というのであれば、素人でも簡単にできる。 では、客に出せるレベルのものを握るには、何年かかるだろうか。

§

SEの失敗談系の本が何冊も出ているが、 多くの本に共通する話がある。 メールや掲示板のコミュニケーションで大失敗したというネタだ。 この種のコミュニケーションを使うと、 掲示板でバトルが始まってしまって大変だという問題点の指摘である。 確かに掲示板でバトルが始まったら大変だ。 フォーラムのような大規模集団を十数年も管理していれば、 どう大変なのか体で覚えてしまう位タイヘンなのだ。 もっとも、こんなのは6か月でもやっていたら分かる話ではあるが。

しかし一つ気になることがある。 この種の問題に対して、 多くの本が提案している共通の解決策があるのだ。 「掲示板を使うな」「基本は口頭で」「電話で」というソリューションである。

実際に掲示板でバトルに参加した経験があったりすると、 そうだそうだと錯覚してしまう人もいるかもしれない。 ただ、不思議なことに、 この種の本にはなぜか口頭で全然話が通じないヘンな人が大勢でてきて、 プロジェクトを滅茶苦茶にする事例で大活躍していたりするのである。

人間は社会生活をしながら育つから、 コミュニケーションの技術というのは、 ある程度は自然に身に付く。 ただし、あくまでコミュニケーションは技術なのだ。 「技術」という漢字をその通り解釈するならば、 それは技であり術である。 意識しなければ上達しないし、 訓練して場数を踏まないと、極めることはできない。

BBSのような世界には独特のコミュニケーションスタイルがあると言われている。 私はそれを否定しないが、 とはいっても、 文字だけのコミュニケーションに慣れていない人の先入観によるものが大きいような気がする。 実は、 文字によるコミュニケーションというのは、誤解の入りにくい、 優れた手法の一つなのだ。

いや、直接会話した方が分かるよ、という人は、よく考えてみた方がいいと思う。 まず、音声による情報伝達というのは、非常にエラー率の高い情報伝達方法なのだ。 しゃべるときには噛んでしまうし、聞き間違える。 しかも、先入観によって都合よく聞き間違えるのも人間の習性である。 面と向かっているのだから、 コミュニケーションの途中で確認できるというメリットはあるが、 なかなかそういう場面にはお目にかかれない。 実際、会話中に勘違いや誤解をした経験がない人は皆無だと思うが、 さらに、表情というのものが入るとややこしいことになる。

実世界では、不満があってもとりあえずにこやかに応対する、 なんてことは普通にあることだ。 相手に不満があるということを隠すのは別に構わないが、 この場合、 おそらく、コミュニケーションしている相手は、 もう一方が不満を持っていることに気付かない。 というより、相手が好意的であると誤解するはずである。 というか、そのように誤解させて有利に交渉するための「にこやかな応対」なのだ。 そういう意味では、このコミュニケーションは正しく誤情報を伝えたと言っていいのかもしれない。 それに、真意が分からないからといっても、ところでそれマジですかとテストしてみるのもヘンだし、 本当にそんなことを言ったら、 極めてややこしい事態になったりしそうな気もする。

§

  

テストドリブン開発という開発手法がある。 ブログでテストファーストの話とかを書いたときの絡みで知ったのだが、 そのときは、 テストファーストとテストドリブンの違いがよく分からなかった。 テストファーストの場合、テストケースはもちろん書くのだが、 実際に、必ずしもテストケースから書くとは限らなかったりする。 うっかりすると、テストケースが書けないとか、 テストケースを書く時点で、テスト対象の処理をリファクタリングする、ということがしばしば発生する。 テストドリブンの場合、 テストケースを最初に書いておいて、 それを元にして実際に使うコンポーネントを設計する。 最初からテストケースがあるのだから、 後からテスト対象の処理を書き直すということは有り得ないというか、 設計中に同時に行っているという感じだろうか、そういう差だと思う。

 

※ 概念レベルの違いは分かるのだが、 実際にテストドリブンのつもりでソフトウェアを作っていても、 本当にテストドリブンなのか、という状況に陥ることがあるような気がする。

テストケースに合わせてリファクタリングが必要になる場合は結構ある。 Factoriy Method を使った処理に書き換えてモックが使えるようにするとか、 そのような割と大きな変更もあるが、 私の場合、結構くだらないのが多い。 private メソッドを protected にするとか、 void ではなく boolean の値を返すようにするとか、 そのような細かい修正だ。 例えば、List 1。

---- List 1 (テストが難しいメソッド) ----

    /**
     * 指定した日付の発言が入った XML ファイルを生成する。
     * 
     * @param date Date 処理対象の日
     * @param dailyPages DailyPages 1日分の XML を生成するためのクラス
     * @param buffer StringBuffer 結果を格納するためのバッファ。
     */
    private void createXmlByDate(Date date,
            DailyPages dailyPages, StringBuffer buffer) {

        Date fromDate = MawouUtil.adjustDate(date);
        Date toDate = MawouUtil.adjustDateWithOffset(date, 1);
        dailyPages.createPage(fromDate, toDate, buffer);
    }

---- List 1 end ----

このメソッドに対するテストケースは、どう書けばいいか?

  

まず、private というのがひっかかる。 外部のテストケースからは呼び出せないからだ。 public か protected に変更すれば問題は解決するが、 テストのために public にするというのは愚作ということで、 ここは protected で宣言し、 同じパッケージにテストケースを置くのがよいとされている。 protected にした瞬間に、同じパッケージから丸見えになってしまうが、 拡張性を考えればむしろ protected だろという考え方もあるようだ。

 

※ 拡張性を考えれば…というくだりは、どうも怪しいような気がする。 protected の話は、 テストケースを別のディレクトリに分離するときに、 別のパッケージにしてしまうと public のメソッドしか呼び出せなくなるから、 同じパッケージ名の別ディレクトリを作っておいて、 そこから protected のメソッドを呼ぶ、という手法。 なお、private のメソッドのままで外から呼び出すためのツールを使う、 という手もある。

この例で困るのは、このメソッドの一体何をテストするのか、ということだ。 createPage は void だから値は何も帰ってこない。 そこで、無理やり createPage を boolean に修正して、 何かあったら false を戻すように書き直したりするわけだが、 そのような修正では false を返すということは滅多にないものである。

では、fromDate とか toDate はチェックしなくていいのか、 adjustDate というメソッドは、 指定した Date の時・分・秒を 0 にして bias 日経過した Date を返すという処理だ。 ただし、その処理は別のテストケースでテストしたはずなので、 正しい結果が戻ってくると想定しても構わないだろう。

  

初心に帰ろう。 ここでテストしたいのは、あくまで createXmlByDate というメソッドがやるべき処理を期待通りにこなしてくれたのか、 ということなのだ。 つまり、このメソッドは buffer に何かを入れて戻ってくるはずだ。 その内容が、期待したものであることをテストすればいいのである。 つまり、 指定した期間内の発言がバッファの中に入ったかどうかをテストすればいい。

 

※ と書けば簡単だが、 実際にそれを確認することが難しい。

ということで、 もう一つ想像してみたのだが、こんなのはどうだろうか?

---- List 2 (現在時刻をセットする処理)----

    /**
     * 現在時刻をセットする。
     */
    protected void setCurrentDate() {
        setDate(new Date());
    }

---- List 2 end ----

setter となっている setDate は別にあると想定しておく。 これは多分期待した通りに動作するはずなのだが、 問題は、それをどうやってテストケースとして判定すればよいのか、という話なのだ。

---- List 3 (テストケース?) ----

    public final void testSetCurrentDate() {
        Hoge hoge = new Hoge();
        hoge.setCurrentDate();
    }

---- List 3 end ----

意味が分からない。というか、ここで一体何をテストすればいいのだ? さっきと同様に立ち返ってみるなら、現在時刻がセットされたことを確認すればいいんだ。ということは、こんな感じか?

---- List 4 (違うものをテストしようとしている) ----

    public final void testSetCurrentDate() {
        Hoge hoge = new Hoge();
        hoge.setCurrentDate();
        assertEquals(new Date(), hoge.getCurrentDate());
    }

---- List 4 end ----
  

言いたいことは分かるが違う。 getCurrentDate() が返すのは、 assertEquals を通る瞬間の現在時刻ではなく、 1行前の処理が行われたときの現在時刻だ。 たまたま1ms以内に処理が行われたら同じ値になるかもしれないが、 それはテストというよりは運試しに近い。

 

※ かなり怖いが、 システムクロックを停止してテストするという手を後で思いついた。 でも、やらない方がいい。

かといって、 テストのために仮の「現在時刻」を代入できるようにリファクタリングして、 それが同じ値になったらok、 というのは、本来の処理をテストしたことにならない。 だいたい、パソコンの時刻がおかしかったらどうする、 とか考え始めると夜も眠れない。

 

※ 音とか画像の質が問題になるときは、 どうテストすればいいだろうか。 処理時間が想定内であることを確認するとか、 リアルタイム性をテストするのは難しい。 テストケースで確認できるのは、 あくまでスタティックな振る舞いに限ると思った方がいいのだろう。

(C MAGAZINE 2005年10月号掲載)
内容は雑誌に掲載されたものと異なることがあります。

修正情報:
2006-03-01 裏ページに転載。

(C) Phinloda 2005, 2006 All rights reserved.