はじめに
この記事は、研修のネタとして単体テストの基礎学習とTDDの演習教材として用意したものです。
ほんとの入門者以外にはあまり役に立たたないと思いますが公開します。
ちなみについ最近、Ruby on Rails開発者のDHHによる以下の記事が話題になりました。
こちらも合わせて読んでみるのも面白いと思います。
TDD is dead. Long live testing.
http://david.heinemeierhansson.com/2014/tdd-is-dead-long-live-testing.html
原文で読むのが結構難しい文章ですが、翻訳してくれた方がいます。
http://d.hatena.ne.jp/yach/20140424
Lesson0
単体テストの定義
単体テスト(ユニットテストと呼ばれることもあります)は、 プログラムを構成する比較的小さな単位(ユニット)が個々の機能を正しく果たしているかどうかを検証するテストです。
通常、関数やメソッドが単体テストの単位(ユニット)となります。
プログラムが全体として正しく動作しているかを検証する結合テストは、開発の比較的後の段階でQA(品質保証)チームなどによって行なわれることが多いのとは対照的に、単体テストは、コード作成時などの早い段階で開発者によって実施されることが多いのが特徴です。
ポイント
- プログラムを構成する小さな単位の機能を検証するテスト
- 関数 や メソッド が単体テストの単位(ユニット)となる
- 主に開発者自身によって行われる
単体テストの利点
- モジュール結合前にテストが実施されるため、問題の原因の特定や修正が容易
- 開発全体のバグ修正コストを下げる効果が高い
- 仕様の整理や、設計の不備などに気づける
単体テストの基本
その1 機能に着目
- Given ... Then ... を意識する
◯◯という条件を与えた(Given)場合には(Then)□□となる
#=> 仕様を満たすことの確認となる
例) 加算を行うADD関数
ADD関数は以下の仕様を満たすとする。
- Given 引数1: 整数、引数2: 整数
- Then 引数1 + 引数2の結果
より具体的に
- Given 引数1: 3、引数2: 5
- Then 3 + 5 #=> 8
テストで確認することは、ADD関数に 引数1: 3、引数2: 5 を与えたとき、結果が 8 となること。
結果というのは戻り値を指すことが多い。
※状態が絡むような場合は、Given ... When ... Then の形としたほうが自然な場合もある。
その2 網羅性(カバレッジ)に着目
- 出来る限り多くの(意味ある)テストを行うことを意識する
- 制御パステスト
例えば、If ...
Else If ...
Else ...
全てのパスを通す- 命令網羅
- 条件網羅
- etc...
http://www.itmedia.co.jp/im/articles/1111/07/news144.html
その3 単体テストをやりやすく
結合されることが前提のモジュールは単体テストがやりづらいことがある
- 単体テストの目的は・・・?
#=> 関数など小さい単体機能が正常動作することをテストするのが目的
ならば、その目的を達成するための確認に効果的なものであれば、本来その手段は問われない。
場合によっては以下のような手段を使うことも。
- テスト用にコードをいじる・・・
- デバッガを使う・・・
スタブ/ドライバ
以下のようなモジュールを利用することで、単体テストをやりやすく、効果的に行うことができる。
- スタブ
呼び出し側のテストのための、呼び出されて決まった動作をするモジュール - ドライバ
呼び出される側のテストのための、呼び出す用のモジュール
単体テストの性質
2種類のテスト
- ブラックボックステスト
- ホワイトボックステスト
単体テストはホワイトボックステストとして扱う。
単体テストの欠点
- 開発者にかかるテストの負担が大きくなりやすい
- テスト実施にある程度のスキルが必要。
- 結合レベルで単体レベルの問題が発生しても、単体テストを再度やる余裕がない
どうすれば?
単体テストの欠点を補い 良い単体テストを行うには・・・?
The ART OF UNIT TESTING
- 単体テストは、自動化され、反復実行できるものであるべきだ
-単体テストは実装しやすいものであるべきだ - 一度単体テストが記述されたら、将来の使用に備えて残しておかれるべきだ
- 誰でもその単体テストを実行できるべきだ
- 単体テストはボタン1つで実行できるべきだ
- 単体テストは素早く実行できるべきだ
by Roy Osherove
http://artofunittesting.com/
テストフレームワーク
The ART OF UNIT TESTINGは理想だよね。
でも原始的なテスト手法ではそれを実践することは非常に難しい。
テストのためのフレームワークを用いることでそれが可能になる
xUnit系フレームワーク
xUnitとは、コンピュータプログラムの単体テスト(ユニットテスト)を行うためのテスティングフレームワークの総称である。これらのフレームワークでは、関数やクラスなど、ソフトウェアの様々な要素(ユニット)をテストすることができる。xUnitフレームワークの主な利点は、テストを自動化できること、同じテストを何度も書かずに済むこと、個々のテストの結果がどうあるべきかを覚えておかなくても良いことである。
このようなフレームワークの最初の実装は、ケント・ベックが開発したSmalltalk用のテスティングフレームワークSUnitである。その後、各コンピュータプログラム言語や開発環境毎に、同様の設計を持つフレームワークが多数作成されている。xUnitそれ自体は非常に単純なプログラムであるが、近年のソフトウェア開発で採用されつつある。
有名なxUnit系フレームワーク
- JUnit (Java)
- CUnit, Cutter(C)
- CppUnit, Cutter(C++)
- Test::Unit(Ruby)
- NUnit(.NET Framework)
xUnitの基本
◯◯という条件を与えた(Given)場合には(Then)□□となることをコード表現し、実行することで確認する
-
expected
期待される結果 -
actual
実際の結果 -
assertion
expectedとactualを突き合わせる
効果
単体テスト用フレームワークを用いることで、
単体テストの効率が大きく上昇し、
繰り返し実行のコストが減少する。
=> 開発しながらテストを何度も繰り返すことができる
TDD
-
Test Driven Development
テスト駆動開発 -
XP(Extreme Programming)の一種
by ケント・ベック
テストを原動力として開発を進めること!
TDDの流れ
- プロダクトコードを書く前にテストコードを書き、それが失敗することを確認する (レッド)
- テストに成功するようにプロダクトコードを書く (グリーン)
- プログラムの振る舞いを変えないように、プロダクトコードの重複などを整理する (リファクタリング)
- (最初に戻る)
TDDの原則
- テストに失敗しない限り、プロダクションコードを書いてはいけない。
- プロダクションコードはテストを通るように書く
- テストは少しずつ書き進めていく
もう一度流れ
- 要求仕様を満たすテストコードを1つ書く
- テストコードが失敗することを確認する
- テストコードにパスするコードを書く
- コードのリファクタリングを行う
- 要求仕様を満たすテストコードを1つ書く
- テストコードが失敗することを確認する
- テストコードにパスするコードを書く
- コードのリファクタリングを行う
- 要求仕様を満たすテストコードを1つ書く
- テストコードが失敗することを確認する
- テストコードにパスするコードを書く
- コードのリファクタリングを行う
- 繰り返し
リファクタリング
プログラムの外部から見た動作を変えずにソースコードの内部構造を整理すること。
-
テストコードを先に書いていることにより、コードのリファクタリングがしやすい
-
リファクタリングによるリグレッション(デグレ)の抑止にもなる
-
テストコードを満たすプロダクションコードを書いたら、そのタイミングで見直しをする
TDDの型
Repeat after me!
Everybody say RED!
Say GREEN!
Say リファクタリング!
RED!
GREEN!
リファクタリング!
RED!
GREEN!
リファクタリング!
RED!
GREEN!
リファクタリング!
...
TDDの真髄
さっき、単体テストの利点として、
- 仕様の整理や、設計の不備などに気づける
ということを挙げた。
つまり、TDDは単純なテスト技法に留まらない。
TDD の皮肉の 1 つは、TDD がテスト技法ではないこと (カニンガムの考案) である。
TDD は分析手法および設計技法であり、実際には開発のすべてのアクティビティを構造化するための技法である。
(ケント・ベック『テスト駆動開発入門』)
Lesson1
これから、実際にTDDのハンズオン演習を行う。
演習の対象として以下を利用するので、得意な言語でやること。
- Cutter (C)
- Visual Studio のテスト機能 (VB.NET, C#)
- Test::Unit (Ruby)
もしどの言語も知らない人は、最低限必要となる説明を加えているので、Rubyを利用すること。
演習問題
- 引数に任意の整数を取り、その絶対値を返す関数
abs_value
を作成せよ -
abs_value
関数が正しく動作することを確認せよ
Rubyのためのヒント
標準出力への出し方
puts ...
例
puts 'Hello Ruby!' #=> Hello Ruby
x = 3
puts x #=> 3
関数の定義の仕方
def 関数名(引数1, 引数2 ...)
... 処理 ...
return 戻り値
end
例
def add(x, y)
x + y
end
- 最後に評価された式の結果が自動的に戻り値となるので、実はreturn文が無くても構わない。
if文の書き方
if 条件式
... 処理 ...
elsif 条件式
... 処理 ...
else
... 処理 ...
end
例
if x < 10
puts 'xは10未満です'
elsif x == 10
puts 'xは10です'
else
puts 'xは10より大きいです'
end
Lesson2
- 引数に任意の整数を取り、その絶対値を返す関数
abs_value
を作成せよ -
abs_value
関数が正しく動作することを確認せよ - ただし、テストコードを書く前にプロダクションコードを書いてはいけない
- テストにパスするための「最低限の」プロダクションコードのみ書くことが許される
極端な例
Given 引数1 -> 2, 引数2 -> 3 then 戻り値 5
というテストのためのテストコードがあった場合に、この条件を満たすプロダクションコードの最低限のものは
引数などの条件を問わず、 return 1 となる!
Rubyのためのヒント
テストコードの作成方法
require 'test/unit'
class テストクラス名 < Test::Unit::TestCase
def test_テストメソッド名()
assert(条件式)
end
end
- テストメソッド名の
test_
というプレフィックスは必須です
例
require 'test/unit'
class TC_Lesson2 < Test::Unit::TestCase
# 引数が1と2のときのadd関数のテスト
def test_add_1_2()
# 期待する値をexpected変数に入れる
expected = 3
# 実際の関数の実行結果をactual変数に入れる
actual = add(1, 2)
# expectedとactualが等しいかaseertionを行う
assert(expected == actual)
end
end
Lesson3
FizzBuzz問題
以下のFizzBuzz関数を作成せよ。
- 引数に数値を1つとる。
- 3の倍数の時は”Fizz”を返す
- 5の倍数の時は”Buzz”を返す
- 3と5の公倍数のときは”FizzBuzz”を返す
- 上記のどれにも当てはまらない場合は、引数の数値をそのまま返す
- ただし、テストコードを書く前にプロダクションコードを書いてはいけない
- テストにパスするための「最低限の」プロダクションコードのみ書くことが許される
Lesson4
ファイルの操作
以下の3つのファイルがある
1 本日の講習の中では、
3 手法を使った、ごく簡単なプログラムの開発を行います。
9 どのようなプロジェクトであっても、きっと開発の中で活かす機会があります。
8 TDD は、学んですぐに業務で役立てられるものではないですが、考え方については、
6 「TDD を実践する中で、開発の中でテストを意識する考え方を身につけ、安定したプログラムを書けるようになること
7 これらを、講習で身につけたいこととして位置付けています。
2 単体テストを動力として開発を進める「テスト駆動開発 (以下TDD)」という
5 「単体テストの定義や意義を正しく認識すること」
4 ただ、TDD を身につけてもらうことを主たる目的とはしていません。
これらのファイルを全て読み込んで、行頭の番号順1からに並べ変えて、
1つのファイル File-ABC.txt
として書き出すプログラムを作成せよ。
- ただし、テストコードを書く前にプロダクションコードを書いてはいけない
- テストにパスするための「最低限の」プロダクションコードのみ書くことが許される
終わりに
紹介したTDD手法やxUnitフレームワークは、今日ではとても古典的なもの。
(※古いからダメというわけじゃなく)
興味のある人はBDDなどのキーワードでも調べてみること。