基礎勉強会 #2

ソフトウェアテストの基礎

今回の話

  • ソフトウェアテストとは
  • テスト計画をたてよう
  • テスト設計をしよう
  • テストコードを書こう
  • テスト駆動開発をしてみよう
  • 参考資料

今回の話の対象

  • 弊社新人
    • なんか最初はテストから入ることが多いよね
    • Excel でテスト設計もするし、テストコードも書くはず

あるいは

  • 開発者向け
    • 自分で実装をして、かつ、自分でテストを行う人

今回話さないこと

  • 特定の言語、フレームワークによった話
    • Selenium や JUnit5 の使い方など
  • 非機能テスト
    • パフォーマンステスト
    • セキュリティテスト
    • ユーザビリティテスト
  • テストの自動化
    • かなり別の分野
    • 別の機会に

「ソフトウェアテスト」の話をするのは難しい

  • プロジェクトによって適切なテストは違う
    • ウォーターフォールかアジャイルか
    • GUI か CLI か
  • 極端な意見が出やすい
    • 「テストをする必要はない」
    • 「カバレッジは100%を目指すべき」
  • 用語が混乱しやすい
    • 「単体テスト」と「ユニットテスト」は違う?

ところで

用語は何を参考にするのが良いか?

  • JSQTBシラバス
    • ISQTB(国際ソフトウェアテスト資格認定委員会) の日本人技術者むけ団体が出しているテキスト
  • IEEE829-2008
    • ソフトウェアテストドキュメントの国際標準

おまけ

ソフトウェアテストとは

ソフトウェアテストとは

 

  • ソフトウェアの次の性質を確認してフィードバックすること
    • 欠陥がどの程度存在するか?
    • 品質をどの程度満たしているか?

 

欠陥、品質はフィードバックがあって初めて改善される

→ レポートや報告などでフィードバックするまでがテスト

 

  • 欠陥とは?品質とは?
    • プロジェクトによりブレがある
    • 受託開発の場合、品質は「要件をどの程度満たすか」
    • 欠陥は機能系ならバグ、ユーザビリティだったら?

ソフトウェアテストにはどのようなものがあるか?

  • ソフトウェアテストには色々な分類がある
    • 静的テスト vs 動的テスト
    • 機能テスト vs 非機能テスト
    • ホワイトボックステスト vs ブラックボックステスト
    • などなど

テスト計画をたてよう

「テストしてね」といわれたら

テストの始まりはいろいろある

  • 「テスト追加しておいて」

 

  • 「一応テストやっておいて」

 

  • 「今回はテストは必要ないので早めにお願いします」

 

早速テストを始めるべきか?

「テストしてね」といわれたら

なにも考えずに手を付けるとこういうこともありうる

  • 「テスト追加しておいて」

      → 「いまどき手動テスト? CI で動かせないとこまるんだけど」

  • 「一応テストやっておいて」
    時間かかりすぎてない?手動テストで十分だったんだけど」
  • 「今回はテストは必要ないので早めにお願いします」

      → 「動かないんだけど?動作確認は当たり前でしょ」

 

認識違いは多々ある!

何事も始める前に意識合わせをしよう

テスト計画

プロジェクトの三角形に対してテストの三角形も考えられる

プロジェクトの三角形

予算

時間

スコープ

テストの三角形

リソース

工程

スコープ

テストのスコープ

  • 何をテストするか
    • どの機能をテストするか、どの機能をテストしないでよいか
  • どういう観点でテストするか
    • 正常系、異常系、機能系、非機能系 etc
  • どの程度テストするか
    • テストの終了条件は?
      • テストの種類によっては 1 回で終わらない場合もある
    • テストの成功基準は?
      • どの程度テストケースが通ったら成功になるか?
    • テストできない項目がある場合はどこまでやるか?
      • 未実装や、前提となる機能がバグで動かない場合

テストのリソース

テストに必要なリソースはなにか?

  • 機材
    • 特別なハードウェアは必要ないか?
    • OS、バージョンに指定はあるか?
  • 環境
    • クラウドかオンプレか
    • ネットワーク環境が必要か
  • 人員
    • テスト担当は何人いるか?
    • 仕様に不明点がある場合誰に聞けばいいか?

テスト工程

どのテストをどのタイミングでいつまでに実施するか?

 

  • 開発がウォーターフォールなら
    • 実装の進捗に合わせて各種仕様書に対応するテストを行う
      • 例) 詳細設計=>単体テスト、基本設計=>結合テスト
  • 開発がアジャイルなら
    • 各イテレーションでどのようにテストをするか?

テスト計画書

IEEE 829 でテストプランテンプレートというものが用意されている

  • かなり汎用なものを目指しているので、現実プロジェクトにあうかは微妙
    • 参考程度にとどめておきたい
  • IEEE 829 で探せば出てくる

ここまでのまとめ

テスト担当になってもいきなりテストを始めてはいけない

  • 人間の活動は大抵計画を立てたほうがいい
  • 意識合わせ必須
    • 「テスト」が示す意味・範囲はかなり曖昧

テスト設計をしよう

テスト設計をしよう

  • テストの観点ごとにテストの設計を行う
    • 正常系試験, 異常系試験, 性能試験, ...
  • テスト設計では
    • テストケース、テストデータを作成する
    • 作成にはソフトウェアテスト技法が使える

テストケースを書こう

いつ、誰がやっても同じように成否が判断できるように書く

  • よくある内容
    • テストケースID
    • テストケース名
    • テスト手順
    • テスト結果の確認手順
    • 期待する結果

 

テスト手順、確認手順、結果は具体的に書く

  • 入力する内容がある場合は実際に入力する値も含める

テストケースの例

テストケースID: SEARCH1234

テストケースタイトル: ファイルの検索

テスト手順:

    ステップ1: TestTaisho ページをブラウザから開く

    ステップ2: abcd と検索欄に入力する

    ステップ3: 検索ボタンを押下する

結果及び確認:

ステップ3 の実行後に検索結果画面に遷移すること確認する。検索結果画面に abcd を含む結果が表示されていることを確認する。

よくないテストケースの例

これでは誰かがやったテストを再現することができない

  • どのような入力を行うかわからない
  • どうなれば OK なのか書いていない
対象 テスト項目 結果
アルファベットファイル名 OK/NG
日本語ファイル名 OK/NG
長いファイル名 OK/NG
禁止文字を含むファイル名 OK/NG

検索機能

ソフトウェアテスト技法を活用しよう

ソフトウェアテスト技法

  • ピンポイントテスト
  • 範囲をみるテスト
    • 同値分割
    • 境界値
  • ロジックを網羅するテスト
    • ドメイン分析
    • ディシジョンテーブル
  • 組み合わせのテスト
    • 直交表
    • 原因結果グラフ
    • HAYST法
    • ペアワイズ法
    • 状態遷移のテスト

ピンポイントテスト

  • 仕様をみて怪しい点を狙い撃ちするテスト
    • 仕様の読み込み
    • 経験が物を言う場合
      • 「自然数」を見たらとりあえず 0 を入力してみる
      • 尾骶骨」、「𠮷(つちよし)」テストなど
      • SQL の処理があったらとりあえず「;」を入力してみる

同値分割

入力を条件でグルーピングして、それぞれのグループから代表となる値を選び、それをテストケースに使う方法

例えば「65歳以上」という条件が合った場合

  • 10 歳を 65 歳未満の代表値
  • 70 歳を 65 歳以上の代表値

として 2 つのテストケースをつくる

65歳

70歳

10歳

境界値分析

入力を条件でグルーピングして、それぞれのグループの上限値、下限値をテストケースに使う方法

  • 境界値ではバグができやすいので有効

例えば「65歳以上」という条件が合った場合

  • 64 歳を 65 歳未満の境界値
  • 65 歳を 65 歳以上の境界値

としてテストケースをつくる

65歳

64歳

ディシジョンテーブル

複数の条件がある仕様をテストする際に条件のすべての組み合わせを圧縮してテストケースを省く手法

圧縮は

  • 2 列の最終行のY, N が動作に影響を及ぼさない場合、その行は「どちらでもよい行」として圧縮する
品物は書籍 Y Y Y Y N N N N
合計 1,500 円以上 Y Y N N Y Y N N
配送先は離島以外 Y N Y N Y N Y N
動作 送料無料 Y N N N N N N N

条件

ディシジョンテーブルの圧縮

1 2 3 4 5 6 7 8
品物は書籍 Y Y Y Y N N N N
合計 1,500 円以上 Y Y N N Y Y N N
配送先は離島以外 Y N Y N Y N Y N
動作 送料無料 Y N N N N N N N

条件

赤枠は動作に影響なし

→圧縮できる

ディシジョンテーブルの圧縮

1 2 3 4 5 6 7
品物は書籍 Y Y Y Y N N N
合計 1,500 円以上 Y Y N N Y Y N
配送先は離島以外 Y N Y N Y N -
動作 送料無料 Y N N N N N N

条件

赤枠は動作に影響なし

→圧縮できる

ディシジョンテーブルの圧縮

1 2 3 4 5
品物は書籍 Y Y Y N N
合計 1,500 円以上 Y Y N Y N
配送先は離島以外 Y N - - -
動作 送料無料 Y N N N N

条件

赤枠は動作に影響なし

→圧縮できる

ディシジョンテーブルの圧縮

1 2 3 4
品物は書籍 Y Y Y N
合計 1,500 円以上 Y Y N -
配送先は離島以外 Y N - -
動作 送料無料 Y N N N

条件

4 件までテストケースを減らせた

  • 条件の数が少ない場合はいいが、ある程度多くなった場合はツールを活用するとよい

組み合わせテスト

  • 複数の入力項目が考えられるテスト
    • すべての組み合わせをテストしようとすると膨大な数のテストケースが必要になる(組み合わせ爆発)
  • 組み合わせテスト向けのソフトウェアテスト技法では
    • ある N 個の組み合わせが揃ったときにバグがでると考え
    • バグを見つけやすい組み合わせに絞り込む

 

(余談)

開発者のつくるテストではあまり聞かない気がする…

  • 入力値を独立に扱えている、という自信があるのかも

組み合わせテストで絞り込んだ例

No ドリンクの種類 砂糖のありなし ミルクのありなし
1 コーヒー あり なし
2 コーヒー なし あり
3 紅茶 あり あり
4 紅茶 なし なし

HAYST法でテストケースを作成した例

  • 2 つの列の組み合わせが重複しないようになっている
  • すべての組み合わせは 8 個になるが 4 個に絞り込んでいる

開発者とソフトウェアテスト技法

開発者がソフトウェアテスト技法を学んでメリットはあるか?

  • ある
    • ブラックボックステストする場合は特にあり
      • 多くのソフトウェアテスト技法はブラックボックステスト向け
      • 仕様に沿っているか確認したい場合はブラックボックステストを行っておきたい
    • テストコードであっても
      • 同値分割、境界値分析ぐらいはなんとなくやってる
      • 応用できる…かも
    • 組み合わせテストについてはあまり話を聞かない
      • 開発者側の怠慢という気もする

テストエンジニアはどういうツールを使っているか?

State of Testing Report 2018 によると

  1. バグトラッカー (75%)
  2. MS Excel, Word かそれに近いツール (59%)
  3. アジャイル開発ツール(50%)
  4. テスト管理ツール(46%)
  5. プロジェクトマネジメントツール(19%)
  6. マインドマップ(19%)
  7. 探索的テスト用のノート作成ツール(16%)

 

まだ Excel は結構使われている様子

「テスト管理ツール」が具体的に何が良いかはわからず…

おまけ

テストエンジニアはコードを書くのか?

  • テストエンジニアの求人をみてみると
    • 「必須スキル: 〇〇の開発経験」というのが結構多い
    • テストエンジニア一本のもあるが、この場合は「テスト計画」など高度なスキルが必要になる
  • 「テスト自動化エンジニア」という職種もある

手動テストだけで済む時代ではなさそう

おまけ

テストコードを書こう

テストコードでどのようなテストをするか

  • テストコードは書くのにコストがかかる
    • 3 回は実行しないとコスト相当のメリットはない
  • すべてのテストをテストコードに落とし込むのは難しい
    • 手動テストが必要な場合はある

開発者はテスト担当でなくてもテストすべきか?

だいたいの場合は YES

  • ドッグフーディング
    • 自分が作った API の使い勝手はまず自分で試す
  • ドキュメントとして
    • APIとして使い方がわかっていると便利
  • リファクタリングのため
    • 「テストコードがないものはリファクタリングと呼ばない」
  • バグの確認のため
    • Issue 登録の際にはバグを再現するコードがあるとよい

よいテストコードは FIRST

  • F - Fast                    迅速に実行できる
  • I  - Isolate                他のテストとは独立して実行できる
  • R - Repeatable       繰り返し実行しても同じ結果になる
  • S - Self-Validating  テストコード自身がテスト結果を検証する
  • T - Timely                適切なタイミングでテストが実行される

(実践JUnit からの引用)

Fast

func TestParseRequestResult(t *T) {
    c := HeavyObject()
    res, _ := c.RequestSomething() // すごい遅い処理
    v, err := parse(res) // このテストケースでやりたいこと
    T.Assert().NoError(err)
    T.Assert().Equals(v.body, "期待する結果")
}
func TestParseRequestResult(t *T) {
    c := HeavyObjectMock() // Mock に変える
    res, _ := c.RequestSomething() // すぐ終わる
    v, err := parse(res) // このテストケースでやりたいこと
    T.Assert().NoError(err)
    T.Assert().Equals(v.body, "期待する結果")
}

テストで確認したいこととは別の場所で時間がかかる

時間がかかる処理はモックに置き換え、テストしたいことが

迅速に終わるようにする

Isolate

var v Something // 使いまわしたいのでグローバル変数にする

func TestInitialization(t *T) {
    v := SomethingInitialize()
    T.Assert().Equals(v.msg, "期待する結果")
}

func TestParseRequestResult(t *T) {
    v.Change()
    T.Assert().Equals(v.msg, "別の結果")
}

2 つめのテストは 1 つめのテストの実行を前提にしている

  • 2 つめのテストだけを実行すると突然バグが発生したように見えてしまう

Isolate

func TestInitialization(t *T) {
    v := SomethingInitialize()
    T.Assert().Equals(v.msg, "期待する結果")
}

func TestParseRequestResult(t *T) {
    v := SomethingInitialize()
    v.Change()
    T.Assert().Equals(v.msg, "別の結果")
}

テストケースは独立に実行できるようにする

  • 初期化のやり方はテストフレームワークのやり方に従う

Repeatable

func TestInitialization(t *T) {
    f := LoadConfiguration()
    t.Assert().Equal(f.HasSomeValue(), false)
    f.SetSomeValue()
    t.Assert().Equal(f.HasSomeValue(), true)
    f.SaveConfiguration()

    g := LoadConfiguration()
    t.Assert().Equal(g.HasSomeValue(), true)
}

  • テストプログラムを 2 回実行すると失敗する
  • テストプログラムは何度も実行することになるので困ったことになる

Repeatable

func TestInitialization(t *T) {
    f := LoadConfiguration()
    t.Assert().Equal(f.HasSomeValue(), false)
    f.SetSomeValue()
    t.Assert().Equal(f.HasSomeValue(), true)
    f.SaveConfiguration()

    g := LoadConfiguration()
    t.Assert().Equal(g.HasSomeValue(), true)
    CleanConfiguration() // 環境をクリーンアップする
}

テストごとにテストケースによるゴミは片付ける

  • 片付けのやり方はテストフレームワークに従う

Self-Validate

func TestSomething(t *T) {
    runSomething()
    fmt.Fprintf(os.Stderr, "標準出力に Hello, World と出てるか確認してください")
}

人間が目視での確認が必要なテストコードは避ける

 

  • コードを修正する、手動テスト側に回すなど

Timely

作ったテストケースは実行されないと意味がない

なんらかのタイミングで実行する仕組みを作る

  • CI で実行するようにする
  • チーム内で push する前にテストを実行するように決める

他の読みやすくするポイント

  • テスト名が単純な文として読める
  • テストデータには意味のある値を使う
    • 「あああ」,「hoge」など無意味な値でなく
    • 「山田太郎」,「東京都」などテストコードを読んだ人が想像しやすいデータを使う
  • エラーメッセージを読みやすくする
    • アサーションのヘルパー関数を用意する
  • 1 つのテストケースでは 1 つのことだけテストする

テストコードは資産!

テストコードによるテストを実行し続けると

  • レグレッションを回避できる
  • コードの変更がやりやすくなる
  • コードに対するドキュメントとして使用できる

 

などいいことづくめに見えるが…

ひどいテストは負債になりうる

  • ひどいテストは実装の足かせになる
    • 機能の追加や機能の変更のコストが増える
  • テスト結果が不安定だと誰もテスト結果を気にしなくなる
  • しかしテストケースが削除されることはほとんどない
    • テストの追加よりテストの削除の方が非常に気を使う
    • なんらかのタイミングでリファクタリングが必要

テストコードのアンチパターン

  • 失敗しないテスト
  • コピペテスト
  • 詳細に入りすぎたテスト
  • テストカバレッジで 100% を目指す
  • テストのために本物のコードを犠牲にする
  • 大きすぎるテスト
  • 意味のない細かいテスト
  • 結果が不安定なテスト
  • 大量のアサーション

失敗しないテスト

テストコードのバグによって常に成功するテスト

 

一見無害に見えるが、

  • 常に成功するので誰も気にしない
  • バグなので本来テストしたかったことが確認できていない
  • コードのバグに対するテストだったら惨事
    • 元々のバグが修正されたかわからない上に、バグ修正者は想定していないコード変更を行ったことになる

対策

  • 機能の実装orバグの修正前にテストを先に書いて、テストが失敗することを確認する

コピペテスト

似たような機能がたくさんあるために、コピペでテストをつくる

コピペでテストできてしまうので、

  • とりあえずコピペで作っておこう
  • コピペでもテストが通ったからいいか

などの理由でテストの意味が失われてしまう

 

Template Methodパターンを採用しているときに起きやすい

 

対策

  • コピペテストの増殖に気付いたら、ヘルパー関数などを追加し、テストしたいことに集中できるようにする

カバレッジ(網羅率)について

テスト対象のコードがテストコードでどれくらい実行されたかを示す指標。 100% になることはまずない

 

  • 行カバレッジ
    • テストで実行された行の割合
  • C0カバレッジ or ステートメントカバレッジ
    • 実行可能命令のうち、テストで実行されたものの割合
  • C1カバレッジ or 条件カバレッジ
    • 判定条件の組み合わせのうち、テストで実行されたものの割合
    • if 文が 2 つあるなら組わせの数は 2*2 = 4 通り

テスト駆動開発をしてみよう

テスト駆動開発

  • XP (eXtreme Programming) のプラクティスのひとつ
    • 1999, Kent Beck et. al.
  • テスト手法でなく設計手法
  • 〇〇駆動開発のはしり
  • 2000 年代あたりすごい流行った
    • テストを先に書かずは開発者に非ずぐらい

テスト駆動開発デモ

  • FizzBuzz をテスト駆動開発でやってみる

テスト駆動開発

  • テストと実装のフィードバックループ
    • テストが設計をガイドするということでテスト駆動開発
  • 要点
    • テストを先に書く
    • テストの結果をコードにフィードバックする
    • コードの状態をテストにフィードバックする

テスト駆動開発の効果は?

TDDを実施した場合に、コーディング(実装)の時間が16%増えた。

 

 TDDを実施した場合に機能テスト(ブラックボックス)で不具合を検出するテーストケース数が削減された(不具合を検出したテストケースが18%減少)。 

 

TDDを実施した場合、テストのカバレッジが大きくなった。

 

(http://blogs.itmedia.co.jp/morisaki/2010/03/tdd---3-5b4a.html から引用)

テスト駆動開発は流行ってないのか?

  • 「マニアが殺した」
    • 「テストファースト」の部分だけを切り取った誤解
    • 「実際のコード < テストしやすさ」行き過ぎた偏見
  • TDD is dead というつもりはない
    • 開発者向けのテスト本には大抵書かれている

現在のテスト駆動開発

  • TDD から BDD へ
    • 振る舞い駆動開発 (Behavior Driven Development)
    • TDD の構造は変えずに、より誤解を与えない形式に
  • 〇〇駆動開発という形で色々現在の開発に影響
    • 誤用もかなりされている
      • (ex: 締め切り駆動開発, 

今から TDD を学習したほうがいいか?

YES

  • TDD はスキル、やれば身につく
    • 疎結合な書き方
      • → Dependency Injection、DDD などの開発手法
    • 副作用を出さない書き方
      • → Reactive Programming、関数型プログラミング
    • テストコードの書き方
      • 最小のテストを書く習慣

 

TDD 本の写経をしてみるのもおすすめ

今でも TDD を実践したほうがいいか

状況による

  • TDD は自転車の補助輪
    • TDD を身につけると、TDD しなくてもよい設計とよいテストコードが書けるようになる
  • そもそもプロジェクトがテストを簡単にできるようになっていないと実践は難しい

 

とはいえ、

  • 複雑な問題に立ち向かう場合にはスモールステップな開発方法が役に立つ
  • どこから手をつけていいかもわからない場合は、最小のテストコードを書いて実験してみると良い

参考資料

資料

テスト関連でフォローするとよいアカウント

  • テスト駆動開発
    • @KentBeck - テスト駆動開発の著者
    • @t_wada - テスト駆動開発の翻訳者
  • ソフトウェアテスト
    • @akiyama924 - ソフトウェアテスト技法ドリルの著者

 

ココらへんの人から芋づる式に辿れるはず

テストに関するブログ

  •  Google Testing Blog
    • https://testing.googleblog.com/

国内のテストに関するカンファレンス・イベント

  • JaSST ソフトウェアテストシンポジウム
    • ソフトウェアテスト技術に関心がある人を対象にしたカンファレンス
  • WACATEワークショップ
    • 若手でテストに興味がある人を対象にしたワークショップ

 

紹介はしたが、テストエンジニア向け。開発者にはちょっとむいていないかも

次回の候補

  • アルゴリズム

  • データ構造

  • ネットワーク

  • 低レイヤ
  • RDBMS, NoSQL
  • オブジェクト指向プログラミング
  • 関数型プログラミング
  • 継続的インテグレーション
  • 並行・並列処理
  • 仮想化・コンテナ
  • 言語処理系
  • 量子コンピューティング

おわり

ソフトウェアテストの基礎

By Shingo Suzuki

ソフトウェアテストの基礎

  • 1,386