いまさらRSpecを触ってみて最初のうち戸惑っていた部分の話
この記事はRuby on Rails Advent Calendarの1日目の記事です。
実は、この夏からはじめて仕事で本格的に RSpec を触ることになり、少し慣れてきたところで最初に感じたことをメモしておきます。
RSpecの構文は慣れない人にはこんな風に映ることもあるんだーと笑っていただけますと幸いです。
TL;DR
subject
やlet
でテストで利用する変数の宣言と初期化をする記法に慣れるまで時間がかかったit .. do ... end
のブロックをexample
ということ、example
の中にテストコードを書いていくことがわからなかったdescribe
やcontext
に文法上の違いがあるかと思って身構えていた
慣れないうちに戸惑った話
テストコードやテスト対象をどこにどうやって記述したらいいかわからない
なかなか斬新な話なんですが、どういうことかというと、たとえば、自分がこれまで一番テストを書いてきたのはAndroidなので、Java用のテストフレームワーク JUnit5を例に挙げると
// https://junit.org/junit5/docs/current/user-guide/#writing-tests-nested より @DisplayName("A stack") class TestingAStackDemo { Stack<Object> stack; @BeforeEach void createNewStack() { stack = new Stack<>(); } @Test @DisplayName("is instantiated with new Stack()") void isInstantiatedWithNew() { new Stack<>(); } @Nested @DisplayName("when new") class WhenNew { @BeforeEach void createNewStack() { stack = new Stack<>(); } @Test @DisplayName("is empty") void isEmpty() { assertTrue(stack.isEmpty()); } // ... @Nested @DisplayName("after pushing an element") class AfterPushing { String anElement = "an element"; @BeforeEach void pushAnElement() { stack.push(anElement); } @Test @DisplayName("it is no longer empty") void isNotEmpty() { assertFalse(stack.isEmpty()); } // ... } } }
のような感じなんですが、
- 基本的にテストはクラスの単位でまとめられていて
- インスタンス変数などの定義は通常のクラスと同様に行ってよく
- テストの各ケースも通常のメソッドと補足情報をアノテーションで指定したりするが、同様に書いていけばよい
- 各テストケース実行前の変数の初期化は
BeforeEach
などのアノテーションがついた箇所で宣言とは別の箇所で行う
んですね。一方同じ内容をRSpecで書くと以下のようになります。
describe "A stack" do let(:stack) { Stack.new } describe "when new" do it "is empty" do expect(stack.empty?).to eq true end # ... describe "after pushing an element" do let(:an_element) { "an element" } it "is no longer empty" do expect(stack.empty?).to eq false end # ... end end end
- まず、テストのためにクラスを定義しない
- 内部実装をよくわかってないので違うのかもしれませんが、少なくともそういう書き方をしない
let
で変数の宣言と初期化の方法を一緒に記載してしまう。変数の初期化だけのために前処理のブロックを用意していない- テストの内容を書いていく箇所もメソッドではなくてブロックの中
という具合で、かなり戸惑いが大きかったです。
特に3番目について、RSpecでは it ... do ... end
のブロックで囲まれた部分にテストのコードを書いていき、その単位を example
といって、その中にexampleなどを書いてくのですが、最初は
「describe
ブロックや context
ブロックの中にどうして処理を書いていけないのか( example
のそれと同じブロックやんけ)」
みたいな感じであわあわしてました。
また、テスト対象が1つに決まっている場合は subject
という機能を使うと
- テスト対象が1つに決まっていることを明確化され
expect(Hogehoge).to
みたいにテスト対象の指定をしないis_expected.to
という書き方が可能となる
のですが、これも最初「テスト対象どこに行った??」という感じでした。
# subject を使わない場合 describe "A stack" do let(:stack) { Stack.new } describe "when new" do it "is empty" do expect(stack.empty?).to eq true end
# subject を使う場合 describe "A stack" do let(:stack) { Stack.new } subject { stack.empty? } # 特にテスト対象がインスタンスそのものでなく、 # インスタンスのメソッドの結果だったりする場合は # 直感的に理解できるようになるまで時間がかかった describe "when new" do it "is empty" do is_expected.to eq true end
エイリアスがたくさんある
また、テストコードを読んでいると
describe "..." do describe "..." do it "..." do ... end end context "..." do it "..." do end end end
みたいな同じ文法の位置に違う単語があって、最初は文法的に何か意味があるんだろうか、と思ったんですが、
- it / specify/ example は同じ意味で1つのテストケースのコードをグループ化するために用いられる
- describe / context は同じ意味で example をグループ化するために用いられる
- これらのエイリアスは自然な英語でテストが記述できるように用意されている
そうです。
まとめ
黙って一回チュートリアル。謙虚な気持ち、大事。
現場からは以上です。
慣れてきて覚えたことの話も書こうかと思ったのですが、長くなったので別記事にします。