移動: トップ > ラボ > プログラミングのアイディア > テストで気を付けるポイント
履歴:
統計:
目次:
このページの最終更新日: 2010/12/28
提案日: 2009/01/13

テストで気を付けるポイント

概要

概要文章
○ 短い背景+をするかの短い説明(どちらにも一番分かりやすく短い具体例を使う、まとめではない)
○ 「~するといったアイディアです。」の形で終わる
○ これによって現在よりもよくなること

プログラムを作成したら、必ずテストを行って動作を確認します。このように、どんなときでもコードを書いたら、後に必ずテストを行わなくてはいけません。そのため、後で必ず行うテストのために、コードを書く段階から気を付けるとよい点をまとめます。

背景

開発の中盤になって気付いた点

背景文章
○ これが出てきた過程、これを思いつくまでの経緯、世の中の必要性などを書く。
○ 「そこで、~する、といったアイディアに至りました。」の形で終わる。
○簡単な言葉で書ける必要なことだけを短く書く。詳細な背景は後で書く。

開発を行う中で、初めの頃はまったく気付かず、開発の中盤になって、本当はこうしておけばよかった、と気付く場合が多くあります。しかし、中盤になってから気付いた点を実践しようとすると、大抵、多くのコードを書き換えなくてはいけません。

そこで、本記事では、このような過程を踏んで分かった、気を付けておくとよい点をまとめます。そして、今後の開発では、コードを作り始める段階から、これらを心がけるようにします。

どんなもの?

後で必ず行うテストのために

方法文章
○「背景」からの流れを使わず、ここから読んでも分かるように新しく文章を書き始める。
○はじめに筆者が想像するその具体的な場面状況をまず読者に伝える。「~ときに/~の場合を考えます」のようなフレーズを使う。
○状況設定の後、そのアイディアがどんなものなのかを一言(最も短い例)で書く。 「~といったアイディアです。」のようなフレーズを使う。限定しすぎたり不足があっても後の文章で修正できる。

○一言説明の後、アイディアの説明を1つの例に限定してそのシナリオ全部を書く。
取り残した部分、利点性質後で書く。

プログラムを作成したら、必ずテストを行って動作を確認します。このように、どんなときでもコードを書いたら、後に必ずテストを行わなくてはいけません。そこで、後で必ず行うテストのために、コードを書く段階から気を付けるとよい点をまとめます。ポイントを次の表に挙げます。以降で各項目の内容を詳しく説明します。

  A. クラスの下にユニットテストを書く
   B. テストの結果は見ればすぐ分かるように表示する
   C. テストの結果には必ず正解も表示する
   D. テストの結果は成功/失敗を明確に表示する
   E. 手作業の操作を自動化してワンタッチ化する
   F. いろいろな値が入力されても実行が止まらないコードを作る

A. クラスの下にユニットテストを書く

ある機能を持つクラスを作ったら、その機能を確認するテストコードを必ず作るようにします。テストコードは、例えば、クラスをnewして、クラスのパラメータを設定して、模擬データをクラスに入れて、クラスのメソッドを呼び出して、結果が正しいかを判別する、一連のコードです。テストしたい項目が複数あれば、複数のテストコードを書きます。

テストすることで、こんなに簡単なクラスはうまくいって当然と思っていたところで、思わぬ小さなバグを発見できます。動作が単純なクラス単体のうちなら、バグ原因の発見は容易です。また、クラス単体のテストが完璧であれば、後に行う全体テストでバグを探すとき、クラス単体のバグは探さなくてよくなるので、バグを探す範囲が狭まり、全体テストでバグを素早く発見できるようになります。

テストコードは、クラスを作ったそのときに一緒に作ります。時間が経つと、そのクラスがどのように動作するかや、どの点をテストすればよいかを忘れてしまいます。クラスを作れば、その動作を必ず確認しなくてはいけないので、クラスの特徴を覚えているうちに、テストコードを作っておきます。

また、テストコードが残っていれば、時間が経ってからでも、そのクラスの使い方が分かります。クラスをテストするということは、そのクラスを使うということなので、テストコードには必然的にクラスの使い方が残ります。テストコードを見ると、どのメンバにどのような値を設定すればよいかや、どの順で呼び出せばよいかなど、そのクラスの想定する使い方が分かります。

さらに、テストコードは、正常に動作することが確かめられた、完璧なサンプルコードになります。そのクラスを使う上で、必要なコードが漏れなく書いてあり、エラーなく動作することが保証されています。テストコードをコピー&ペーストすれば、誰でもそのクラスをとりあえず動作させることが可能です。

各関数とクラスの下にユニットテストを書くこと。うまくいって当然と思っていたところで、小さなバグを発見できる。責任がしっかり与えられて、動作パターンが少ない=振る舞いが単純な関数のうちなら、バグの発見は容易。また、全体のうち、どこまでは完璧と分かるので、バグを探す範囲を特定でき、全体のバグ発見のスピードUPに貢献。テストコードから、その関数/クラスの想定する使い方が分かる。

B. テストの結果は見ればすぐ分かるように表示する

テストを行うと、必ずその結果が出来ます。そのため、テストコードの中で結果を表示するコードを作ります。このテストの結果は、テストする度に毎回人間の目で確認するものです。そのため、結果は見ればすぐに分かるような形式で表示します。少しでも数値の変換や頭の中での解釈が必要なものはだめです。

例えば、変換が必要な悪い例を次の表に挙げます。また、悪い例を直して、見ればすぐに分かるような形式で表示した良い例を隣に示します。

  悪い例 良い例
  掛かった時間を12345秒と表示する 3:25:45と表示する
 
  • いつも数時間オーダーで時間が掛かるなら、時間:分:秒の形式に直して、大まかに何時間ぐらい掛かるかをすぐに分かるように表示する。
  求めた速度を12.34[m/秒]と表示する 44.4[km/時間]と表示する
 
  • 通常イメージしやすい時速[km/時間]の単位に直して、感覚的にすぐ分かるように表示する。
  計算した3×3の行列を[[1.001, 2, 3.456], [4, 5.001, 6], [7.5, 8.5, 9.001]]と表示する [[1.001, 2.000, 3.456],
[[4.000, 5.001, 6.000],
[[7.500, 8.500, 9.001]]
 
  • 行列をぱっと見たときに見間違えないように、行列の行と列をそろえて表示する。
  ベクトルの配列で表示する
[0.5,0,0.1], [1,0,2.2], [1.5,0.5,0.8], [1.01,0.2,0.3], [2.5,1.5,0], [1,0,1.2], [0.9,0.6,0.1], [0,0.4,0.7], [0,0.5,0], ...
2D / 3Dのグラフで表示する
 
  • ベクトル配列の値だけを見ていてもイメージが思い浮かばないので、全体の傾向を俯瞰できるグラフで表示する。

このように、テストの結果は見ればすぐに分かるような形式で表示します。テストは何度も行うものです。なので、テストの結果は、テストを行う度に何度も見ることになります。毎回、結果を手作業で変換していると、テストの作業スピードが落ちてしまいます。繰り返し手作業になるので、面倒であまりテストしなくなるかもしれません。また、変換し間違いや結果の見間違いがよく起こって、テストの意味がなくなる恐れがあります。

それよりも、ちゃんと時間を割いて、変換のコードを1度書くだけでこれらはすべて解決します。さらに、結果が見やすいとバグ原因を早く発見できるようになります。特に表やグラフで視覚化すると、それまで気付かなかった傾向や特長が分かります。そのため、見やすい形式があるなら、必ずその形式に変換して表示します。グラフで表示すれば一番良いなら、必ずグラフで表示するところまでコードを作ります。

テストの結果は見ればすぐ分かるように表示すること。少しでも変換や頭の中の計算が必要なものはだめ。グラフで表示できれば一番いいなら、必ずグラフで表示することろまでコードを作る。例:ベクトルの数値列挙でなくて、Blender上でMeshを使って方向を表示するなど。
だめな理由 : テストは何度も行う。毎回、テスト結果を手作業で変換しなくてはならない→面倒でテストしなくなる、変換し間違い/結果の見間違いが起こってテストの意味がなくなるそれよりも変換のためのコードを1度書くだけでよい。さらに、結果が見やすいとバグ原因を早く発見できる、特にグラフで視覚化すると、それまで気付かなかった特長が分かることがある。
結果が見やすいとバグ原因を早く発見できる、特にグラフで視覚化すると、それまで気付かなかった特長が分かることがある。

C. テストの結果には必ず正解も表示する

テストの結果には、実行結果の他に必ず正解も表示して、結果が正しいか判定できるようにします。例えば、1+2のテストの場合なら、「1+2=3, 正解:3, OK」のように「正解:3」を合わせて表示します。

正解を表示しておくと、時間が経ってから再びテストする場合に役立ちます。時間が経つと、テストコードの内容などは直ぐに忘れてしまいます。なので、どのような結果が正しいかも忘れてしまいます。すると、テストは実行できても、その結果が正しいかどうか直ぐに判断できなくなり、再度コードを読み直して正しい結果を見つけなくてはいけません。

テストをしているその瞬間は、コードの振る舞いがよく分かっているので、正解なんて自明だと思っても、時間が経つとすっかり忘れてしまうので、自明と思った正解が何か分からなくなるものです。そのため、はっきり正解を表示して、いつでも結果が正しいか判定できるようにします。

何か考えないと正解が分からないような表示はだめです。実行結果と同じ形式ではっきり正解を表示します。正解の形式が、長い式や大きな行列、表など表示しにくい場合は、ソースコードに埋め込んで残します。また、正解の形式が、グラフや説明付きの画像などの場合は、別のピクチャファイルとして残します。

正解をソースコードや別ファイルに残しておくと、テストを実行しなくても、テストの実行結果がどのようなものかが分かります。そのテストを作った当初に想定していた理想的な動作が正解から分かります。

■テストの結果には、必ず正解も表示して、結果が正しいか判定できるようにすること。副産物として、その関数がどんな動作をするかと、使うにはどのようなコードを書けばよいかが分かる。 正解を表示しにくい場合は、テストの結果をソースコードに埋め込んで残す(長い式、行列、表など)/別のピクチャファイルとして残す(グラフ、説明付きの画像など)。

D. テストの結果は成功/失敗を明確に表示する

テストの結果には、実行結果の他に必ず成功/失敗を表示して、ぱっと見てテストが成功したかどうかを判断できるようにします。例えば、1+2のテストの場合なら、「1+2=3, 正解:3, OK」のように「OK」を合わせて表示します。

成功/失敗が目で見てすぐに分かると、「B.」と同じようにテストの作業スピードが上がります。特に、とても多くの実行結果やテスト項目がある場合に効果的です。多くの実行結果がある場合、成功/失敗の表示がないと、毎回人の目ですべての結果を正解と照らし合わせなくてはならず、手間が掛かります。すべての結果をプログラムで照らし合わせて、全て一致すれば「成功」、1つでも不一致があれば「失敗」と表示することで、作業スピードが上がります。

テストの結果が「成功」と表示された項目は、実行結果が正解と一致したことになるので、その詳しい実行結果を見る必要がありません。「失敗」と表示された場合だけについて、実行結果を見て振る舞いを調べればよくなり、作業スピードが上がります。

UnitTestを支援するライブラリを使えば、自然に成功/失敗を明確に表示できます。例えば、.NET用のUnitTestライブラリの1つにNUnitがあります。NUnitでは、実行結果と正解の値を「Assert.AreEqual(実行結果, 正解の値);」という形で代入して、2つの値が等しいかどうかを判定します。この判定結果は、別の管理ウィンドウに一覧で表示されるので、自然に成功/失敗を明確に表示できます。さらに、数多くのテスト結果を簡単に管理できます。

これまでに記述した「B.」、「C.」、「D.」の内容を合わせると、テスト結果には、ぱっと見て分かる実行結果、ぱっと見て分かる正解の値、成功/失敗の3つを表示します。

テストの結果が正しいか判定して、成功/失敗を明確に表示すること。なので、すぐ分かるテスト結果、正解、成功/失敗の3つを表示する。UnitTestするライブラリを使用すれば、自然に成功/失敗を明確に表示できる。

E. 手作業の操作を自動化してワンタッチ化する

テストの実行の中で手作業の操作がある場合は、その操作をバッチやスクリプトなどで自動化して、なるべく少ない操作でテストの実行が出来るようにワンタッチ化します。

Visual StudioのようなIDE環境で開発できれば、ここでのワンタッチ化は不要ですが、例えば、複数のビルダを使ってビルドしなくてはいけない場合、複数のビルダを呼び出したり、生成されるファイルを移動する処理をスクリプトに書いて自動化し、スクリプトを1回実行すれば全ての処理が完了するようにワンタッチ化します。

テストは頻繁に繰り返し行うものです。毎回のテストで繰り返しになりそうな手作業の操作は、時間を掛けてでもはじめにスクリプトを作って自動化します。きっと後から、それ以上の操作時間を節約できることになります。今回限りしか使えない特殊なスクリプトになりそうでも、スクリプトを使って自動化します。

■テストの実行に必要な作業に手の操作がある場合は、手の操作をバッチやスクリプトでオートメーションして、ワンタッチ化すること。そのケース限りの(今回だけの)スクリプトになりそうでもスクリプトを作る。毎回繰り返しになりそうな操作は、時間を投資してでもはじめにスクリプトを作る。きっと、後からそれ以上の操作時間を節約できることになる。例:ビルド/実行/テスト実行にかかる手作業の数を減らす。

F. いろいろな値が入力されても実行が止まらないコードを作る

クラスのフィールドや関数の引数などに、いろいろな値が入力されても、なるべく例外などで実行が止まらないようなコードを作るようにします。次の表に、いろいろな入力値に対して、どのようにコードを書けば良いか、例を挙げます。

  入力値X コードの作り方
  入力値Xが100で、想定する範囲1<X<10以外になる場合 100に近い側の境界値10が入力された場合と同じ動作をするようにコードを作って、エラーを回避します。
  入力値Xがモードの設定値で、不適切なモードが設定された場合 何もしないようにコードを作って、エラーを回避します。
  入力値Xで計算すると、途中でゼロ割りが発生する場合 計算式の中の割る値(分母)dが-1e-6<d<1e-6の中に入ったら、1e-6で割ったときと同じ動作をするようにコードを作って、エラーを回避します。
  入力値ではどうしても処理を続けられない場合 例外を発生させて、適切にその例外を受けるコードを作って、エラーを回避します。

このように、なるべく実行が止まらないようにコードを作ることで、結合テストや総合テストのような上位のテストがしやすくなります。例えば、さまざまな条件下で結合テストをするときに、変数に想定外の値が入るようになっても全体の実行は止まらず、とりあえず動作が確認できます。

もし、このようにコードを作っていないと、ひとたび想定外の値が入ったら、いたるところで例外が発生して、動作が確認できなくなります。この場合、発生する例外すべてに対して、対処するコードを作らないと動作しません。そのため、総合テストの作業により多くの時間が掛かります。

実行が止まらないようにコードを作ることで、機能を拡張したときに、その拡張による変更の波及小さく抑えられます。例えば、ある機能を拡張しようとコードを変更したら、内部の変数にこれまでとは異なる値が入るようになって動作しなくなる、といったことが多くあります。この場合、原因となる変数をコード上で追いかけて原因を見つけ出し、うまく動作するようにコードを修正しなくてはいけません。これは、拡張しようとコードを変更したら、他の部分へ変更が波及してしまう悪い傾向です。

そのため、はじめから、いろいろな値が入力されても、実行が止まらないようにコードを作ります。これによって、機能を拡張したときに、変数に想定外の値が入るようになっても、実行が止まらず、すぐに動作が確認できます。他の部分を変更しなくて済むようになり、変更の波及を小さく抑えられます。

実行が止まらないようにコードを作ることで、結果的に、適切にエラー処理されたコードを作ることが出来ます。エラー処理は、開発のどこかで必ず作らないといけません。そのため、エラー処理を後回しにせず、作り始めの段階から、実行が止まらないようなコードを作るようにして、自然にエラー処理がコードに含まれるようにします。

■クラスのフィールド/関数の引数などに、いろいろな値が入力されても、例外などで実行が止まらないようなコードを作ること。または、適切に例外を受けるコードを作ること。さまざまな条件下でテストをする(結合テスト、総合テスト)とき & 機能を拡張したときに、変数に想定外の値が入るようになっても実行が止まらず、とりあえず動作が確認できる。ex: null値でなくて空のオブジェクトでアクセス例外回避など。これが最終的には適切なエラー処理されたコードになる。

世の中の似た機能

現状文章 (同じこと、似たことがされている場合のみ)
参照は「本記事のアイディアのように/本記事で提案した~」

一般的に、次のような点が大切とよく言われます。

  • 1つのクラス/関数に対して、ユニットテストを書く。
    ユニットテストのためのライブラリが世の中に数多くあり、ユニットテストの重要性が分かる。
  • エラー処理を漏れなく書く。

UnitTest/エラー処理の重要性。

 

更新履歴
2010/11/19
  • 書き始め
2010/12/26 v1
  • 初版作成

※ご意見、ご感想、改善点、その他の情報などがありましたら、メールにてお知らせ願います。

Copyright (C) 2002 - 2019 Simon.P.G. All Rights Reserved. Top | Simon.P.G. とは | 使用条件 | ご意見
inserted by FC2 system