システム開発におけるテストの概要や概念 | 第2話

システム開発におけるテストの概要や概念 |第1話 は、アプリケーション開発においてテストを作成することが重要であること、テストを作成するための開発手法はいくつかあるため現場に即した方式を採用することが望ましいこと、テストする対象としてどのようなものがあるのかを概要と概念について説明しました。

今回は、分かりやすいテストを作成するための記述方式について紹介し、そのような記述形式に多くの場合に対応しているテストフレームワークの存在しについて紹介したいと思います。

分かりやすいテストを書くための記述方式

一例として、分かりやすいテストを記述する方式について紹介していきます。

分かりやすいテストとは何かということを考えた場合、テストしたいアプリケーション(システム)がどのような動作をするのが望ましいかということが分かることが重要と言えます。

逆に分かりづらいテストとは何かということを考えた場合、テストする項目が無機質に並べられて結果の値を比較するような場合です。

例えばテストを次のように書いたとします。

def test
  Micropost.search("John Doe").blank?
  return false if alice.blank?

  alice.setting.update(hidden: true)
  bob = User.find_by(name: 'Bob')

  bob.friends.find_by(name: 'Alice').blank?
end

上記は Alice というユーザの hidden 設定を有効にした後、 Bob というユーザの友達一覧から Alice を探すことが出来なくなることをテストしたいと思って記述したコードです。

分かりづらい点としては test メソッドで書かれたテスト内容を確認しないと、何をテストしているのか分からないことと、どうなっていればテストが成功と言えるのかが分からないことです。

分かりづらい点をコメントやメソッド名で補おうとすると次のように書けます。

def hidden_user_should_not_be_found_by_friends
  alice = bob.friends.find_by(name: 'Alice')
  # alice's friend bob can find alice
  return false if alice.blank?

  alice.setting.update(hidden: true)
  bob = User.find_by(name: 'Bob')

  # alice's friend bob cannot find alice
  bob.friends.find_by(name: 'Alice').blank?
end

上記のように書かれていれば、メソッド名を見ただけで先程述べたテストしたい内容とテストを実行した結果何を期待しているのかが分かるようになります。

言葉にしてみて通じるとよい

このように分かりやすいテストを書くためには、テストしている内容が説明されていればよいと言えます。

そこで、まずはテストする内容を「言葉にしてみて通じるかどうか」を確認してみることが重要です。

このテストは
「Appのオブジェクト」が
「どのような場合」に実行されると
「どうなる」ことをテストしています

上記のように言葉にしてみて、すんなり通るかどうかを確認してみましょう。

先の例では、

このテストは
「友達一覧を取得するメソッド」が
「秘匿値を設定されている場合」に実行されると
「秘匿されたユーザは一覧に含まれない」
ことをテストしています

テストの説明を書く

例では、メソッド名を説明文章で表し、コメントとしてテストで期待することを記述しました。

ここで、テストフレームワークはプログラミング言語の違いで多々存在し記述形式も異なるものの、
多くのテストフレームワークでは、テストコードを記述する際に
自然言語に近い形で何が書かれているか読み取ることが出来るようなフォーマットを用意しています。

例えば、Ruby のテストフレームワークである RSpec は describe, context, it というメソッドがあります。単体では何をするメソッドであるか分かりませんが、例のテストを RSpec を使って記述すると理解できると思います。

RSpec.describe Micropost, type: :model do
  describe "search posts by term" do
    context "when no post is found" do
      it "returns an empty collection" do
        expect(Micropost.search("John Doe")).to be_empty
      end
    end
  end
end

上記は、Micropost モデルの search メソッドが何も見つからなかった場合に空のコレクションを返すことをテストしています。

ここで、describe, context, it に与えられた値を階層順に読み進めると、

Micropost, type: :model
  search posts by term
    when no post is found it returns an empty collection.

のようになり、テストしたい内容が分かるようになっています。

上記テストコードを実行した場合、 it ... do ... end の中に書かれた Micropost.search("John Doe") が実行され、その結果が空であることを期待していますが、

expect Micropost.search("John Doe") to be empty

のように、何を期待しているのか分かるようになっています。

また describe, context, it に記述した内容はテストを実行した時のレポートとして表示することができます。
そのため、テストコードが読みやすいだけでなく、テストが失敗した時にレポートを見ただけで何のテストが失敗したのかが分かりやすいようになっています。

テストフレームワーク

テストコードを実行でき、テストを実行する時に必要なモックなどの仕組みを備えたフレームワークをテストフレームワークと呼びます。

テストフレームワークでは、分かりやすいテストが書けるように、RSpec のようにテスト内容の記述形式を自然言語に近くしているものも多く、大抵はモックが使えるようにモッキングフレームワークを利用できます。

例で挙げた RSpec のように、アプリケーションやシステムを実装している言語に応じたテストフレームワークを利用することで、テストを効率よく作成することが出来ます。

テストフレームワークにはテストを実行する「テストランナー」、ある振る舞いを模倣する「モック」、テストで利用する「テストデータ(フィクスチャ)」があります。
ここでは、それぞれについて紹介していきます。

テストランナー

テストランナーはその名のとおり、テストを実行するプログラムのことです。
テストケースを収集し、収集したテストを実行し、結果をレポートするのがテストランナーの役割です。

テストフレームワークは当然テストを実行することが出来るためこの機能が含まれていますが、並列化により高速化する等、ある特徴を持ったテストランナー単独のツールも存在します。

単独でテストランナーが用意されている場合は複数のテストフレームワークで記述されたテストを実行できるよう対応しているものもあるようです。

モック / モッキングフレームワーク

モックとはある振る舞いを模倣するものです。
モックを提供するフレームワークをモッキングフレームワークと呼びます。

例えば DB や API 等で連携している外部システムのように、アプリケーションやシステムを動作させるために必要な外部システムがあった場合に、それらが無くても同様の振る舞いを行うことでテストを実行できるようにすることが目的です。

外部システムに限らず、実装が完了していないクラスの動作を模倣させるため等にも使えます。

テストデータ(フィクスチャ)

テストで利用するデータで、特に固定値として利用するものをフィクスチャと呼びます。

テストを実行するために一定の条件を再現したい場合に、その条件を構成するデータなどがフィクスチャです。

テストデータとしてテキストに保存されたものを利用する場合や、アプリケーションと同様にテストデータをコードとして記述する等、データを生成する方法に特徴が出てきます。

まとめ

今回は、テストケースを分かりやすく記述するためのテクニックを紹介し、分かりやすいテストを記述し実行することが出来るテストフレームワークが存在することを紹介しました。

次回は、テストフレームワークの具体例を紹介したいと思います。