これは何?
自動テストとは何か、どのように書くのかを 出来るだけ簡単に書いた記事 です。
自動テストの入門には RSpec などのフレームワークの使い方や導入が先にくる記事が多いため、
今回はそうした テストフレームワークを使わずに自動テストの実践 をしていきたいと思います。
対象者
- 自動テストに障壁がある、自動テストをまったく知らない、書いたことがない、という方
読んで期待するところ
- 自動テストを自分でも書いてみようと思ってくれると嬉しいです
自動テストの定義
この記事では自動テストを以下のように定義します
- 対象のプログラム(A)が、期待通りに動いていることを検証するためのプログラム(B)を作成する
- Aを
プロダクトコード, B をテストコードと名前をつけます
- Aを
- テストコードを実行してプロダクトコードが期待通りに動いていることをテストする
- これを 自動テストと定義 します
簡単な目次・流れ
- 初めに
FizzBazzのプロダクトコードを作成し動作確認をします - 上記の
FizzBazzに対してテストコードを作成し、実行します - テストコードの出力結果を見やすいようにリファクタをします
- 最後に付録で
RSpecで書き換えたバージョンを記載しています
プロダクトコード の記載
みんな大好き Ruby で以下を実現できるプログラムを作成します。
- 渡された数が 3 の倍数のとき 「Fizz」 という文字列を返す
- 渡された数が 5 の倍数のとき 「Bazz」 という文字列を返す
クラスの用意
FizzBazz クラスを作成し、上記の仕様を実現するメソッド fizz_bazz を作ります
返す文字列を判定するための受け取る数は num という名前で受け取ることにします
ファイル名は fizz_bazz.rb という名前で保存をしておきましょう
class FizzBazz
def self.fizz_bazz(num)
end
end
これで以下のようにプログラムを実行して動作確認ができるようになりました。
FizzBazz.fizz_bazz(1)
# => 結果
中身の実装
仕様通りに fizz_bazz を実装します
class FizzBazz
def self.fizz_bazz(num)
if num % 3 == 0
"Fizz"
elsif num % 5 == 0
"Bazz"
end
end
end
これでとりあえず プロダクトコード が実装できました。
テストする
ではこのクラスの動作確認をしてみます。
以下のようにプログラムを実行できる fizz_bazz_test.rb スクリプトを用意します。
# ! /usr/bin/ruby
# FizzBazzクラスを相対パスで読み込む
require_relative "fizz_bazz"
# FizzBazz.fizz_bazz の結果を受け取る
result = FizzBazz.fizz_bazz(3)
# 結果をコマンドラインに出力する
puts result
用意ができたら実行をしてみます。
今回は FizzBazz.fizz_bazz に 3 という数字を渡しているので Fizz という結果が出力されたら期待通りに動作していることになります。
- 動作確認スクリプトの実行
$ ruby fizz_bazz_test.rb
# => Fizz
期待通りに動いているようです。では FizzBazz.fizz_bazz に 5 を渡すようスクリプトを修正して実行してみます
$ ruby fizz_bazz_test.rb
# => Bazz
これで FizzBazz.fizz_bazz が期待通りに動いているのを確認できました。
自動テストを作成する
上記のテストで人間の目による動作の保証を行いました。
おそらくこの段階で完成、とする方も少なくないでしょう。
ですがここは続いて自動テストを作成していきたいと思います。
上記の fizz_bazz_test.rb を書き換えて テストコード にしていきます。
結果をプログラムで検証する
Fizz, Bazz という文字列が返ってくることを人間の目で検証しましたが、これをプログラムで検証するようにします。
--- a/fizz_bazz_test.rb
+++ b/fizz_bazz_test.rb
@@ -4,6 +4,11 @@
require_relative "fizz_bazz"
# FizzBazz.fizz_bazz の結果を受け取る
-result = FizzBazz.fizz_bazz(3)
-# 結果をコマンドラインに出力する
-puts result
+# 引数で3を渡す
+num = 3
+result = FizzBazz.fizz_bazz(num)
+if result == "Fizz"
+ puts "【成功】"
+else
+ puts "【失敗】"
+end
これで Fizz の確認ができるようになりました。
どうせなら Bazz のほうも確認を一緒にできるようにします。
--- a/fizz_bazz_test.rb
+++ b/fizz_bazz_test.rb
@@ -4,6 +4,11 @@
require_relative "fizz_bazz"
# FizzBazz.fizz_bazz の結果を受け取る
-result = FizzBazz.fizz_bazz(3)
-# 結果をコマンドラインに出力する
-puts result
+# 引数で3を渡す
+num1 = 3
+result1 = FizzBazz.fizz_bazz(num1)
+if result1 == "Fizz"
+ puts "【成功】"
+else
+ puts "【失敗】"
+end
+
+# 引数で5を渡す
+num2 = 5
+result2 = FizzBazz.fizz_bazz(num2)
+if result2 == "Bazz"
+ puts "【成功】"
+else
+ puts "【失敗】"
+end
これで動作確認スクリプトを実行します。
$ ruby fizz_bazz_test.rb
【成功】
【成功】
人間の目でも動作が正しく動いているのが一目で分かるようになりました。
検証もプログラムがしてくれるようになりました。
これで 自動テストの作成ができました。
自動テストの結果を見やすくする
このままでも自動テストとしては良いのですがまだいくつか問題があります。
例えば今の状況でもし誰かが Bazz という文字列を bazz に変えたとします。
そのとき自動テストを実行すると
$ ruby fizz_bazz_test.rb
【成功】
【失敗】
と出力されるだけです。何が失敗したのか、テストコードをきちんと読んでいかないと分かりません。
そこで 出力される結果から成功/失敗の結果や原因がある程度絞り込める ようにしたいと思います。
出力結果に条件と期待値を出す
以下の情報を結果に出力するようにテストコードを修正します。
- テストケースの条件
- 検証する内容
--- a/fizz_bazz_test.rb
+++ b/fizz_bazz_test.rb
@@ -4,20 +4,24 @@
require_relative "fizz_bazz"
# FizzBazz.fizz_bazz の結果を受け取る
-# 引数で3を渡す
+puts "(引数に3を渡すとき)"
num1 = 3
result1 = FizzBazz.fizz_bazz(num1)
+puts " Fizzの文字列が返ること" # 結果を見やすいように空白スペースを先頭に入れる
if result1 == "Fizz"
- puts "【成功】"
+ puts " 【成功】" # 結果を見やすいように空白スペースを先頭に入れる
else
- puts "【失敗】"
+ puts " 【失敗】" # 結果を見やすいように空白スペースを先頭に入れる
+ puts " 実行結果: #{result1}" # 結果を見やすいように空白スペースを先頭に入れる
end
-# 引数で5を渡す
+puts "(引数に5を渡すとき)"
num2 = 5
result2 = FizzBazz.fizz_bazz(num2)
+puts " Bazzの文字列が返ること" # 結果を見やすいように空白スペースを先頭に入れる
if result2 == "Bazz"
- puts "【成功】"
+ puts " 【成功】" # 結果を見やすいように空白スペースを先頭に入れる
else
- puts "【失敗】"
+ puts " 【失敗】" # 結果を見やすいように空白スペースを先頭に入れる
+ puts " 実行結果: #{result2}" # 結果を見やすいように空白スペースを先頭に入れる
end
これでテストを実行してみると以下のように結果が出力されます。
- 成功するとき
$ ruby fizz_bazz_test.rb
(引数に3を渡すとき)
Fizzの文字列が返ること
【成功】
(引数に5を渡すとき)
Bazzの文字列が返ること
【成功】
- Bazzがbazzになっているとき(失敗したとき)
$ ruby fizz_bazz_test.rb
(引数に3を渡すとき)
Fizzの文字列が返ること
【成功】
(引数に5を渡すとき)
Bazzの文字列が返ること
【失敗】
実行結果: bazz
これでかなり分かりやすい結果になったかと思います。
テストするクラスやメソッドも出力する
今回は FizzBazz クラスの .fizz_bazz メソッドしかテストしていませんが、
本来であればもっと複数のクラス、複数のメソッドをテストすることになると思います。
そうした時に 今どのクラスのどのメソッドのテストしているか も一緒に出力するようにしておいたほうが、より分かりやすくて良いでしょう。
以下のようにコードを修正します。
--- a/fizz_bazz_test.rb
+++ b/fizz_bazz_test.rb
@@ -3,25 +3,26 @@
# FizzBazzクラスを相対パスで読み込む
require_relative "fizz_bazz"
-# FizzBazz.fizz_bazz の結果を受け取る
-puts "(引数に3を渡すとき)"
+puts "FizzBazzクラスをテストする"
+puts " .fizz_bazz メソッドをテストする"
+puts " (引数に3を渡すとき)"
num1 = 3
result1 = FizzBazz.fizz_bazz(num1)
-puts " Fizzの文字列が返ること" # 結果を見やすいように空白スペースを先頭に入れる
+puts " Fizzの文字列が返ること"
if result1 == "Fizz"
- puts " 【成功】" # 結果を見やすいように空白スペースを先頭に入れる
+ puts " 【成功】"
else
- puts " 【失敗】" # 結果を見やすいように空白スペースを先頭に入れる
- puts " 実行結果: #{result1}" # 結果を見やすいように空白スペースを先頭に入れる
+ puts " 【失敗】"
+ puts " 実行結果: #{result1}"
end
-puts "(引数に5を渡すとき)"
+puts " (引数に5を渡すとき)"
num2 = 5
result2 = FizzBazz.fizz_bazz(num2)
-puts " Bazzの文字列が返ること" # 結果を見やすいように空白スペースを先頭に入れる
+puts " Bazzの文字列が返ること"
if result2 == "Bazz"
- puts " 【成功】" # 結果を見やすいように空白スペースを先頭に入れる
+ puts " 【成功】"
else
- puts " 【失敗】" # 結果を見やすいように空白スペースを先頭に入れる
- puts " 実行結果: #{result2}" # 結果を見やすいように空白スペースを先頭に入れる
+ puts " 【失敗】"
+ puts " 実行結果: #{result2}"
end
これでテストを実行してみると以下のように結果が出力されます。
$ ruby fizz_bazz_test.rb
FizzBazzクラスをテストする
.fizz_bazz メソッドをテストする
(引数に3を渡すとき)
Fizzの文字列が返ること
【成功】
(引数に5を渡すとき)
Bazzの文字列が返ること
【成功】
これで自動テストがさらに良くなりました!!
完成品
最終的に以下の プロダクトコード と テストコード が出来ました。
これで FizzBazz.fizz_bazz も安泰ですね!(大げさ)
- プロダクトコード
class FizzBazz
def self.fizz_bazz(num)
if num % 3 == 0
"Fizz"
elsif num % 5 == 0
"Bazz"
end
end
end
- テストコード
# ! /usr/bin/ruby
# FizzBazzクラスを相対パスで読み込む
require_relative "fizz_bazz"
puts "FizzBazzクラスをテストする"
puts " .fizz_bazz メソッドをテストする"
puts " (引数に3を渡すとき)"
num1 = 3
result1 = FizzBazz.fizz_bazz(num1)
puts " Fizzの文字列が返ること"
if result1 == "Fizz"
puts " 【成功】"
else
puts " 【失敗】"
puts " 実行結果: #{result1}"
end
puts " (引数に5を渡すとき)"
num2 = 5
puts " Bazzの文字列が返ること"
result2 = FizzBazz.fizz_bazz(num2)
if result2 == "Bazz"
puts " 【成功】"
else
puts " 【失敗】"
puts " 実行結果: #{result2}"
end
- 実行方法と結果
$ ruby fizz_bazz_test.rb
FizzBazzクラスをテストする
.fizz_bazz メソッドをテストする
(引数に3を渡すとき)
Fizzの文字列が返ること
【成功】
(引数に5を渡すとき)
Bazzの文字列が返ること
【成功】
挑戦
練習で以下に挑戦をしてみましょう。
-
FizzBazz.fizz_bazzを 3,5のどちらの倍数でもない数値が渡されたら、数値をそのまま返すようにする - 上記のテストコードも実装する
まとめ
RSpec などのテストフレームワークなどを使わずに自動テストを実装を書いてみましたがいかがでしたでしょうか。
本記事で記載したテストコードはあくまでも最低限の例であり、良いテストコード とは到底いえないものです。
それでも自動テストとは一体どんなものなのかを何となくでも分かってもらえたらと思い記載しました。
まず最初の一歩として、上記のようなテストコードを書いてみて自動テストの壁をなくしていただければと思います。
試せるブランチ
こちらのブランチに今回のコードを置いています。
もし興味や勉強をしてみたい、などあればご利用ください。例えば
- もっとメンテナンス性の良いテストコードを書く
- 結果をより分かりやすい文章で記載する
- テストの実行時間をもっと早くする…etc
といったことをこちらのブランチから試してみると良いかもしれません。
付録
RSpec でテストコードを書き換える
RSpec だと本記事のテストコードはどのようになるのかを記載します。
RSpec の導入方法などは別記事などを見てご確認ください。
簡単に結果だけ記載しておきます。
-
RSpecはファイル名の末尾を_specにしておくと自動で読み込みなどをしてくれるためファイル名を修正しています
require 'rspec'
require_relative "fizz_bazz"
RSpec.describe "FizzBazzクラスをテストする" do
describe ".fizz_bazz メソッドをテストする" do
context "(引数に3を渡すとき)" do
it "Fizzの文字列が返ること" do
num = 3
expect(FizzBazz.fizz_bazz(num)).to eq "Fizz"
end
end
context "(引数に5を渡すとき)" do
it "Bazzの文字列が返ること" do
num = 5
expect(FizzBazz.fizz_bazz(num)).to eq "Bazz"
end
end
end
end
- 実行方法と出力結果
$ rspec fizz_bazz_spec.rb -f d
FizzBazzクラスをテストする
.fizz_bazz メソッドをテストする
(引数に3を渡すとき)
Fizzの文字列が返ること
(引数に5を渡すとき)
Bazzの文字列が返ること
もう少し RSpec っぽく
require 'rspec'
require_relative "fizz_bazz"
RSpec.describe FizzBazz do
describe ".fizz_bazz" do
subject { FizzBazz.fizz_bazz(num) }
context "(When num is given 3)" do
let(:num) { 3 }
it { is_expected.to eq "Fizz" }
end
context "(When num is given 5)" do
let(:num) { 5 }
it { is_expected.to eq "Bazz" }
end
end
end
- 実行方法と出力結果
$ rspec fizz_bazz_spec.rb -f d
FizzBazz
.fizz_bazz
(When num is given 3)
should eq "Fizz"
(When num is given 5)
should eq "Bazz"
挑戦の模擬回答
- プロダクトコード
class FizzBazz
def self.fizz_bazz(num)
if num % 3 == 0
"Fizz"
elsif num % 5 == 0
"Bazz"
else
num
end
end
end
- テストコード
# ! /usr/bin/ruby
# FizzBazzクラスを相対パスで読み込む
require_relative "fizz_bazz"
puts "FizzBazzクラスをテストする"
puts " .fizz_bazz メソッドをテストする"
puts " (引数に3を渡すとき)"
num1 = 3
result1 = FizzBazz.fizz_bazz(num1)
puts " Fizzの文字列が返ること"
if result1 == "Fizz"
puts " 【成功】"
else
puts " 【失敗】"
puts " 実行結果: #{result1}"
end
puts " (引数に5を渡すとき)"
num2 = 5
result2 = FizzBazz.fizz_bazz(num2)
puts " Bazzの文字列が返ること"
if result2 == "Bazz"
puts " 【成功】"
else
puts " 【失敗】"
puts " 実行結果: #{result2}"
end
puts " (引数に7を渡すとき)"
num3 = 7
result3 = FizzBazz.fizz_bazz(num3)
puts " 7が返ること"
if result3 == 7
puts " 【成功】"
else
puts " 【失敗】"
puts " 実行結果: #{result3}"
end
- 実行方法と結果
$ ruby fizz_bazz_test.rb
FizzBazzクラスをテストする
.fizz_bazz メソッドをテストする
(引数に3を渡すとき)
Fizzの文字列が返ること
【成功】
(引数に5を渡すとき)
Bazzの文字列が返ること
【成功】
(引数に7を渡すとき)
7が返ること
【成功】