設計向けとテスト設計向けの適切な仕様表現の差異(デシジョンテーブルを例として)

設計向けとテスト設計向けでは、適切な仕様表現について認識に差異が出ることがあります。

題材として、以下の仕様をデシジョンテーブルで表現する場合を考えます:

題材とする仕様

  • 組み込み機器の単軸制御モード機能が対象

  • この機能は次の3つの入力を持つ
    • 省電力化モード(とり得る値:ON、OFF)
    • 制御方針(とり得る値:精密重視、速度重視)
    • 目標との距離(とり得る値:整数値[cm])

  • この機能は次の出力を持つ
    • 速度モード(とり得る値:高速モード、中速モード、低速モード)

  • この機能は出力の速度モードを次のように決定する
    • 省電力化モードがONの場合、低速モードで固定する
    • 省電力化モードがOFFの場合、以下に従う
      • 目標との距離が10cm未満の場合、低速モードを選択する
      • 目標との距離が10cm以上かつ、制御方針が精密重視なら、中速モードを選択する
      • 目標との距離が10cm以上かつ、制御方針が速度重視なら、高速モードを選択する

 

設計向けのデシジョンテーブルの例

設計では、実装の方針がわかる前提下ならば、デシジョンテーブルを簡単化しても支障はありません。むしろ必要十分な範囲内で情報を簡単化した方が、都合が良い場合が多いです。

今回の仕様では、以下のようにデシジョンテーブルを表記できます。

f:id:goyoki:20200628225659p:plain

 

テスト設計向けのデシジョンテーブルの例

一方、テスト設計向けに、デシジョンテーブル法に基づいてテーブルを作る場合、上記のようにデシジョンテーブルを簡単化できるとは限りません。どこまで簡単化できるかが、処理順序や品質リスクに依存するためです。

まず以下のような実装が行われている場合について考えます。

# 速度モード設定処理()にはモードに応じた障害のリスクがあるとする

def 単軸制御モード機能(省電力化, 制御方針, 目標との距離):
    if 省電力化 == ON:
        速度モード設定処理(低速モード) #1
        return

    if 目標との距離 < 10:
        速度モード設定処理(低速モード) #2
        return
    
    if 制御方針 == 精密重視:
        速度モード設定処理(中速モード) #3
        return
    else:
        速度モード設定処理(高速モード) #4
        return

この場合、シンプルな分岐構造で仕様が実装されているため、前述の簡単化デシジョンテーブルを使っても問題ありません。テスト技法に則り、デシジョンテーブルの各列をテストケースに展開すれば、すべての速度モード設定処理()を網羅するテストケースを用意できます。

一方で、次のような実装が行われている場合について考えます。

# 速度モード設定処理()にはモードに応じた障害のリスクがあるとする

def 単軸制御モード機能2(省電力化, 制御方針, 目標との距離):
    if 制御方針 == 精密重視:
        if 省電力化 == ON:
            速度モード設定処理(低速モード) #1
        elif 目標との距離 < 10:
            速度モード設定処理(低速モード) #2
        else:
            速度モード設定処理(中速モード) #3
        return
    else:
        if 省電力化 == ON:
            速度モード設定処理(低速モード) #4
        elif 目標との距離 < 10:
            速度モード設定処理(低速モード) #5
        else:
            速度モード設定処理(高速モード) #6
        return

この実装の場合、前述の簡単化したデシジョンテーブルに従ってテストケースを導出すると、一部の速度モード設定処理を網羅できず品質リスクが残ります。

この実装に基づいて適切にデシジョンテーブルを簡単化するならば、一例として以下のようなものになるでしょう。

f:id:goyoki:20200628225724p:plain

また機能の処理が複雑なコードで分散実装されていたり、見えにくい品質リスクが散在していたりする場合だと、実装から順序性や品質リスクが読み取れなくなる場合があります。そうなると、デシジョンテーブルは全く簡単化できない場面もあり得ます。

すなわち、設計と同じように簡単化できない場合があるということです。

なおこの話の参考文献として、 ソフトウェアテスト技法練習帳 に解説があり、お勧めできます。

 

設計向けとテスト設計向けの適切な仕様表現の差異

テストの導出元にするデシジョンテーブルをどこまで簡単化できるかは、順序性、広く言えば品質リスクに依存します。そしてテスト設計ではより包括的に品質リスクに対応する必要があることから、設計のための仕様より、簡単化できない場面が多いです。言い換えると、テスト設計では、設計と比べ、より広い視点で、情報が補強された仕様表現が求められる場面が多いです。

なお「より広い視点で、情報が補強された仕様表現が求められる」といっても、現実でそれを十分に実現できる場面は多くありません。テスト用に仕様を作り直すコストはないのが普通ですし、そもそも品質リスクは広く・見えにくく潜在していて、仕様化ですべて対応できないためです。その現実に立ち向かうため、以下のような活動が重要になってくると感じます。

  • 設計や実装でのリスクコントロールの工夫。少ないテストでも大丈夫なように作る。
  • 探索的テストによる補完。仕様の情報不足を、テストエンジニアの能力や学習結果で補完する。
  • グレーボックステストの拡充。設計やコードの構造に基づいてテスト設計の穴を補強する。
  • 無則のテストの導入。ペアワイズ法などで仕様化されていない条件もテストする。
  • 品質保証の重ね合わせ。コードレビュー、ユニットテストシステムテスト、専任チームによる品質保証などを組み合わせて、品質リスクの見落としを減らす。

UTP2(UML Testing Profile 2)でテスト設計の成果物を一通り表現する

 UMLのプロファイル(特定用途向けに、ステレオタイプ、タグ付き値、モデル要素の制約・関係性の拡張をくわえたもの)の中に、ソフトウェアテストのためのプロファイルUTP2(UML Testing Profile 2)があります。

 UTP2は、テストでモデルベースドアプローチを実現して、ソフトウェアテストの各種活動でモデリングの恩恵を受けることを目的としています。恩恵とは、例えばテスト分析・設計での思考をモデルで補助する、トレーサビリティを明示化する、テスト成果物のレビューなどの協業を支える、モデル駆動などのツールの恩恵を得る、といったものです。
 UTP2の内容は、テスト環境とテスト実行の仕様定義が多めとなっています。今後普及するかは、使い勝手の良いツールが出てくるかに依存していると思います。

 今回はUTP2の紹介として、テスト設計の一通りの活動の成果物を、UTP2で表現していきたいと思います。

前置き

 今回は以下の2.1betaに基づいてテスト設計を行います。

UML Testing Profile 2.1beta
https://www.omg.org/spec/UTP2/About-UTP2/

 なおUTP2は仕様が大きく、使用する際はテーラリングした上での部分採用の形をとることになります(実際、UTP2仕様書中のExampleは、それぞれでかなり仕様を絞り込んでいて表現がばらばらになっています)。今回の例示もUTP2の一部の仕様のみを使用しています。

題材

 今回は医療用デジタル体温計のプロダクトシステムを対象とします。

UTP2に基づいたテストの活動

テストコンテキストの定義

 UTP2ではまずテストの活動をテストコンテキストの集まりで表現します(注:ここでいうテストコンテキストは、UTP2独特の定義づけがされた言葉です)。

 テストコンテキストは、以降のテストの活動が行いやすいように、テストレベルやテストタイプなどを基準に、テストの活動を分けたものです。テストコンテキストは、テストの成果物を含む、テストの活動にかかわる情報の集合として表現されます。

 比喩を使うと、例えばシステムを開発する際は、システムをサブシステムに分割し、サブシステムごとに分かれて開発を進めます。そこでいうシステムとサブシステムの関係が、テスト活動全体とテストコンテキストの関係に当てはまります。

 テストコンテキストはTestContextをステレオタイプにもつパッケージと定義されており、パッケージ図でモデリングします。

f:id:goyoki:20200426183923p:plain

 図中のとおり、テストコンテキストはテストタイプとテストレベルをパラメータとして持っています。
 今回はこのうち、システムテストの機能正確性テストに属する、温度計測精度テストに絞ってテスト設計を進めます。

 なおテストコンテキストを定義するためには、その前にテストレベルやテストタイプの分析・計画が必要です。UTP2ではテストタイプとテストレベルをValueSpecificationと定義しており、分析はクラス図あるいはオブジェクト図で行うことになります。ただそれらテストコンテキスト定義以前の活動については、UTP2でほとんど扱われていないため今回は省きます。

テスト目的とテスト要求の分析

 次にテストコンテキストごとにテスト分析・設計を進めていきます。

 まず、テストコンテキストのテスト目的を、以降のテスト分析ができるようになるまで具体化します。テスト目的はTestObjectiveのステレオタイプを付与したクラスと定義されており、クラス図でモデリングしていきます。

f:id:goyoki:20200426183941p:plain

 テスト目的を具体化したら、その目的に基づいてテスト要求を定義します。テスト要求は、テストしたいと思えるテスト対象の特質です。テスト目的を満たすように、テスト対象の要求を参照しながら定義します。テスト要求はTestReqirementのステレオタイプを付与したクラスと定義されており、クラス図でモデリングしていきます。

f:id:goyoki:20200426183946p:plain

テスト設計

 次に、テスト要求とテスト目的からテスト設計方針を立て、その方針に基づいてテストケースを作成します。モデルにおいては、テスト設計方針にはTestDesignDirective、テストケースにはTestCaseのステレオタイプを付与して表現します。
 テスト要求・テスト設計方針・テストケースの関係性については、2.1beta時点のUTP2仕様書では仕様定義と文章による解説が中心で、一貫性を持った例示を行っていません。ただ以下のようなモデルで表現できると思います。

f:id:goyoki:20200426183928p:plain

 なお上記ではテスト要求との関係を表現するためクラス図を用いていますが、TestDesignDirectiveはインスタンスと定義されており、それ単体でモデリングする際はオブジェクト図を用いることになります。

 別の活用例として、SysMLのようにテスト要求モデルにテストケースを関連付けて、両者のトレーサビリティをモデルで表現する方法も提示されています。またテスト設計方針や、テストケースの集まりであるテストセットは、オブジェクト図やパッケージ図で構造化しながら設計を進めることもできます。

テスト環境の設計

 一方、テストを実行するためのテスト環境をモデリングします。テスト環境はTest Configurationとして表現されます。テスト対象にTestItem、テスト環境のコンポーネントにTestComponentのステレオタイプを付与して、クラス図やコンポジット構造図でモデリングします。

f:id:goyoki:20200426183919p:plain

テスト実装

 そしてテストケースごとにテストケースの手順を作成します。
 手順はTestCaseのステレオタイプを付与したふるまいモデルで表現します。今回は仕様書の例示に従ってシーケンス図でモデリングしています。

f:id:goyoki:20200426183937p:plain

Prometheusでファイルサービスディスカバリを使って監視対象を動的に変える

監視対象が動的に増減するような状況においてPrometheusで監視を行う場合、サービスティスカバリ機能が便利です。今回はその例として、Prometheusのファイルサービスディスカバリで監視対象を動的に変更する方法についてまとめます。

動作環境の構築

まずdocker-composeで最低限の環境を構築します。
次のディレクトリ構成を用意します。

docker-compose.yaml 
prometheus/
  prometheus.yaml
  targets.json

docker-compose.yamlは次の通りです。prometheusとnode-exporterのdockerコンテナを用意します。

version: '3'
services:
  prometheus:
    image: prom/prometheus
    container_name: prometheus
    volumes:
      - ./prometheus:/etc/prometheus
    command: "--config.file=/etc/prometheus/prometheus.yaml"
    ports:
      - 9090:9090
    restart: always
  exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    ports:
      - 9100:9100
    restart: always

prometheus.yamlは次の通りです。
ファイルサービスディスカバリを使用する場合、scrape_configsにfile_sd_configsを指定し、その中のfilesにglob形式でターゲットファイルへのパスを記入します(今回は*.json。このyamlファイルからの相対パスで記述)

global:
  scrape_interval: 15s
scrape_configs:
  - job_name: 'node'
    file_sd_configs:
      - files:
        - '*.json'

すると、prometheusはglob形式で指定したファイルを探し出して監視対象に加えてくれるようになります。

targets.jsonは次の通りです。

[
  {
    "labels": {
      "job": "node"
    },
    "targets": [
      "node-exporter:9100"
    ]
  }
]

prometheusで最初の状態を確認する

「docker-compose up -d」でprometheusとnode-exporterを立ち上げます。
「http://【実行環境のアドレス】:9090/graph」にアクセスし、Expressionに「up{job="node"}」を入力してExecuteします。すると以下の結果が出力されます。

Element	Value
up{instance="node-exporter:9100",job="node"}	1

targets.jsonで指定した監視対象がTargetに加えられていることがわかります。

ターゲットファイルを追加して監視対象を変更する

次にprometheusを実行したまま監視対象を変えてみます。
targets.jsonと同じディレクトリに次のtargets2.jsonを追加します。

[
  {
    "labels": {
      "job": "node"
    },
    "targets": [
      "prometheus:9090"
    ]
  }
]

再度prometheusのgraphページにアクセスし「up{job="node"}」をExecuteします。すると以下の結果が出力されます。

Element	Value
up{instance="node-exporter:9100",job="node"}	1
up{instance="prometheus:9090",job="node"}	1

targets2.jsonでの追加分が、監視対象に反映されていることがわかります。


Github Actionsでテストや静的解析に失敗するプルリクエストをマージできないようにする

Github Actionsを活用するとGithub内でCIを完結できます。
その活用例として、今回はプリリクエスト作成時にユニットテストと静的解析を実行し、それらが成功しないとプルリクエストのマージを拒絶する仕組みをGithub Actionsで作ります。

対象コード

対象ソースコードは以下です。

https://github.com/hiro-iseri/blog_demo

プロダクトコードをdemoディレクトリに、ユニットテストをtestsディレクトリに格納する構成をとります。

python-package.ymlの作成

/.github/workflows/ディレクトリにpython-package.ymlを作成し、Github Actionsの設定を記述します。
今回の設定内容ですが、Python3.8環境にて、プロダクトコードに対し、flake8で静的解析、標準unittestでtestsディレクトリのユニットテストを実行します。
これをプルリクエストの作成時、更新時に実行するように設定します。
以下記述例です。

name: demo

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  build:

    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: [3.8]
    steps:
    - uses: actions/checkout@v2
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v1
      with:
        python-version: ${{ matrix.python-version }}
    - name: Lint
      run: |
        pip install flake8
        flake8 demo --show-source
    - name: Test
      run:
        python -m unittest discover tests

プルリクエスト画面で静的解析・ユニットテスト結果を表示する

上記yamlをpushすると、プルリクエストの作成・更新時に、Github Actionsが実行されるようになります。
そしてプルリクエストのマージ画面で次のように静的解析・ユニットテスト結果が付記されるようになります。

f:id:goyoki:20200419195436p:plain
no_block

静的解析・ユニットテスト失敗時にプルリクエストのマージをブロックする

次に、masterブランチへのプルリクエストを対象に、静的解析・ユニットテスト失敗時のブロックを有効化します。
設定方法ですが、GithubプロジェクトのSettingsタブ→Branchesメニューを選択します。
Branch name patternの対象をmasterとし、「Require status checks to pass before merging」「Require branches to be up to date before merging」を有効化して、前述のyamlで定義したbuildにチェックを入れます。

f:id:goyoki:20200419195737p:plain

この設定を有効化すると、masterに対するプルリクエストについて、静的解析・ユニットテストが失敗するものはマージがブロックされるようになります。

f:id:goyoki:20200419200012p:plain

Nスイッチカバレッジ計測ツールを公開

状態遷移テストの補助用に、Nスイッチカバレッジを計測するツールnswitchcov_aを公開しました。

https://github.com/hiro-iseri/nswitchcov_a

使い方の例

CUIツールです。状態遷移定義ファイルと実行フローファイルの2つを入力に実行します。

具体例として、以下の状態遷移を扱います:

f:id:goyoki:20200210002948p:plain

この状態遷移を、以下のテストケースで網羅します:

  • テストケース1 :初期状態→状態1→状態4
  • テストケース2 :初期状態→状態1→状態2→状態3→初期状態
  • テストケース3 :初期状態→状態1→状態3

このケースの2スイッチカバレッジを計測します。

作成するファイル

状態遷移定義ファイル(stateflow.txt)は例えば以下のようになります

初期状態 - イベント1 > 状態1
状態1 - イベント2 > 状態2
状態1 - イベント4 > 状態3
状態1 - イベント5 > 状態4
状態2 - イベント3 > 状態3
状態3 - イベント5 > 初期状態

実行フローファイル(exepath.txt)は例えば以下のようになります

#テストケース1
初期状態 - イベント1 > 状態1 - イベント5 > 状態4
#テストケース2
初期状態 - イベント1 > 状態1 - イベント2 > 状態2 - イベント3 > 状態3 - イベント5 > 初期状態
#テストケース3
初期状態 - イベント1 > 状態1 - イベント4 > 状態3

これらを入力としてコマンドラインで実行すると、2スイッチカバレッジ25.00%の結果が得られます。

nswitchcov_a.exe --exepath=exepath.txt --stateflow=stateflow.txt --n=2
nswitchcov_a(ver:1.01)
number of execution path:3
number of n-switch path:8
n-switch coverage:25.00%(2/8)

組み込み開発での品質保証、テスト、CD/CIの未来についてのイベント開催

直前ですが、「Tomorrow’s Software Testing for Embedded Systems」というイベントを開催します。

10月23日 Tomorrow’s Software Testing for Embedded Systems(東京都)

電通大にしさんの同名の講演の再演になります。組み込み開発における品質保証、CI/CD、DevOps、モデルベース開発について講演いただくほか、組み込みの品質保証について深く語る場となります。内容にご興味ある方はぜひご参加いただけると嬉しいです。

TDDはゆるく実践しても大丈夫

 最近、TDDのテストコードは捨てても良いかみたいな議論を見ました。
 これに対する自分個人の経験上の意見ですが、TDDは雑多にテストコードを使い捨てても効果を出せると思います。

 もちろん、TDDで保守性が高く価値あるテストを書いて、捨てすにCIや中長期的なリファクタリングで再利用していくと、TDDの効果を増幅できます。ただ、それをするにはスキルや事前の工夫、労力が必要ですし、できる場面に限りがあります。
 そういったことをやらず、もっとゆるい姿勢で取り組んでも、費用対効果をプラスにできる手法がTDDだと考えています。

 今回は、そのTDDでゆるくしてもよいポイントを、実経験からまとめたいと思います。

TDDのテストは使い捨てでいい

 TDDのテストはプログラマのこまごまな課題に応じて累積的に作られるため、保守コストがかかるテスト・保守する価値の低いテストが生まれがちです。そのためテストの使い捨ての発生はむしろ自然な流れです。
 またTDDのテストはプログラマの不安を解消するといった軽い動機付けで作ってよいので、ゆるい動機でテストを書いて、ゆるい判断でテストを捨てていくスタイルでも、TDDは効果を出せます。

【発展的内容】もちろんTDDで再利用し続けられるテストを増やして、CIなどに組み込んでいけると、TDDの費用対効果を増幅できます。ただそれをするにはテストの保守性確保(テストコードの再整理等)、有効なテストを作るテスト設計スキル、テスタビリティの確保の労力・スキルが必要です。さらに適用できる場面にも限りがあります。そういったものは、個人でTDDをうまく回せるようになった次のステップから手掛けてみても大丈夫だと思います。またそれができるのは一部のみという認識も大事です。

TDDのテストの網羅性は気軽に主観で決めていい

 TDDでのテストの十分性・網羅性は、主観でざっくり判断してよいです。例えばプログラミング中に不安を感じたら、主観で不安がなくなったと感じるまでテストをかけばそれで十分、といった感じです。体系的なテスト手法やテスト技法で、ロジカルにテスト設計しなくても効果を出せます。
 逆になれない技法で厳格なテスト設計を行おうとすると、スピードが落ちて、TDDの費用対効果の費用が悪化しがちです。

【発展的内容】もちろん はじめて学ぶソフトウェアのテスト技法 で挙げられているような、体系的なテスト手法やテスト技法を身につけて、有効なテストを軽快に確保できるようになると、TDDでも効果的なテストコードがかけるようになります。ただこれもTDDを回せるようになった次のステップで、逐次身につければよいと思います。

TDDの適用は一部だけでいい

 あえていうまでもないかもしれませんが、TDDはコードの全てに適用できません。自動テストのテスタビリティを確保できる箇所のみで効果を発揮できます。テスタビリティを確保できない箇所でTDDを行うと失敗するか、費用対効果がマイナスになる可能性が高いです。
 そのためTDDがやりにくいと感じたらTDDをやめても大丈夫です。プログラミングの2割ぐらいしかTDDができなくても、その2割で効果を出せれば御の字です。

【発展的内容】なおTDDの適用範囲を広げるためには、アーキテクチャレベルでの自動テストのテスタビリティの作り込みが必要です(ユニットレベルのテスタビリティはTDDで確保できます。それだけでは解決できない課題に対応するため、アーキテクチャレベルの工夫が必要になります)。これもTDDをまわせるようになった次のステップで挑戦するとよいと思います。

テストはあとから書いてもいい

 テスト駆動・TDDとは呼べませんが、テストはあとから書いても大丈夫です。
 というのも、TDD実践のとても重要な前提に、「自分の手足のように軽快にユニットテストを活用して、プログラミングを効率化する習慣の実現」があるためです。例えば、仕様に迷ったらテストファーストでテストをサッと書く、書いたコードに不安を感じたらテストをさっと継ぎ足す、のような状態です。
 テストファーストであれ後付けであれ、とにかく気軽にユニットテストを活用する習慣を続ければ、その前提が満たされ、TDDを実践できるタイミングが一気に増えます。
 なのであとからテストを書くのに気後れは不要です。気軽に・軽快にユニットテストをプログラミングに活用することが大事です。

ユニットへのテストでなくてもいい

 パッケージや、アーキテクチャレベルのレイヤといった大きな粒度の方が、有効・堅牢な自動テストを書きやすい場面は少なくありません。そういう場合は、大きな粒度のテストでテスト駆動開発を行っても大丈夫です(いわゆる 実践テスト駆動開発 のOutside-In TDDやTDDのダブルループはその代表例です)。「TDDのテストはユニット(コードの最小単位)ごとに書かなければならない」という原理主義に拘る必要はなく、書きやすい粒度のテストで駆動させるのが費用対効果改善に有効です。

【発展的内容】これをやりやすくするためには、アーキテクチャレベルでのパッケージやレイヤ、バウンダリの粒度で、堅牢で自動テストを書きやすいインターフェースを確保するのが重要です。そこで確保したインターフェースに対する自動テストでテスト駆動を行うことで、TDDのテストの堅牢性、有効性を高められます。そうすればTDDのテストをCIなどで再利用できる場面が増えます。

TDDはチーム全体でやらなくてもいい

 TDDは一人で実践しても費用対効果をプラスにできます。

 プログラミングをしていれば、コードの仕様を検討したくなったり、不具合のリスクを感じたり、リファクタリングしたくなったりする場面に数多く遭遇すると思います。それらを個人レベルで気軽に解決するのがTDDです。

 逆に個人では費用対効果がマイナスで、チーム全体でTDDしないと費用対効果を出せない、みたいな状況だと、TDDのやり方に問題がある可能性が高いです(これはチーム全体のスピード不足か、テスタビリティ実装スキル不足が原因であることが多いです。あるいは前述したゆるさを許さず完璧なTDD実践をしようとしているかもしれません)。

【発展的内容】ただテストをCIなどで継続的に利用するためには、チームでの推進が重要です。その実現には技術リードや開発インフラ整備の主導が求められます。これもTDDを個人で回せるようにしてから、次のステップで挑戦するとよいと思います。

余談:使い捨てを減らし、TDDのテストの費用対効果を高めるために

 TDDは、プログラマが個人主観で気軽にテスト書いては捨てるアプローチでも効果が出せます。
 ただし、次のステップとして、TDDで以下の実現を助ける(※)と、テストの使い捨ても減り、費用対効果をさらに高められます。

  • 堅牢なテストを作り、それを継続的に利用して、リグレッションバグ検出やリファクタリングに活用する
  • 有効なテストを作り、効率的にバグを見つけたり品質を保証したりする

※TDDで上記を直接担保するのではなく、TDDのテストを流用して上記を助ける形です。


 個人活動にフォーカスを絞った場合、この次のステップに進むためには次のスキルが求められます。

  • 自動テストのテスタビリティを確保するスキル
    • アーキテクチャ設計の工夫が求められます。テスト自動化の支障となる要素は局所化・分離する、各パッケージを切り出して自動テストできるようにする、ユニットテストを広く実行できるような設計方針を建てると言ったものです。
  • テストの保守性を作り込むスキル
    • テスト対象の変更に強くする(変更性を高める)、テストコードの結合性・凝集性を改善する、テストコードの可読性を高めて保守しやすくする、といったものです。
  • 有効なテスト設計を行うスキル。
    • 必要なテストを見極め、そのテストの要求に対して、妥当なテスト設計を行うものです。リスクベースドテストや、有効・網羅的なテストを作るための各種技法の使いこなしが求められます。

 このステップに進むためには、TDDの活動に、品質保証の戦略立て(TDDのテストを再利用してどのような責務を果たすべきかのテスト要求を明らかにする)、テストコードの改善・リファクタリング、テスト設計の改善、Outside-In TDDのような粒度の大きなテストサイクルの構築の活動の補強が求められます。

 そしてテストの再利用を行うためには、チームレベルでの推進もある程度必要です。CIや共通のテスト自動化環境を整備し、チームでテストを継続的に改善・保守していく習慣が求められてきます。