VSTePを使ってみよう！実践テストフレーム

ソフトウェアテストAdventCalendar2016 3日目担当の@ToshiManaPlus1です。
2日目は@mhlycさんによる[テスト設計コンテストに参加してみての感想を率直に書きます。](http://mhlyc.hatenablog.com/entry/2016/12/02/234141)でした。
VSTePのテストフレームを実際に使ってみた感想を書いていきます。

# はじめに
JaSST東北実行委員をやっています。JaSST'16東北ではソフトウェアテスト開発技法であるVSTePをテーマにワークを行いました。それ以来VSTePが大好きになったのですが、VSTePの構成要素の一つであるテストフレームを使ってみたらテストケースが良くなったので、その経験をもとにテストフレームの導入について書きたいと思います。テストフレームからテスト実装については基本自己流ですので、ご注意ください。

# VSTeP 
VSTePとは電気通信大学の西 康晴氏が考案したソフトウェアテスト開発技法です。VSTePではテストエンジニアにより意見交換を活発に行うことにより、チーム全員が納得いくモデリングを行うことを良しとしています。VSTePには大きく３つの技法があります。

- テスト観点図

VSTePにおけるテスト要求分析にあたります。テスト観点(テストの関心事)を洗い出し、ツリー上に整理したものがテスト観点図になります。

- テストコンテナ

VSTePにおけるテストアーキテクチャ設計にあたります。テストレベル、テストタイプ、テストサイクルをまとめて俯瞰的に全体を把握できるようにします。

- テストフレーム(今回の対象)

VSTePにおけるテストアーキテクチャ設計ですが、テストコンテナよりもテストケースに近いです。テストケース作成に必要なテスト観点をまとめてテストのスケルトンを作成します。

VSTePについての詳細はこちらを参照してください。
http://qualab.jp/vstep/

# テストフレームからテストケース実装の進め方

単機能テストを想定して、今回のテストフレーム作成からテストケース実装までの流れを記載します。

- テスト観点を洗い出す

テスト対象のテスト観点を洗い出します。事前にテスト観点を洗い出していればそれを使いますが、今回はテストフレームから始めますので、思いつくキーワードを洗い出すだけでも良いと思います。足らなければ後から追加しても良いでしょう。

- テストフレームの作成

複数のテスト観点でテストケースを作る要素の組み合わせを表現します。「何をテストしたいのか」を軸にテスト観点をまとめておきます。「何をテストしたいのか」が複数あればテストフレームを複数作ります。その際、同じテスト観点が何度出てきてもかまいません。また、テストフレームごとにテスト観点を「テスト条件」「テスト対象」「振る舞い」に分類します。「テスト条件」はテストケースにおけるインプットにあたり、組み合わせを試したいものになります。「テスト対象」はテストケースにおけるインプットにあたり、そのテストケースでは値が変化しないものになります。「振る舞い」はテストケースにおけるアウトプットにあたり、テストケースで確認したいことになります。

- 抽象テストケースの作成

テストフレームにおけるテスト観点の言葉を用いて、抽象的なテストケースを作成します。テスト観点の言葉がテストケースの値の粒度に合わなければ、値の粒度になるようにテスト観点を分解すると良いでしょう。

- パラメータの検討

抽象的なテストケースの中から「テスト条件」および「振る舞い」に対して、値の組み合わせを検討します。「テスト対象」は適切な固定の値を用います。

- テスト実装

抽象テストケースおよび値の組み合わせを使ってテストケースを実装します。

# テストフレームの嬉しいこと

VSTePの導入は上記すべてを一度に導入するよりも、できるところから順に導入することが推奨されています。今回はテストフレームから着手してみました。私がテストフレームを試した理由はテストケース生成に最も近く、現場に導入しやすいと考えたからです。

テストケースの管理の仕方はそれぞれだと思います。いわゆる「コピー＆ペースト＆モディファイ法」と呼ばれる、他のテストケースをコピーして値だけを変えるテストケース生成法が使われているところも多いのではないでしょうか。この手法は、テストケースを量産しやすそうに見えるのですが、文章量が多くなりやすく、テストの過不足が判断しにくい傾向にあります。

![excel.png](https://qiita-image-store.s3.amazonaws.com/0/67356/c918a924-fdf1-7622-7538-6def1c6a81ae.png)
図：コピー＆ペースト＆モディファイ法によるテストケース例

今回のテストフレームを用いた手法により、(個人的には)スッキリしたテストケースが作成できます。以下に実際にテストフレームを試してみて、感じたことをまとめてみます。

## 説明しやすい

テストフレームを作るため、「どのようなことをテストしたいのか」を整理します。出てきたテストケースが何をテストしたいのかを毎回明確に意識することにより、目的のないテストケースが出にくくなります。

## 管理しやすい

抽象的なテストケースと値の組み合わせでテストを管理できるため、個別にテストケースを作るのに比べて、余計な文章量を減らすことができ、値の組み合わせに注力することができます。余計な情報が少なくなることで、値の過不足に気付きやすくなることでテストの質を向上させることに繋がります。

## 自動化しやすい

テストフレームの後に作成する抽象テストケースは多くの値の組み合わせをカバーできるテストケースになります。共通化されたテストケースということで、自動化対象としても効果が高く、個別に作ったテストケースと比べると再利用性が高くなります。

# 実践テストフレーム

簡単な「2点間の距離を求める関数」の単機能テストをテストフレームを使って検討します。
2点の座標と、距離の種類(L1距離(マンハッタン距離)とL2距離(ユークリッド距離))を引数にとって、2点間の距離を返します。
C++を使って実装し、単体テストフレームワークとして「google Test」を使用します。
以下にテスト対象のソースコードを書きます。(ヘッダのみ)

```cpp:distance.h
#pragma once

#include <cstdint>
#include <utility>

using Point = std::pair<int32_t, int32_t>; // 座標型

enum class DistanceType
{
	L1,
	L2,
};

// 座標型の値生成関数
static Point point_(int32_t x, int32_t y) 
{
	return std::make_pair(x, y); 
}

// 2点間の距離を求める関数
double distance(const Point& p1, const Point& p2, DistanceType type);
```

## テスト観点の洗い出し

今回のテストの関心事を洗い出して、ツリー上に整理します。今回のような簡単な関数のテストだと入出力の洗い出しは簡単ですが、この時点で「どういうところをテストしたいか」を整理しておくと良いでしょう。テスト観点図には正解はないので、納得できるテスト観点図が出せると良いでしょう。

![２点間の距離を求める関数.png](https://qiita-image-store.s3.amazonaws.com/0/67356/b876bd26-68ee-80fc-c024-5653b736ddd6.png)
図：簡易版テスト観点図

## テストフレームの作成

テスト観点の組み合わせでテストフレームを作成します。今回はテスト観点で洗い出した「どういうところをテストしたいのか」ごとにテストフレームを出していきます。テスト観点ごとに「テスト対象」、「テスト条件」、「振る舞い」のどれに当たるのかを整理します。「テスト対象」は値を固定したいもの、「テスト条件」は値を変えて試したいもの、「振る舞い」は結果を確認するものにあたります。

![frame.png](https://qiita-image-store.s3.amazonaws.com/0/67356/4390ab64-220e-b969-fd58-d460b9279db9.png)
図：テストフレームの例

## 抽象テストケースの作成

テスト観点の言葉を使って抽象的なテストケースを作成します。抽象的なテストケースが妥当かどうかは、具体的な値を入れて違和感がないかを確認するとよいでしょう。今回は簡単ですが、言葉の粒度が合わない場合があります。必要に応じてテスト観点を細分化して、テストケースに見合う言葉の粒度で抽象テストケースを作りましょう。

## パラメータの検討

テストフレームごとに値の組み合わせを検討します。「どういうところをテストしたいのか」に合わせて、値の組み合わせを検討します。必要に応じて同値分割や境界値分析などを利用して、効率良くテストできる組み合わせを検討しましょう。

![テストケース例.png](https://qiita-image-store.s3.amazonaws.com/0/67356/b6724d4c-734a-6941-d412-02d7b5d2f4ac.png)
図：テストケース例(値の組み合わせは不十分です。しっかり検討しましょう。)

## テスト実装

得られた抽象テストケース、パラメータを元にテストスクリプトを作成します。単体テストフレームワークでパラメタライズドテストに対応しているのであれば、それを使って効率良くテスト実装することができます。ここで抽象テストケースとテスト本体が対応付いていれば後で見返したときに把握がしやすくなり、管理が容易になります。

```cpp:distanceTest.cpp
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include "distance.h"

// Param = (distance, x1, y1, x2, y2, type)
using Param = std::tuple<double, int32_t, int32_t, int32_t, int32_t, DistanceType>;

class DistanceTest : public ::testing::TestWithParam<Param>
{
};

TEST_P(DistanceTest, TestCase)
{
	auto         p      = GetParam();
	double       expect = std::get<0>(p);
	Point        p1     = point_(std::get<1>(p), std::get<2>(p));
	Point        p2     = point_(std::get<3>(p), std::get<4>(p));
	DistanceType type   = std::get<5>(p);

	double actual = distance(p1, p2, type);
	
	ASSERT_THAT(actual, ::testing::DoubleEq(expect));
}

INSTANTIATE_TEST_CASE_P(
	2点間のL1距離を求められる,
	DistanceTest,
	::testing::Values
	( std::make_tuple( 0.0,  0,  0,  0, 0, DistanceType::L1)
	, std::make_tuple(25.0, 15,  0, -2, 8, DistanceType::L1)
	, std::make_tuple(23.0, -3, -7,  9, 4, DistanceType::L1)
));
```

![Result.png](https://qiita-image-store.s3.amazonaws.com/0/67356/1fd9b46b-8c18-1c14-80c6-167b6012d40b.png)
図：実行結果

# おわりに

テストケースをコピー＆ペーストで作っているのであれば、テストフレームを導入することで、説明しやすい、管理しやすい、自動化しやすいテストを作れるようになります。今回はテストケースは少ししか例を挙げていませんが、テストケースの量が多くなるほど効果を発揮します。テストケースを上手に作れるようになれば、より上流のテスト分析、テスト設計に意識を割けるようになるので、そこからVSTePのテスト観点図、テストコンテナなどを試してみたらいかがでしょうか。


次回、4日目は@goyokiさんの[（C++）constexpr & static_assertによるコンパイル時テストの用途](http://goyoki.hatenablog.com/entry/2016/12/04/224833)になります。