JUnit実践入門 5日目(テスト実行方法の制御)

JUnitのテスト実行方法

コマンドラインからの実行
org.junit.runner.JUnitCoreクラスを使用する。

// テストクラスSomeTestをコマンドラインから実行する場合
$java org.junit.runner.JUnitCore test.SomeTest

// コマンドラインから複数のテストクラスを実行する場合
$java org.junit.runner.JUnitCore test.SomeTest test.OtherTest

テストクラスにmainメソッドを設定している場合は通常のJavaのクラス同様に呼び出すことができる。

JUnitCoreクラスのmainメソッドが実行されると、起動引数で指定したテストクラスからTestアノテーションのついたメソッド(テストケース)が収集、実行、テスト結果の出力が一連の流れとして実行される。

テストランナー:テストクラスに定義されたテストケースの実行に関する制御を行う。(「どのように実行するか」を制御する仕組み)
テストランナーはクラスごとに設定する。テストランナーを指定するには、テストクラスにorg.junit.runnerRunWithアノテーションを付与し、値としてテストランナーとなるクラスを指定する。

@RunWith(JUnit4.class)
public class SomeTest{
}

■テストランナーの種類
・JUnit4:テストクラスの全テストケースを実行する
JUnitの標準的なテストを実行するテストランナー。明示的にテストランナーを指定しなかった場合は、このJUnit4テストランナーが使用される。
テストクラスから次の条件を満たすメソッドをテストケースとして収集し、実行する。

  • publicメソッドである
  • org.junit.Testアノテーションが付与されている
  • 戻り値がvoidで引数を持たない


・Suite:複数のテストクラスをまとめて実行する
デフォルトのJUnit4テストランナーでは1つのテストクラスが対象となる。
しかしながらテストクラスが増えていくと、パッケージ内あるいはプロジェクトに含まれる全てのテストケースのテストを行いたくなる。
このような場合はSuiteクラスを用いる。

JUnitでいくつかのテストクラスを多板流には、テストスイートクラスを作成する。
テストスイートクラスには、RunWithアノテーションでSuiteテストランナーを指定する。
テストスイートクラスは、複数のテストクラスの集合であるため、慣例として「〜Tests」のように、クラス名を複数系とする。
テストスイートクラスにはメソッドなどの実装は不要。

@RunWith(Suite.class)
@SuiteClalasses({FooTest.class, BarTest.class})
public class AllTests{
// FooTest.classとBarTest.classを束ねたテストスイートクラス
}

またテストスイートクラスでは、他のテストスイートクラスを指定することもできる。

・Enclosed:構造化したテストクラスを実行する
標準的なテストランナーの場合、テストケースはテストクラスのpublicメソッドとして定義する。
Enclosedクラスをテストランナーとして指定した場合、ネストしたクラスを定義することができる。
これにより同じ特徴を持つテストケースをまとめ構造化できる。

@RunWith(Enclosed.class)
public class ItenStockTest{
 public static class 空の場合{
  ItemStock sut;

  @before
  public void setUp() throws Exception{
   sut = new ItenStock();
  }

  @Test
  public void size_Aが0を返す() throw Exception{
   assertThat(sut.size("A"), is(0));
  }

  @Test
  public void contains_Aはfalseを返す() throws Exception{
   assertThat(sut.contains("A"), is(false));
  }
 }

public static class 商品Aを一件以上含む場合{
 // 実装は省略
 // テストクラスを複数記述することができる
 }
}

・Theories:パラメータ化したテストケースを実行する
パラメータ化テストとは、テストケースとテストデータ江尾分離し、同じテストメソッドを複数のパラメータで再利用するテクニック。
JUnitではTheoriesクラスをしてしすることで、パラメータを持つテストメソッド定義することができる。
この時、@Testの代わりに@Theoryを使用する。
テストメソッドに渡されるパラメータは、@DataPointsの付与されたstaticフィールドなどで定義する。

@RunWith(Theories.class)
public class CalcTheoriestest{
 @DataPoints
 public static int[][]value = {
  {0,0,0},
  {0,1,1},
  {1,1,1}
 };

 @Theory
 public void add(int[] values)throws Exception{
  Calc sut = new Calc();
  assertThat(sut.add(values[0], values[1], is(values[2])));
 }
}

@DataPointsに設定されるパラメータも外部ファイルを読み込むことで値をsetできるようにすると対応できる幅が広がりそう。

JUnit実践入門 4日目(テストコードの書き方について~ アサーション)

JUnitが提供するアノテーション

■@Test -テストメソッドを宣言する
Testアノテーションを付与することによって、JUnitが実行するテストメソッドを認識する。

付与される属性
・expected -例外の早出を検証するテストで、早出が期待される例外クラスを指定するために利用する。

@Test(expected = IllegalArgumentException.class){
 sut.doSomething();
}

・timeout -ユニットテストタイムアウト値を設定するために利用する。設定した時間以上の時間がかかった場合、テストは自動的に失敗するようになる。

@Test(timeout = 100L)
public void timeoutTest() throws Exception{
 sut.doSomething();
}

■@Ignore -テストの実行から除外する
原則として、ユニットテストは常に全てを実行し、成功するようにすべき。だが、都合により一時的にテストの実行を抑制したい場合に@Ignoreを付与することで対象のメソッドの実行をスキップすることができる。なお、テスト区タスにも付与でき、その場合はクラス内のメソッド全てのメソッドの実行がスキップされる。

@Ignore("未実装のためスキップ")
@Test
public void divideに4と2を与えると2を返す() throw Exception{
 //Exercise
 int actual = sut.divide(3, 4);
 //Verify
 assertThat(actual, is(7));
}

■@Before -テスト実行前に処理を行う
JUnitによるユニットテストでは、テスト対象のクラスごとにテストクラスを作成し、テストクラスに複数のテストメソッドを定義する。
その場合、メソッド間で重複した初期化コードが発生する。重複した初期化コードをメソッドに抽出し、@Beforeアノテーションを付与することによって重複を削除することができる。@Beforeアノテーションを用いたメソッドは慣習としてsetUpとする。

@Before
public void setUp() throw Exception{
 sut = new Calculator();
 sut.init();
}

※@Beforeによる初期化処理の共通化はクラス内にしか適用できないため、クラス間で共通処理を適用したい場合にはルールを利用する。

■@After -テストの実行後に処理を行う
テストケースに共通する後処理をメソッドとして抽出する。@Afterに定義された終了処理はテストの成功/失敗にかかわらすか鳴らす実行されるのでリソースの解放など「必ず行わなければならない後処理」が必要な場合に利用される。慣習としてメソッド名はtearDownとする。

@After
public void tearDown() throw Exception{
 sut.shutdown();
}

基本的なアノテーションとしては上記4点となる。

JUnitのテストパターンについて
ユニットテストは比較的パターン化しやすい。パターンを構築することで、ユニットテストを効率よく書くことができる。
下記3パターンはその一例となる。

■標準的な振る舞いを検証するテスト
ほとんどのユニットテストはこのパターンとなり、初期化/実行/検証/後処理の4つのフェーズで構成される(4フェーズテスト)
すなわちテスト対象オブジェクトや入力/検証に必要なデータを初期化し、テスト対象となるメソッドを一つだけ実行したあと、期待される結果となるかを検証する。
標準的な振る舞いを検証するテストでは、検証時に予期された値を実際の値が異なる場合にテスト失敗となる。
またテスト実行中に何らかの例外が発生した場合もテスト失敗となる。

■例外の送出を検証するテスト
メソッドの実行時に例外が送出されることを検出するテスト。expect属性に指定した例外が送出された場合成功となる。
注意点としてJUnitの例外の検証では、例外の型以外はチェックできない。したがって、例外に含まれるメッセージの検証や、想定しない箇所で同じ型の例外については検証できない。

■コンストラクタを検証するテスト
インスタンスが生成された時に、初期状態として妥当な値が設定されているかを検証する。
コンストラクタをテストする場合には、インスタンスの生成が実行フェーズに相当する。
つまり、インスタンスはSUTでもあり、実測値でもある。変数名をSUTでも良いが、instanceにすると理解しやすい。

@Test
public void インスタンス化テスト(){
 //Exercise
 Person instance = new Person("Duke");

 //Verify
 assertThat(instance.getName(), is("Duke"));
 assertThat(instance.getAge(), is(-1));
 assertThat(instance.getEmail(). is(nullValue()));
}

アサーション:実行して得られた結果が期待される結果と一致するかどうかの検証。比較検証。
■assertThat
JUnitではassertThatメソッドとMatcherAPIを利用し、アサーションを記述する。

public static <T> void assertThat(T actual, Matcher<? super T> matcher)

Matcherオブジェクトは、equalsメソッドによる比較で、実測値と期待値が等しいことを検証する。
基本的にはassertThatメソッドを用いて検証を行う。

■fail
Assertクラスのfailメソッドは、無条件にそのテストを失敗させる。
何らかの理由でテストを失敗させたい場合に利用する。

@Test
public void 何か難しいけど重要なテストケース(){
 fail("TODO テストコードを実装する");
}

Matcher APIのメソッド
https://junit.org/junit4/javadoc/4.11/org/hamcrest/CoreMatchers.html

■is
オブジェクトクラスのequalsメソッドによる比較を行う。
ユニットテストで比較検証を行う時、多くのケースはこのisで実施する。
ただし、isメソッドは型パラメータを持つメソッドであるため、nullを指定するとコンパイルエラーとなる。

assertThat(actual,is(null));

実測値がnullであることを確認する場合には下記のnullValueメソッドを利用する。

■nullValue
nullであることを検証するMatcherを返す。
単体でも利用できるが、通常はisメソッドと組み合わせて利用する

assertThat(actuals,is(nullValue()));

■not
他のMatcherの評価値を反転させるMatcherを返す。
nullValue同様に、isメソッドと組み合わせて利用する。

assertThat(actual,is(not(0)));

■notNullValue
nullでないことを比較検証するMatcherを返す。

assertThat(actual,is(notNullValue()));

// これと同等の意味
assertThat(actual,is(not(nullValue)));


■sameInstance
実測値と期待値が同一のインスタンスであるかを比較するMatcherを返す。
つまり、==演算子を用いた比較を行う。

assertThat(actual,is(sameInstance(expected)));

なお、プリミティブ型でsameInstanceメソッドを利用する場合、ボクシング変換されたラッパー型のオブジェクトに対してインスタンスの同一性を比較するため注意。

■instanceOf
実測値が期待するクラスのインスタンスと互換性のある型であるかを比較判定するMatcherを返す。
instanceof演算子を用いた比較を行い、オブジェクトの型を検証する。

assertThat(actual,is(instanceOf(serializable.class)));

JUnitMatchersのメソッド
https://junit.org/junit4/javadoc/4.12/org/junit/matchers/JUnitMatchers.html

■hasItem
リストや配列など反復可能なオブジェクト(実測値)に期待する値が含まれているかを判定するMatcherを返す。
この時、順序や他の要素は無視し、実測値のリストの中に指定した要素が含まれていることを検証する。

List<String> actual = sut.getList();
assertThat(actual,hasItem("world"));

■hasItems
リストや配列など反復可能なオブジェクト(実測値)に期待する値が、複数含まれているかを判定するMatcherを返す。
必要な要素が全て含まれているかどうかを検証したい場合に有効なMatcherである。

List<String> actual = sut.getList();
assertThat(actual,hasItems("Hello", "world"));

カスタムMatcherの作成
プロジェクト固有の検証やより柔軟な検証を行いたい、詳細なエラーメッセージをレポートしたい場合にカスタムMatcherを作成すると便利。

作成手順
org.hamcrest.Matcherインターフェースを実装したクラスとして作成する。
※ただしMatcherインターフェースを直接インプリメントすることは推奨されておらず、org.hamcrest.BaseMatcherクラスのサブクラスとする。

public abstract class BaseMatcher<T> implements Matcher<T> {

    /**
     * @see Matcher#_dont_implement_Matcher___instead_extend_BaseMatcher_()
     */
    @Override
    @Deprecated
    public final void _dont_implement_Matcher___instead_extend_BaseMatcher_() {
        // See Matcher interface for an explanation of this method.
    }

    @Override
    public void describeMismatch(Object item, Description description) {
        description.appendText("was ").appendValue(item);
    }

    @Override
    public String toString() {
        return StringDescription.toString(this);
    }
}

JUnit実践入門 3日目(ユニットテストについて)

Junitなどテストをしてんするフレームワークは、テスティングフレームワーク呼ばれる。
「どのようにテストを記述して、実行し、検証するか」の仕組みを提供する。
→一定のフォーマットでテストケースを記述できるのでテストコードが読みやすくなる。

コードの種類
ソフトウェア本体のコード :プロダクションコード
テストのためのソースコード:テストコード
テストコードを書くときに重視すべき点
プロダクションコードと同じように重複をなるべく減らすように整理をしたものより、可読性の高さを求められる
→テストコードを読むことで、テストで何を前提として、何を行い、何を期待しているのか理解しやすいようにする。

テストケースで明確にするもの
・前提条件
・入力値や操作
・テスト実行後の期待する値や動作
これらが不完全な場合、テスト自体が不完全なものとなる。

SUT(Sysytem Under Test)
テスト対象となるクラスやオブジェクトをSUTと呼ぶ。
一つのテストケースでは一つのSUTのみ対象とする(複数のSUTを一つのテストケースで扱った場合、そのテストケースがなんのテストをしているのかが不明瞭となる)
テストコードでは、テスト対象クラスのインスタンスを生成し、「sut」という名前の変数で扱うことでテスト対象がどれかわかりやすくなる。
※慣習としてテストクラスはテスト対象と同じパッケージとし、クラス名を「テスト対象のクラス名+Test」とする。このためテスト対象のクラス(SUT)は命名規則から特定される。

@Test
public void isValidはuserNameとpasswordが空でないときにtrueを返す() throws Exception{
 UserForm sut = new UserForm("user01", "1234");
 assertThat(sut.isValid(), is(true));
}

実測値と期待値
実測値:メソッドが返す値やオブジェクトの変化する状態
期待値:仕様として返すべき値や変化する状態である値
→可読性を高めるために、テストコードに含まれる変数名は実測値に「actual」、実測値に「expected」とするとよい。

@Test
public void userNameが空のときエラーメッセージを返す() throws Exception{
 UserForm sut = new UserForm("", "1234");
 String expected = "ユーザーIDは必須項目です";
 String actual = sut.getErrorMessage();
 // actual:実測値 expected:期待値
 assertThat(actual, is(expected));
}

※一つのテストメソッドに複数の実測値や期待値が必要となった場合、テスト対象が大きすぎる可能性がある。
 複数の検証要素が存在する場合は、メソッドを増やすことで検証している要素がブレないようにする。

メソッドと副作用
わかりにくいテストコードとなる最大の要因は、テスト対象がテストしにくい設計となっていること。
テストの可動性やメンテナンス性を高めるために効果的なことは、ユニットテストを行いやすいクラス設計とすること。
ユニットテストが行いやすいメソッドの性質
・メソッドが返り値を持つ
→実行結果が明確でわかりやすい
・メソッド呼び出しの際、副作用がない(オブジェクトの内部状態が変更されないこと)
→繰り返しテストを実行しても、都度同じ条件でテスト実行ができる
 →副作用があるメソッド:ArrayListのaddとか
・同じ状態、同じパラメータを実行すれば、必ず同じ結果を返す
→乱数による影響がない

これらのような性質を持つメソッドを関数的と呼ぶ

4フェーズテスト
ソフトウェアのテストは次の4フェーズで実行される
・事前準備
→テスト対象オブジェクト(SUT)の初期化、必要な入力値、期待される結果などの準備を行う
・実行
→SUTに対し、テストする操作を一つだけ実施する
・検証
→テストの結果として得られた実測値が期待値と等価であるかを比較検証する
・後処理
→次のテストに影響がないように後始末をする。

これら4フェーズを意識して書かれたテストコードは可読性が高くなる。
このためコメントとして描くフェーズの区切りを記述しておくことが良い。

@Test
public void メソッド名()throw Exception{
 //Setup
 ArrayList<String> sut = new ArrayList<String>();
 sut.add("Hello");
 sut.add("World");

 //Exercise
 sut.remove(0);

 //Verify
 assertThat(sut.size(), is(1));
 assertThat(sut.get(0), is("World"));
}

Eclipceのテスト作成時のテンプレートにコメントを追加して自動的に記述されている状態にするのが良さそう。

JSTQB認定テスト技術者資格 Foundation Level試験対策 個人用メモ

試験対策問題を解いてわからなかった部分をシラバスから抜粋して以下に備忘録として記載する。
随時更新。
※順番とか形式をまとめたい。。。

●独立したテストの利点

  • 先入観がないため、異なる結果を見つけられる。
  • システムの仕様策定中や実装中に想定通りかを検証できる。

●独立したテストの欠点

  • 開発チームから隔絶している。
  • 開発担当者の品質に対する責任感が薄れる。
  • 独立したテストチームは、ボトルネックと見られたり、リリース遅延のために非難されたりする。

●テスト仕様の支援ツール

  • テスト設計ツール
  • テストデータ準備ツール

●テスト実行と結果記録支援ツール

●性能・モニタリング支援ツール

  • 動的解析ツール
  • 性能テスト/ロードテスト/ストレステストツール
  • モニタリングツール

●運用テストの目的
→信頼性や可用性などのシステム特性をチェックすること。

●テスト技法の違い

  • 静的技法:故障より欠陥(故障の原因)を効率よく検出できる。
  • 動的技法:欠陥より故障を効率よく検出できる。

●機能要件と非機能要件の違い

  • 機能要件:要件定義の中で実装する機能に関する要件のこと
  • 非機能要件:実装する機能以外の要件のこと

→属性として、信頼性、効率性、使用性、保守性、移植性などが関係する。

ブラックボックス技法とホワイトボックス技法の違い
ブラックボックス技法
テストするコンポーネントやシステム内部構造についての情報を使用しない。
仕様ベースのテスト技法の特徴

  • 対象となる問題、ソフトウェアコンポーネントを定義する場合、形式的、非形式的にかかわらず、モデルを使用する。
  • モデルから、体系的にテストケースを導き出す。
  • 同値分割法
  • 境界値分析
  • デシジョンテーブルテスト
  • 状態遷移テスト
  • ユースケーステスト

ホワイトボックス技法
コンポーネントやシステムの内部構造に基づく。
構造ベースのテスト技法の特徴

  • ソフトウェアの構成に関する情報(コードや詳細化された設計情報)を基にテストケースを導き出す。
  • テストケースを基に、ソフトウェアの構造に関するカバレッジを計測し、カバレッジを上げるためのテストケースを導き出せる。

●イテレーティブインクリメンタル開発モデル
システムの要件、設計、構築、テストを何回かの短い開発サイクルで繰り返してソフトウェアを作成するモデル。
具体例としては下記

イテレーション(反復)によって開発されたシステムは、それぞれのイテレーション匂いつ複数のテストレベルでテストされる。
開発ずみのソフトウェアを追加する場合も、システムの一部を構成するのでテストが必要である。
開発を繰り返すたびに回帰テストの重要性は増していく。
検証や妥当性確認は、各繰り返しにおいて、実施できる。

●プロジェクトリスク
組織問題の要素

  • スタッフのスキル、およびトレーニング不足、人員不足
  • 人事の問題
  • テスト担当者のニーズやテスト結果がうまく伝わらない
  • テストやレビューで見つかった事項がチームでうまくフォローアップできていない。(例えば、開発やテストの実施方式が改善されていない)
  • テストから期待できるものを正しく評価しようとしない。

技術的な問題

  • 要件を正しく定義できない。
  • 制約があるために、要件を拡張できない。
  • テスト環境が予定通りに用意できない。
  • データ変換の遅れ、移行計画の遅れ、データ変換/移行ツールの開発・テストの遅れ
  • 設計、コード、構成データ、テストデータ、テストの低い品質

供給者側の問題

●プロダクトリスク
ソフトウェアやシステムで失敗する可能性のある領域(次に起きる事象が意にそぐわなかったり、危険を引き起こしたりする可能性)のこと。

  • 故障の起きやすいソフトウェアの出荷
  • ソフトウェアやハードウェアが個人や会社に対し損害を与える可能性
  • 貧弱なソフトウェア特性(機能性、セキュリティ、信頼性、使用性、性能など)
  • 貧弱なデータの完全性と品質(データ移行問題、データ変換問題、データ伝送問題、データ表人への違反)
  • 指定の機能が動作しないソフトウェア

●テストリーダーとテスト担当者の実施作業の違い
テストリーダー(テストマネージャー、テストコーディネーターとも言う)

  • テスト戦略とテスト計画をプロジェクト管理者やその他の関係者と調整する。
  • プロジェクトのテスト戦略や組織のテストポリシーを策定したりレビューしたりする。
  • テストの観点から、プロジェクトの他の活動(例えば、統合計画など)に貢献する。
  • プロジェクトの背景を考慮し、テストの目的とリスクを理解してテストを計画する。
  • テスト結果やテストの進捗に基づいて計画を修正し、問題を補正するために必要なあらゆる対策を取る。
  • 構築するテスト環境を決定する。
  • テスト期間中に収集した情報をベースにテストレポートを書く。

テスト担当者

  • テスト計画に対してレビューによって貢献する。
  • 試験性の観点で、要件仕様、モデルを分析し、レビューし、評価する。
  • テスト仕様を作成する。
  • テスト環境をセットアップする。
  • テストデータを作成したり、入手したりする。
  • 全てのテストレベルのテストを実装し、テストを実行して記録を取り、テスト結果を評価し、期待した結果との差異を文書化する。
  • 必要に応じて、テストコントロールツールやマネジメントツール、モニタ用ツールを使う。
  • テストを自動化する。
  • 必要ならば、コンポーネントやシステムの性能を計測する。
  • 他の人が実施したテストをレビューする。

●テストの終了基準

  • コード、機能性、リスクのカバレッジのような、徹底度
  • 欠陥密度や信頼性の見積もり値
  • コスト
  • 既存リスク、例えば、未修正の欠陥や、特定領域のカバレッジ不足など
  • 出荷時期などのスケジュール

●レビューの種類
非公式レビュー

  • 決まったプロセスはない。
  • 主な目的は、金や時間をかけずにある程度の効果を出すことである。

ウォークスルー

  • 作成者が進行を主導する。
  • 主な目的は、ソフトウェアの内容を学習、理解し、欠陥を見つけることである。

テクニカルレビュー

  • 同僚や技術のエキスパートが参加し、そのプロセスはあらかじめ定義し、ドキュメント化してある。
  • 経験を積んだモデレータ(作成者ではなく)が進行を主導するのが理想である。
  • 指摘一覧、ソフトウェア成果物が要求事項を満たしているかどうかの判定、そして適宜、指摘事項に関連する推奨事項を含むレビューレポートを準備する。
  • 主な目的は、ディスカッションすること、判断を下すこと、他の方法を評価すること、欠陥を見つけること、技術的な問題を解決すること、仕様や計画、企画、標準に準拠していることをチェックすることである。

インスペクション

  • 経験を積んだモデレーター(作成者ではなく)が進行を主導する。
  • 同僚によるチェックが一般的である。
  • 各人の役割が決まっている。
  • メトリクスの収集が含まれる。
  • 規則やチェックリストを使い、形式に基づいたプロセスで進める。
  • ソフトウェア成果物の受け入れに対する開始、終了基準を決める。
  • インスペクションのレポートや指摘一覧を書く。
  • 形式の決まったフォローアッププロセスがある。

 →プロセス改善が含まれる場合がある。

  • 主な目的は欠陥の検出である。

●機能テスト、非機能テストの違い
機能テスト:システムが「何をするのか」のテスト

  • 全てのテストレベルで実施する必要がある。
  • ソフトウェアやシステムの機能からテスト条件とテストケースを抽出する際に仕様ベースの技法が使える。
  • ソフトウェアの外部動作を検証する。

非機能テスト:システムが「どのように動作するのか」のテスト

  • 全てのテストレベルで実施できる。
  • 性能テスト、ロードテスト、ストレステスト、使用性テスト、相互運用性テスト、保守性テスト、信頼性テスト、運用テストなどがある

●テスト戦略、テストアプローチ
分析的アプローチ:例えば、緊急度が最も高い分野を重点的にテストするリスクベースのテストなど。
モデルベースアプローチ:故障率(信頼度成長モデル)や、使用法(運用プロファイル)に関する統計的情報を使う確率テストなど。
方法論的アプローチ:フォールトをベースにしたもの(例えば、エラー推測、フォールト攻撃)チェックリストをベースにしたもの。品質特性をベースにしたものなど。
プロセス準拠アプローチや、標準準拠アプローチ:例えば色々なアジャイル方法論や、業界固有の標準など。
動的で経験則に基づいたアプローチ:事前に計画するのでなく、発生した事項に対応する方法や、テスト実行と評価を同時に実行する探索的テストなど。
コンサルテーションテストベースのアプローチ:テストチーム外のビジネスドメインのエキスパート、又は技術的エキスパートの助言や指導を受けた部分から優先的にテストカバレッジを上げていく。
回帰的アプローチ:既存のテストを再利用したり、回帰テストを高度に自動化したり、標準のテストスイートを使うなど。

JUnit実践入門 2日目(ユニットテストについて)

ソフトウェアテストの概要とユニットテストの目的や基本となる概念について

ソフトウェア開発における「テスト」の定義:
「ある条件下においてソフトウェアの振る舞いを記録し、その記録が期待される結果となることを検証するプロセス」

学生の頃などに体験した「テスト」
問題として用意された質問に対して受験者が回答を行い、成績を出すプロセス。
→成績は受験者がどの程度の成績を持っているかの指標。問題に対する回答が決められているか、模範回答が存在する場合がほとんど。

ソフトウェア開発における「テスト」
検証する内容を定義し、ソフトウェアが期待どおりに動作するかを確認するプロセス。
→ソフトウェアの仕様や要件を元にテスト項目を作成する。ユーザビリティテストのように検証する項目が明確ではなく、人間の感覚に依存するテストもある。

定義から見るソフトウェアテストの重要なポイント3点
1.「ある条件下」という制約があること
→これは前提条件や事前条件と呼ばれる。前提条件が異なっていれば検証する内容も異なるため、テストでは前提条件が明確になっている必要がある。

2.「ソフトウェアの振る舞いを記録する」こと
→記録できなければ、期待される結果となることを検証できないため。

3.「期待される結果との検証を行う」こと
→つまり期待される結果がランダム性の高い振る舞いであればあるほど、テストは検証が難しくなる。

ソフトウェアテストの目的:
主な目的は品質管理。
→しかしながらソフトウェアテストではテストによって目的が異なるため、なんのためのテストであるかを意識するかが重要となる。

テスト技法
テストケースの作り方によってホワイトボックステストブラックボックステストの2つに大分類することができる。

ホワイトボックステスト
内部のロジックや使用を考慮してテストケースを設計する。
→したがって、ソフトウェアの内部構造とコードを理解しているプログラマが実施する。

ブラックボックステスト
内部構造について考慮せず、外部仕様のみからテストケースを作成する。
→プログラミングの知識よりも業務知識などが重要となる。

イメージとしてはこの記事に記載されているものが理解しやすい。
marikooota.hatenablog.com

ブラックボックステストの技法
同値テスト:ソフトウェアが同様の結果をもたらす値を同値クラスとしてグループ化し、各同値クラスからテストデータを選択するテスト技法。
境界値テスト:ソフトウェアが異なる結果をもたらす値(境界値)に着目し、境界値の近傍からテストデータを選択するテスト技法。

ユニットテストとは:
クラスやメソッドを対象としたプログラムを検証するためのテストであり、ソフトウェアテストの中ではもっと小さい粒度のテスト。
期待された振る舞いをするか検証し、テストが成功することによってそれを保証する。
「期待された振る舞い」=対照のクラスやメソッドの仕様

特徴:
ユニットテストはプログラムとして実行できる仕様書となる。
ユニットテストが成功する限り、正確な仕様書となる。
プログラムとしてユニットテストを行う場合、最初にテストコードを記述するコストはかかるが、実行するコストはほとんど必要ない。
したがって、何度でも実行できるし、テストを頻繁に実行することも可能となる。
→一方手動によるテストの場合にはテストの実行に大きな労力が必要になり、繰り返し実行することにコストがかかる。

実装コスト 手動テスト>自動テスト
実行コスト 手動テスト<自動テスト

目的:
仕様通りの振る舞いをするか保証する。
→しかしながら、直接の目的はソフトウェアの品質を高めることではない。
 ユニットテストを繰り返し何度も実行することで、プログラムに問題が発生した時に、早い段階で影響範囲などをチェックできる。
 つまり、対象のクラスやメソッドの仕様を動くプログラムとして記述することにより、仕様を明確にし、その仕様を保証すること。
 →改修を進めていくことによってプログラムに修正が入ったとしても仕様を担保することができるのはリグレッションの防止として役立ちそう。。

ドキュメントとしてのテスト:
テストケースは最も正確なドキュメントであり、テスト対象のサンプルコード。
なのでメンテナンスをし続ける必要があり、かつ常にテストコードを読む人を意識してテストコードを書く必要がある。

問題の局所化:
テストケースは十分に小さな単位で可能な限り多く作るべき。
→なんらかの原因でテストに失敗したとしても影響範囲と条件が絞り込みやすくなるため。迅速な原因特定につながる。

独立したテスト:
テストケースは可能な限りお互いに影響を与えないように定義すべき。
もしテストの結果やテストの実行順番がテストの結果に影響を与えるようならば、テストケースの追加や削除により予期しない形でテストが失敗する可能性が発生する。

JUnit実践入門 1日目(テストコードの作成〜実行まで)

JUnitとは:Javaのテスティングフレームワーク
役割
・テスト実行フレームワーク
・テストの期待値と実測値の検証
・テストケースのフォーマット
→どのようにテストを記述して、実行し、検証するかをサポートしてくれる。

テスト対象のクラスを作成する。

package junit.tutorial;
public class Calculator {
	public int multiply(int x, int y) {
		return x * y;
	}
	public int divide(int x, int y) {
		return x /y;
	}
}

f:id:Uzms:20180804191335p:plain

testソースフォルダの作成
→一般的にテスト対象と異なるソースフォルダに作成する。
 今回はプロジェクト直下に作成。

テストクラスの作成
f:id:Uzms:20180804194540p:plain
f:id:Uzms:20180804194604p:plainf:id:Uzms:20180804194609p:plain

以下のテストクラスが作成される

package junit.tutorial;

import static org.junit.Assert.*;

import org.junit.Test;

public class CaluculatorTest {

	@Test
	public void test() {
		fail("まだ実装されていません");
	}
}

Quick JUnitの利用
キー入力無しでテストクラスが生成できる。メッチャ便利。
プロジェクトのテスト対象クラスを右クリックー実行ーJunitテスト
f:id:Uzms:20180804202048p:plain

テストコードのルール
・テストクラスはpublicクラス
・テストメソッドはTestアノテーションを付与したpublicメソッド
・テストメソッドは戻り値がvoidであり、引数を持たない
・thorow句は自由に定義可能
→自動生成したテストクラスでは上記のルールに沿った形式でテンプレートが作られているので意識しなくともルールに乗っ取った形で作成できるが、エラーの原因となりかねるのでしっかりと覚えておくこと。


生成されたテストコードを実行するとエラーが発生。
jarファイルが足りないらしいので追加。
JUnit4.11にしたら、Hamcrestが無いってエラーが出たよ・・・

追加するとエラーが解消され、コードが実行された。

                                                                                                            • -

テストメソッドを日本語で書く(!?)
昔から議論が行われている模様。
曖昧さはなくなりそうなので導入して見たい。
→複数人で開発を実施する際には最初に決め事とすべきか。

                                                                                                            • -

実際に作成したテスト対象クラスを実行するようにテストコードを作成。

package junit.tutorial;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.junit.Test;

public class CalculatorTest {

	@Test
	public void multiplyで乗算結果が取得できる() {
		Calculator calc = new Calculator();
		int expected = 12;
		int actual = calc.multiply(3, 4);
		assertThat(actual, is(expected));
	}
}

作成したテストコードを実行して見ると、JUnitのViewの障害トレースにエラーログが表示されず、グリーンバーが表示された。
これでテストコードとしてはテスト成功を意味している。
f:id:Uzms:20180804221542p:plain

ただ、テストコードは成功したが、実際のテストとしては網羅していないパターンがまだあるはずなので
テストコードをさらに追加する。

追加するテストパターン:5と7の乗算

package junit.tutorial;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.junit.Test;

public class CalculatorTest {

	@Test
	public void multiplyで3と4の乗算結果が取得できる() {
		Calculator calc = new Calculator();
		int expected = 12;
		int actual = calc.multiply(3, 4);
		assertThat(actual, is(expected));
	}
	
	@Test
	public void multiplyで5と7の乗算結果が取得できる() {
		Calculator calc = new Calculator();
		int expected = 12;
		int actual = calc.multiply(5, 7);
		assertThat(actual, is(expected));
	}
}

この状態で実行するとmultiplyで5と7の乗算結果が取得できる()メソッドがエラーとなる
→原因としてはassertThatメソッドで第一引数に指定しているactualの値を変更していないため。
エラーログには下記の情報が表示される。
Expected:期待された値
   got:実際の値
f:id:Uzms:20180804222647p:plain

このようにして、ユニットテストでは1つのメソッドに対して複数のテストを実施して精度を高めていく。

String型変数の空文字&nullチェック

エラーチェックやステータスが新規の場合などで使用します。

String chk = null;
//chk = "";
if ( chk == null || chk.length() == 0 ){
	// chkがnull、もしくは空文字の場合行われる処理
	// chkの値が空白スペースの場合はチェックに引っかからない。
	// 空白スペースもチェックしたい場合にはtrim()等で空白スペースを除去する。
}


変数に値が入っている時に処理を行いたい場合には

if ( chk != null || chk.length() != 0 )