開発の進め方

INPUT · スライド

テストの尽くし方と見積り

01 / 12

「動いた」は「正しい」ではない

ここまでのコースで、コードを書いてエラーを直し、思った表示が出るところまで何度もたどり着いてきた。あの瞬間の安心感は本物だけれど、あれは「自分が試した入力で動いた」だけなんだ。

たとえば年齢を受け取る画面を作ったとする。自分は 20 と打って確かめた。では 17 は? 0 は? 空欄は? 全角の 20 は? マイナスは? 試していない入力の数は、試した入力よりずっと多い。

テストの話は、この「試していない分」をどう減らすかの技術だよ。全部の入力を試すことはできないので、少ない回数で疑いを大きく晴らす選び方を考える。ここからのレッスンは、その選び方に名前をつけていく作業だと思ってほしい。

02 / 12

作る順と、試す順は鏡になっている

テストは1種類ではなく、作った単位ごとに段を分けて行う。小さいものから大きいものへ、4段だよ。

  • 単体テスト … 関数やモジュール1つが仕様どおりか
  • 結合テスト … つないだモジュール同士の受け渡しが合っているか
  • システムテスト … 全体として、性能や例外も含めて仕様どおりか
  • 運用テスト(受入テスト) … 使う人の立場で、要求を満たしているか

おもしろいのは、この並びが設計の並びをちょうど逆にしたものになっていること。プログラムを作ったら単体テスト、内部設計に対しては結合テスト、外部設計に対してはシステムテスト、要件定義に対しては運用テスト。左に降りて右に登る形なので V字モデル と呼ぶよ。

どの工程で決めたことを確かめるテストなのか、が対応の中身だよ。

要件定義  ←→ 運用テスト外部設計  ←→ システムテスト内部設計  ←→ 結合テストプログラム ←→ 単体テスト

03 / 12

スタブは下、ドライバは上

モジュール1つだけをテストしようとすると困ることが起きる。その部品は、ひとりでは動かないからだよ。上から呼ばれる前提なら呼んでくれる相手が要るし、下を呼ぶように書いてあるなら呼ばれる相手が要る。でも相手はまだできていない。だから足りないまわりを仮に用意する

このとき、結合の進め方によって用意するものの名前が変わる。ここがこのレッスンでいちばん混同されるところだよ。

トップダウンテストは上位のモジュールから始める。上位は下位を呼ぶので、まだ無い下位の代わりが必要になる。これが スタブ だよ。

ボトムアップテストは下位のモジュールから始める。下位は誰かに呼ばれないと動かないので、まだ無い上位の代わりが必要になる。これが ドライバ だよ。

覚え方は名前の意味から引くのが確実だよ。ドライバは「運転手」で、上に座って下を動かす側。スタブは「切り株」「半券」で、呼ばれて短い返事をするだけの下側。「上から始めるならスタブ、下から始めるならドライバ」と、始める向きと逆の名前が要ると押さえよう。

トップダウン  [ A ]  ← いま試す  (スタブ) 下位の代わりボトムアップ  (ドライバ) 上位の代わり  [ B ]  ← いま試す

04 / 12

中身を見て試す ― ホワイトボックステスト

テストデータの選び方には、大きく2つの立場がある。1つ目は、プログラムの中身(内部構造)を見て、どこを通すかを決める ホワイトボックステスト だよ。

中身が見えているので、「この if の中を1度も通っていない」といった漏れが分かる。単体テストで使うのはほぼこちらだね。

どこまで通せば十分かの基準を 網羅(カバレッジ) と呼び、厳しさの違いで名前が分かれている。

  • 命令網羅 … すべての命令を少なくとも1回通す
  • 判定条件網羅(分岐網羅) … すべての判定で、真と偽の両方を通す
  • 条件網羅 … 判定の中の個々の条件それぞれで真と偽を作る
  • 複数条件網羅 … 個々の条件の組合せを全部作る

下に行くほどテストケースが増え、厳しくなるよ。

if (a > 0 && b === 1) {  x = x + 1}

05 / 12

厳しい基準はゆるい基準を含む

網羅基準には包み込みの関係があって、ここが試験でよく問われるよ。

さっきの if (a > 0 && b === 1) で考えよう。判定条件網羅を満たすには、真になるデータと偽になるデータの両方が要る。真を通れば if の中の命令を通り、偽を通れば if の外の命令を通る。つまり判定条件網羅を満たしたなら、命令網羅も自動的に満たしている

逆は成り立たない。命令網羅は「すべての命令を1回通す」だけなので、else の無い if では真になるデータ1つだけで達成できてしまう。偽の側を通っていないので判定条件網羅には届かない。

だから関係はいつも一方通行だよ。厳しいほうを満たせばゆるいほうも満たすが、その逆は言えない

命令網羅   … 通ればよい判定条件網羅 … 真と偽の両方条件網羅   … a と b 各々で複数条件網羅 … 組合せ全部

06 / 12

外から試す ― ブラックボックステストと同値分割

もう1つの立場は ブラックボックステスト で、中身を見ずに仕様だけを見る。入力を入れて、出てきた結果が仕様どおりかを見る。機能仕様やインタフェースの仕様がテストデータの根拠になる。

中身を見ないので、使われていない余分なコードがあっても気づけない。これは弱点として必ず選択肢に出てくるよ。逆に、内部構造が変わってもテストを作り直さなくていいのは強みだね。

データの選び方の基本は 同値分割 だよ。同じ扱いになる入力をひとまとめにして、そのグループから代表を1つだけ選ぶ

if (age >= 18) という判定なら、扱いは2通りしかない。「断られる仲間」と「通される仲間」。10 でも 5 でも 17 でも結果は同じなので、グループから1つ選べば足りるという考え方だよ。

if (age >= 18) { ... }同値分割  18 未満 → 断る  18 以上 → 通す  代表: 10 と 30

07 / 12

境界がいちばん危ない ― 限界値分析

同値分割の代表を選ぶとき、1030 のような真ん中の値だけでは見逃すバグがある。いちばん間違えやすいのは >=> の書き分けで、それがずれると境界のちょうど1つだけが違う結果になるからだよ。

そこで 限界値分析(境界値分析) を足す。グループの切れ目とその両隣を狙ってデータにするやり方だよ。

if (age >= 18) なら境界は 18。狙う値は3つ。

  • 17 … 境界のすぐ手前。断られるべき
  • 18 … 境界そのもの。通されるべき
  • 19 … 境界のすぐ先。通されるべき

もし書き手が age > 18 と書き間違えていたら、18 を試した瞬間に落ちる。真ん中の値だけを試していたら永久に気づけないバグだよ。

数える問題では、境界は1つの切れ目に2つの値があることを忘れないでね。1〜100 の範囲なら、01100101 で4つだよ。

if (age >= 18)境界は 18  17 → 断る(すぐ手前)  18 → 通す(ちょうど)  19 → 通す(すぐ先)

08 / 12

直したら別のところが壊れる ― 回帰テスト

バグを1つ直したあと、直した場所だけを試して終わりにしてはいけない。共通の関数に手を入れたなら、その関数を呼んでいた別の画面まで影響を受けているかもしれないよね。

これを確かめるのが 回帰テスト(リグレッションテスト) だよ。目的は1つで、変更が、影響を受けないはずのところに影響していないかを確かめること。

新しく作った機能を試すのではなく、前から動いていたものがまだ動くかを試すのが特徴だよ。保守のたびに走らせるので、手で毎回やるのは現実的でなく、自動化される代表的なテストでもある。

名前で選ぶ問題が多いので、「保守のための変更」「影響を受けないはずの箇所」という言い回しを見たらこれだと反応できるようにしよう。

09 / 12

動かす前に読む ― レビューと静的テスト

誤りを見つける方法は、実行することだけではない。動かさずに、成果物を人の目やツールで読むやり方もあって、こちらを 静的テスト と呼ぶよ。実行するほうは動的テストだね。

人が読むのが レビュー。やり方に名前がついている。

  • ウォークスルー … 作成者が説明し、参加者が質問やコメントをする。会議形式で気軽に
  • インスペクションモデレータが進行役をつとめ、参加者が決まった役割でチェックリストに沿って見て、正式な記録を残す
  • ラウンドロビン … 参加者が順に司会役をまわしていく
  • パスアラウンド … 成果物を配布・回覧してコメントをもらう

インスペクションがいちばん形式が固い。役割・チェックリスト・記録の3語が出てきたらこれだよ。ツールが読むほうは 静的解析 で、ソースコードを解析して誤りを検出する。

静的 … 動かさずに読む      レビュー / 静的解析動的 … 実際に動かす      単体〜運用テスト

10 / 12

バグを追い、バグを数える

見つけた誤りの原因を探して直す作業が デバッグ だよ。テストが「あるかどうかを調べる」段で、デバッグは「どこにあるかを突き止めて直す」段だと分けておこう。

道具としては、途中で止めて変数を覗くトレーサデバッガ、実行時に通った経路を記録するツールがある。

チームで作るときは、見つかったバグを1件ずつ記録して数える。累積の件数をグラフにしたものが バグ管理図 で、その曲線は 信頼度成長曲線 と呼ばれる形になる。はじめはどんどん見つかり、進むにつれて頭打ちになる S字 だよ。

読み方に癖がある。曲線が頭打ちになったら「もう出ないから安心」ではない。テストが止まっているだけかもしれないからだよ。だからテスト項目の消化件数も一緒に見る。消化が進んでいるのに出ないなら本当に落ち着いてきたと言えるけれど、消化もバグも未解決件数も全部横ばいなら、難しいバグに詰まって作業そのものが止まっている疑いがあるよ。

バグ累積  │     ┌──── 頭打ち  │   /  │ /  └────── テスト消化

11 / 12

どれだけかかるかを見積もる

作る前に「どれくらいの規模か」を数字にするのが 見積り だよ。代表的なやり方が2つある。

LOC 法(プログラムステップ法) は、書く行数を見積もって、1行あたりの手間をかける。素朴で分かりやすいけれど、行数は使う言語や書き方で変わるので、比べにくいのが弱点だね。

ファンクションポイント法 は行数ではなく、外から見える機能の数で測る。外部入力・外部出力・外部照会・内部論理ファイル・外部インタフェースファイルの個数を数え、それぞれに重みをかけて足し、複雑さの補正係数をかける。

利点は、プログラムを書く前に、仕様だけで見積もれること。行数を予想しなくていいし、言語が変わっても値がぶれないよ。

ほかに、過去の似た案件と比べる類推法、作業を細かく分けて足し上げる標準タスク法も出てくるよ。

LOC 法  行数 × 1行あたりの手間FP 法  外部入力  1 × 4 =  4  外部出力  2 × 5 = 10  内部ファイル 1 × 10 = 10  合計 24 × 0.75 = 18

12 / 12

版をそろえる ― 構成管理とバージョン管理

最後は、直したものを取り違えないための仕組みだよ。

バージョン管理 は、ソースコードの変更を1つずつ記録して、いつでも前に戻せるようにするもの。Git がその代表だね。

構成管理 はもっと広い。ソースコードだけでなく、仕様書・設計書・テスト仕様書まで含めて、どの版が一組なのかを管理する。変更するときの手続きを決めておくことも構成管理の仕事だよ。

うまくいっていないと、こういう問題が起きる。

  • 手続きを踏まずに直したので、動いていたものが動かなくなる
  • 仕様書と設計書とプログラムの版がずれて、どこを直せばいいか分からない
  • 派生した別のプログラムに、元のバグ修正が反映されない

どれも「版がそろっていない」ことから来ているのが共通点だよ。逆に「テストでバグが多発して進まない」のは版の問題ではないので、構成管理の話ではないと切り分けよう。

v1.0 → v1.1 → v1.2 │      │ └ 仕様書 └ 設計書   も同じ版で束ねる