give IT a try

プログラミング、リモートワーク、田舎暮らし、音楽、etc.

【動画あり】続・リーダブルテストコード:みなさんからの質問に答えてみました #vstat

前回書いたブログの続きです。

blog.jnito.com

「VeriServe Test Automation Talk No.3」というオンラインイベントで登壇した際に参加者のみなさんから質問をたくさんいただきました。

一部はイベント内で回答したのですが、時間内に全部回答することはできなかったので、ここで回答することにします。
ただし、テキストで回答を書こうとするとかなり大変なので、YouTube動画にしています。

興味深い質問が多数あって、何かしらみなさんの参考になると思うのでぜひ一度ご覧ください😄


www.youtube.com

動画を見る時間がない、という人のために、ざっくりとQ&Aの内容を書いておきますね。

質問1

先日開発が始まって半年くらいのあるプロダクトの開発を引き継ぎました。
ドキュメントが一切なく開発者が私一人です。
テストを作り始めていきたいと思うのですが、まず着手するべことに
ついてアドバイスいただけないでしょうか。

レガシーな巨大リポジトリにテストを追加しようと思って二の足を踏んで
いるのですが、どういったファイルからテストを書いていますか?
また、どうすればレガシーコードにテストを導入しやすいでしょうか?
目的や優先度、テストの書き方など、考慮すべきポイントがあれば教えて
下さい。

ユースケース的に一番重要なE2Eテストから書いていきましょう!

質問2

レビューをする際、機能自体のレビューにかけた時間に対してテストの
レビューにかける時間はどのくらいの割合で行っていますか?

時間的な割合だと10〜30%ですかね〜。

質問3

初心者な質問かと思うのですが、失礼致します。
テストを書いた時に、テストのコード量に応じて仕様変更に対して動きが
重くなると思いますが (テストを沢山直さないといけない)
これは仕方がない事なのでしょうか?何か工夫はありますか?

基本的には仕方ないことですね。
工夫としては「権限だけをテストするテストコード」と「それ以外のテストコード」を分割することがあります。

あと、このツイートも追記しておきます……。

質問4

たとえばバックエンドのAPIを開発する際、エンドポイントごとのテストは
必ず書くのですが それより奥の層(サービスメソッドやリポジトリメソッド)
は必要に応じて、とすることが多いです
本来的にはその奥の層の各部品に対しても、基本的なユニットテストを
整備してあげるのが望ましいとは思うのですが、
優先度としては上記のようにする場合が多いです
みなさんはそのあたりどう考えていらっしゃるでしょうか

その考え方で問題ないと思います!

質問5

RSpecで、例えばuserのroleがadminである、ことがテストの結果として
必要な場合、 let(:user) {create(:user, role: :admin)} のような形で描くか、
before do ... enduser.update(role: :admin)のようにかくか、
あるいはit do ... endの中で

user.update(...)
expect(...)

と書くか、どれが良いでしょうか?
個人的にはbeforeやitの中がわかりやすい気がしていますが、let内で書くのが
今担当しているPJ 内の慣習のようになっているので気になっています。

「テストの結果」ではなくて「テストの事前条件」のことでしょうか?
であればletやbeforeで書くことが多いです。
可読性が一定レベルを超えていれば、どちらでも良いと思います。
テストコードについて、あまり細かいレベルであれを直せ、これを直せというのは時間の使い方がもったいないかも。

質問6

仕様Aから仕様Bに変更した場合、テストはどう変更すると良いかの方針
などありますか? 既存の「仕様Aであること」を確認するテストを
「仕様Bであること」のテストに書き換えるのか、
「仕様Aでないこと」と「仕様Bであること」のテストが別にあった方が
いいのか、など

「仕様Bであること」のテストだけでOKです。
ただ、「仕様Aでないこと」もテストしないと不安になるときは「仕様Aでないこと」のテストも書きます。

質問7

テストコードを再利用したい時に、振舞いとデータに分けて実装したく
なります。 そうなる変数を使いたくなります。
この様な場合、どう対処したら良いでしょうか?

パラメタライズドテスト(データ駆動テスト)を利用するといいかもしれません。
下記ブログ記事の後半にパラメタライズドテストの利用例が載っています。

blog.jnito.com

質問8

変数を使うと、テストコードを変更がいらないというメリットもあると思うのですが、いかがですか?

class Hoge
  NAME = 'suzuki'
  def name
    NAME
  end
end
# 名前が変わったときに変更しないといけない 
it 'NAMEがsuzukiであること' do
  expect(Hoge.name).to eq 'suzuki' 
end
# 名前が変わっても変更しなくていい 
it 'NAMEがtanakaであること' do
  expect(Hoge.name).to eq Hoge.NAME 
end

アプリケーション側の定数は直接参照しない方がいいです!
詳しくはこちらのブログをご覧ください。

blog.jnito.com

質問9

こんばんは。Ruby初学者で、Railsを使ってアプリケーション開発の
勉強をしている者です。
質問なのですが、初学者が陥りがちなNGテストコードあるある等が
あれば教えていただければ幸 いです。

初学者が陥りがちなNGテストコードあるあるといえば、idをベタ書きする人をたまに見かけますね。
「ベタ書き大事」といってもidをベタ書きするのはNGです。これは自動採番される値なので。

# idはベタ書きしちゃダメ!
let(:user) { FactoryBot.create(:user, id: 1) }
let(:project) {
  FactoryBot.create(:project,
    id: 1,
    name: "RSpec tutorial",
    user_id: 1)
}
let!(:task) { FactoryBot.create(:task, project_id: 1, name: "Finish RSpec tutorial") }

Railsであればモデルの関連をうまく活用すればidをベタ書きせずに済みます。

# 関連を活用すればidをベタ書きする必要はない
let(:user) { FactoryBot.create(:user) }
let(:project) {
  FactoryBot.create(:project,
    name: "RSpec tutorial",
    user: user)
}
let!(:task) {
  project.tasks.create(name: "....")
}

ちなみに上記のコードは「Everyday Rails - RSpecによるRailsテスト入門」のサンプルコードを一部改変したものです。

leanpub.com

質問10

システムテストを統合テストより先に書くべきでしょうか??
Railsのminitestを使っていて、最もエンドユーザの行動に近いブラウザを
使ったシステムテスト を先に書き、時間があればintegrationやcontroller,
modelのテストを後に書くようにしていま す。

システムテスト(E2Eテスト)が優先、で良いと思います。
僕がテストを書くときもシステムテストかモデルのテスト(システムスペックかモデルスペック)が9割以上です。

質問11

カバレッジについての指針は何かありますか

カバレッジ率については80%以上を目指すようにしています。
100%を狙うのは費用対効果があまりよくないので無理に目指さなくて良いです。
大きな機能をリリースする前は具体的にどのコードがテストされていないのかをレビューすることもあります。

まとめ

僕が回答した質問は以上です。詳しい内容はこちらの動画をどうぞ〜!


www.youtube.com

あわせて読みたい

勉強会本編の登壇内容についてはこちらのブログにまとめてあります。
blog.jnito.com

同じイベントに登壇していた風間さんもいくつか同じ質問について回答してくれているので、こちらも参考になると思います!
nihonbuson.hatenadiary.jp