100度超えサウナでないと満足できない、プレイライフのサウナエンジニアの合原です。
当社では、現在、Ruby on Railsを用いてサービス開発をしています。また、Rspecを使ったテストも普段から行なっています。
そこで、今回はテストの効用、ユニットテストの重要性and少しだけサンプルを示してみます。
対象読者
- テストの効用がよくわからない人
- ユニットテストを書いたことがない人
- ユニットテストの効果がよくわからない人
- 効果的なユニットテストを書くにはどうすればよいか学びたい人
プレイライフではエンジニアリングをしているのか興味がある方
Rspecとは
こちらは言わずと知れた、ポピュラーなテストフレームワーク。
構文には一種のくせ(?)あるため、敬遠する人もいるが、一度覚えると、柔軟なテストができます。
Rspecのメリット
- Rubyで最もポピュラーなテスティングフレームワーク
- メタ視点でのテストが書けるため、テストコードが自然言語っぽくなる
- ユニットテストに限らず、結合テストやブラウザテストなどにも使える
Rspecデメリット
- 一定の学習コストがある
反面、一度覚えれば、より柔軟なテストが可能となる。
なぜテストを書くのか
大きくは、以下の3つ。
- 改修時など、テストが失敗することで漏れや変更点が明確になり、デグレを防げる
- テスト自体が仕様となるため、コードの理解がしやすくなる
- コードの品質を上げられる
ユニット(単体)テストとは
ユニットテストとは、ソースコードの個々のユニット、すなわち、1つ以上のコンピュータプログラムモジュールが使用に適しているかどうかを決定するために、関連する制御データ、使用手順、操作手順とともにテストする手法である[1]。ユニットとはアプリケーションのテスト可能な最小の部品単位である、と直観的にとらえることができる
言い換えると、メソッドやクラスと言った単位で、想定された仕様どおりに動作しているかを検証するテスト。
なぜユニットテストが重要なのか
基本的に、アプリケーションは各種モジュールの束でできているため、各モジュール単位に分解して、それぞれをの動作を検証(テスト)することは不可欠になります。
これができていると、さらに上位の各機能の結合テストはさらに容易になる。
自ずと、アプリケーションのテスト網羅性は高めることができます。
基本的な考え方
ユニットテストでは、クラスやメソッドを最小処理単位として扱い、引数を処理した「結果」と、想定される「期待値」(これを振る舞いと言う)の2つを検証(比較)します。
ここで、以下のような引く数を受け取り1を加算するだけのメソッドをテストすることを考えてみます。
class Calc
def add(arg)
arg + 1
end
end
1.テストケースを想定する
上記のメソッドからテストケースを想定すると、大きくは以下の2つのケースが考えられます。
- 引数がnilの場合
- 引数が正常な数値の場合
- 引数が文字列等の場合
rspecで書くと..
RSpec.describe Calc, type: :model do # テスト対象となるクラス
describe '#add' do #テスト対象となるメソッド
context '引数がnilの場合' do
[ここに具体的なテスト内容]
end
context '引数が正常な数値の場合' do
[ここに具体的なテスト内容]
end
context '引数が不正な値(文字列等)の場合' do
[ここに具体的なテスト内容]
end
end
end
※ contextの箇所は英語でも日本語でもわかればいいと思います。テストの本質とは全く関係ないため。
こうすることで
- メソッドの振る舞いが明確になる
- テストを見るだけで、このメソッドの動作がわかる
- 仮にレビュアーであれば、ここを見るだけで、実は他にも動作ケースがありうるのであればテストケース漏れを指摘しやすくもなる
- 仮にプロダクション環境にデプロイ後でも、ここを見れば漏れが明確になり、修正が容易になる(メンテコスト下がる)
- 既存の振る舞いが明確なので、リファクタリングもしやすい
2. 実際のテストコード
最終的なコードは下記の通り。
※細かいrspecの文法については割愛。
RSpec.describe Calc, type: :model do # テスト対象となるクラス
describe '#add' do #テスト対象となるメソッド
subject { Calc.new.add(arg)}
context '引数がnilの場合' do
expect { subject }.to raise_error NoMethodError
end
context '引数が正常な数値の場合' do
let(:arg) { 1 }
it { is_expected.to eq 2 }
end
context '引数が不正な値(文字列等)の場合' do
let(:arg) { 'a' }
expect { subject }.to raise_error TypeError
end
end
end
まとめ
と言うわけで、あくまで簡易な例で一気に説明しましたが、ユニットテストを行うことで、
- コードの品質向上
- 将来的なメンテコストの下げられる(場合もある)
といったことが望めるのは少しおわかりいただけたか(?)と思います。
もちろん、Rspecに慣れないといけないと言った面もあります。
これからしっかりテストコードを書いていきたいと言う方に少しでも、お役に立てたら幸いです。
また、当社では、テストを軽視することなく、何よりもスピーディなサービス開発を実現することにも、果敢に挑戦しています。気になる方は、ぜひ、こちらから。