RSpec(テストフレームワーク)なしで紹介する自動テスト超入門

## これは何？

自動テストとは何か、どのように書くのかを **出来るだけ簡単に書いた記事** です。
自動テストの入門には `RSpec` などのフレームワークの使い方や導入が先にくる記事が多いため、
今回はそうした **テストフレームワークを使わずに自動テストの実践** をしていきたいと思います。

### 対象者

* 自動テストに障壁がある、自動テストをまったく知らない、書いたことがない、という方

###  読んで期待するところ

* 自動テストを自分でも書いてみようと思ってくれると嬉しいです

## 自動テストの定義

この記事では自動テストを以下のように定義します

* 対象のプログラム(A)が、期待通りに動いていることを検証するためのプログラム(B)を作成する
  * Aを `プロダクトコード`, B を `テストコード` と名前をつけます
* テストコードを実行してプロダクトコードが期待通りに動いていることをテストする
  * これを **自動テストと定義** します

## 簡単な目次・流れ

* 初めに `FizzBazz` のプロダクトコードを作成し動作確認をします
* 上記の `FizzBazz` に対してテストコードを作成し、実行します
* テストコードの出力結果を見やすいようにリファクタをします
* 最後に付録で `RSpec` で書き換えたバージョンを記載しています

## `プロダクトコード` の記載

みんな大好き Ruby で以下を実現できるプログラムを作成します。

* 渡された数が 3 の倍数のとき 「Fizz」 という文字列を返す
* 渡された数が 5 の倍数のとき 「Bazz」 という文字列を返す

### クラスの用意

`FizzBazz` クラスを作成し、上記の仕様を実現するメソッド `fizz_bazz` を作ります
返す文字列を判定するための受け取る数は `num` という名前で受け取ることにします
ファイル名は `fizz_bazz.rb` という名前で保存をしておきましょう

```fizz_bazz.rb
class FizzBazz
  def self.fizz_bazz(num)
  end
end
```

これで以下のようにプログラムを実行して動作確認ができるようになりました。

```ruby
FizzBazz.fizz_bazz(1)
#=> 結果
```


### 中身の実装

仕様通りに `fizz_bazz` を実装します

```fizz_bazz.rb
class FizzBazz
  def self.fizz_bazz(num)
    if num % 3 == 0
      "Fizz"
    elsif num % 5 == 0
      "Bazz"
    end
  end
end
```

これでとりあえず `プロダクトコード` が実装できました。

## テストする

ではこのクラスの動作確認をしてみます。
以下のようにプログラムを実行できる `fizz_bazz_test.rb` スクリプトを用意します。

```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` という結果が出力されたら期待通りに動作していることになります。

* 動作確認スクリプトの実行

```shell
$ ruby fizz_bazz_test.rb
#=> Fizz
```

期待通りに動いているようです。では `FizzBazz.fizz_bazz` に 5 を渡すようスクリプトを修正して実行してみます

```shell
$ ruby fizz_bazz_test.rb
#=> Bazz
```

これで `FizzBazz.fizz_bazz` が期待通りに動いているのを確認できました。

## 自動テストを作成する

上記のテストで人間の目による動作の保証を行いました。
おそらくこの段階で完成、とする方も少なくないでしょう。

ですがここは続いて自動テストを作成していきたいと思います。

上記の `fizz_bazz_test.rb` を書き換えて **テストコード** にしていきます。

### 結果をプログラムで検証する

`Fizz`, `Bazz` という文字列が返ってくることを人間の目で検証しましたが、これをプログラムで検証するようにします。

```diff
--- 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` のほうも確認を一緒にできるようにします。

```diff
--- 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
```

これで動作確認スクリプトを実行します。

```shell
$ ruby fizz_bazz_test.rb
【成功】
【成功】
```

人間の目でも動作が正しく動いているのが一目で分かるようになりました。
検証もプログラムがしてくれるようになりました。

これで **自動テストの作成ができました。**

### 自動テストの結果を見やすくする

このままでも自動テストとしては良いのですがまだいくつか問題があります。
例えば今の状況でもし誰かが `Bazz` という文字列を `bazz` に変えたとします。
そのとき自動テストを実行すると

```shell
$ ruby fizz_bazz_test.rb
【成功】
【失敗】
```

と出力されるだけです。何が失敗したのか、テストコードをきちんと読んでいかないと分かりません。
そこで **出力される結果から成功/失敗の結果や原因がある程度絞り込める** ようにしたいと思います。

#### 出力結果に条件と期待値を出す

以下の情報を結果に出力するようにテストコードを修正します。
* テストケースの条件
* 検証する内容

```diff
--- 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
```

これでテストを実行してみると以下のように結果が出力されます。

* 成功するとき

```shell
$ ruby fizz_bazz_test.rb
(引数に3を渡すとき)
  Fizzの文字列が返ること
    【成功】
(引数に5を渡すとき)
  Bazzの文字列が返ること
    【成功】
```

* Bazzがbazzになっているとき(失敗したとき)

```shell
$ ruby fizz_bazz_test.rb
(引数に3を渡すとき)
  Fizzの文字列が返ること
    【成功】
(引数に5を渡すとき)
  Bazzの文字列が返ること
    【失敗】
    実行結果: bazz
```

これでかなり分かりやすい結果になったかと思います。

#### テストするクラスやメソッドも出力する

今回は `FizzBazz` クラスの `.fizz_bazz` メソッドしかテストしていませんが、
本来であればもっと複数のクラス、複数のメソッドをテストすることになると思います。

そうした時に **今どのクラスのどのメソッドのテストしているか** も一緒に出力するようにしておいたほうが、より分かりやすくて良いでしょう。
以下のようにコードを修正します。

```diff
--- 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
```

これでテストを実行してみると以下のように結果が出力されます。

```shell
$ ruby fizz_bazz_test.rb
FizzBazzクラスをテストする
  .fizz_bazz メソッドをテストする
    (引数に3を渡すとき)
      Fizzの文字列が返ること
        【成功】
    (引数に5を渡すとき)
      Bazzの文字列が返ること
        【成功】
```


これで自動テストがさらに良くなりました！！

## 完成品

最終的に以下の **プロダクトコード** と **テストコード** が出来ました。
これで　`FizzBazz.fizz_bazz` も安泰ですね！(大げさ)

* プロダクトコード

```fizz_bazz.rb
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"

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
```

* 実行方法と結果

```shell
$ ruby fizz_bazz_test.rb
FizzBazzクラスをテストする
  .fizz_bazz メソッドをテストする
    (引数に3を渡すとき)
      Fizzの文字列が返ること
        【成功】
    (引数に5を渡すとき)
      Bazzの文字列が返ること
        【成功】
```

## 挑戦

練習で以下に挑戦をしてみましょう。

* `FizzBazz.fizz_bazz` を 3,5のどちらの倍数でもない数値が渡されたら、数値をそのまま返すようにする
* 上記のテストコードも実装する

## まとめ

`RSpec` などのテストフレームワークなどを使わずに自動テストを実装を書いてみましたがいかがでしたでしょうか。

本記事で記載したテストコードはあくまでも最低限の例であり、**良いテストコード** とは到底いえないものです。
それでも自動テストとは一体どんなものなのかを何となくでも分かってもらえたらと思い記載しました。

まず最初の一歩として、上記のようなテストコードを書いてみて自動テストの壁をなくしていただければと思います。

### 試せるブランチ

[こちらのブランチ](https://gitlab.com/toririn/test_tyou_nyumon)に今回のコードを置いています。
もし興味や勉強をしてみたい、などあればご利用ください。例えば

* もっとメンテナンス性の良いテストコードを書く
* 結果をより分かりやすい文章で記載する
* テストの実行時間をもっと早くする…etc

といったことをこちらのブランチから試してみると良いかもしれません。

## 付録

### `RSpec` でテストコードを書き換える

`RSpec` だと本記事のテストコードはどのようになるのかを記載します。
`RSpec` の導入方法などは別記事などを見てご確認ください。

簡単に結果だけ記載しておきます。

* `RSpec` はファイル名の末尾を `_spec` にしておくと自動で読み込みなどをしてくれるためファイル名を修正しています

```fizz_bazz_spec.rb
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` っぽく


```fizz_bazz_spec.rb
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"
```


### 挑戦の模擬回答

* プロダクトコード

```fizz_bazz.rb
class FizzBazz
  def self.fizz_bazz(num)
    if num % 3 == 0
      "Fizz"
    elsif num % 5 == 0
      "Bazz"
    else
      num
    end
  end
end
```

* テストコード


```fizz_bazz_test.rb
#! /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が返ること
        【成功】
```