こんにちは!マチマチというご近所SNSを開発している id:fujimuradaisuke です。
スタートアップで働くみなさん、テスト書いてますか?テストの書き方・方針ってチームによってかなり異なるんじゃないかなーと思っています。実際いままで僕が参加したチームもそれぞれに個性がありました。そこでマチマチのテスト方針やその背景をシェアしたら面白いのでは…?と思ったので(僕は他の人のテスト方針を読みたいです)、社内向けにまとめておいた文章を共有してみようと思います。
背景
ご近所SNSマチマチは「ひらかれた、つながりのある地域社会をつくる」というミッションのもと2016年に開発がスタートしました。現在外部のお手伝い頂いている方を含めて5人のエンジニアで開発しています。構成としてはプレーンでモノリシックなRuby on Railsのアプリケーションです。現在は気がつけばモデルが200個とそこそこの規模になっています。フロントエンドはReact.jsを使っていて、コンポーネントは250個ほどという状態です。
目的
- がんがんリリースできるようにする
- 「どう動いているべきか」をテストコードとして残す
の2つを目的にテストを書いています。
テストコードがないと新しい変更が正しく動作しているか自信がもてず、リリースへの心理的ハードルがあがってテンポが悪くなってしまいます。また、都度動作確認する時間的コストもバカになりません。
すべての機能に厳密な仕様書を残しメンテナンスするのは現実的ではないので、どうしても動いているコードが正解、ということになってしまいます。しかし動いているコードの現在の挙動は意図しているものなのか?は、テストコードがないとわかりません。自分でも忘れてしまいます。
大方針
- 複雑にしない
- End to endテストをがんばる
をモットーにしています。
「複雑にしない」の理由は、複雑さを解決するコストは手を動かすコストより高い、という認識あっての判断です。愚直に手を動かせば直せるものは無理に共通化せず、読みやすい、変えやすい、という状態を目指しています。
具体的には、複数のテストケースで前提を共有しないようにするなど、DRYさより可読性を重視し冗長に書くようにしています。マチマチはRSpecを使っているので、shared_example / shared_contextは極力使わない、beforeやsubjectに無理に共通部分を押し込まないなど。
「End to endテストをがんばる」の理由は、バックエンド・フロントエンド含めたアプリケーション全体としての動作保証で最も重要であるという点が大きいです。マチマチだとfeature specがEnd to endテストにあたります。
feature spec: 重要な仕様の網羅を目指す
全ての表現をfeature specでも網羅することで得られる価値はコストに見合わないと判断していて、「重要な仕様」とは思えない些末なものはテストしていません。具体的にはユーザー体験上進行不能なレベルではないクラスの切り替えによる表示のハイライトなど。こういうところはJavaScriptのテストで書くと簡単な場合もあるので、それもアリです。
feature spec: 長いexampleを書くの推奨
登録プロセスすべて、などを100行あるexampleで一気に書いています。例えば「登録できる」という機能を、メールアドレスとパスワードの送信、プロフィールの入力など、とそれぞれ単体でテストしても組み合わせると失敗する場合もあるので、システムテストとしてはあまり意味がありません。実際に使うときの一連の流れをテストコードとして残す気持ちで書いています。
feature: spec: 開発スピード重視
「コメントがn件増えた」ことを検証するときに直接モデルに触ってもOK、としています。End to endテストなので厳密には触ってはいけないはずですが、ここはスピードを重視しました。テストデータの投入もFactoryBotでやっています。
request spec: Web APIのドキュメントのつもりで書く
「期待される挙動を残す」のが目的なので。現状まだドキュメントを生成するまでには至っていません。
request spec: feature specでカバーできていれば書かなくてもよい
副作用、レスポンスともにごく簡単なAPIは書いていません。余談ですが、最近は更新系のAPIで204 No Content
を返すことが多くなってきました。面倒だし、レスポンスを使わないんですよね….。
model: Rubyのクラスとしての振る舞いをテストとして残す
ActiveRecord::Base
を継承したモデルもひとつのRubyのクラスです。クラス単体で見てどう振る舞うか、をテストコードして残すようにしています。他のテストでカバーされている場合でも、なるべく単体としての振る舞いはテストケースとして書き残すことを推奨しています。重複に思えるかもしれませんが、拡張、再利用のとき挙動が残されていると楽です。また複数の役割を持ったメソッドってたまに生えてしまうものですが、メソッドの挙動がテストに残っていると不自然さがわかるので、そういったメソッドの発生も避けやすいです。
model: バリデーション、リレーションなど「書けば動く」系のものは込み入ったもの以外書かない
これは過去あまり書いて嬉しかったことがないので書いていません。
job, lib, tasksなどその他
他のテストでカバーできていれば書かなくてもよいが、なるべく書くようにしています。ただ、簡単なジョブ(メソッド呼び出しをバックグラウンド実行するだけのもの)は書かず、feature/request/model specでカバーしています。
まとめ
以上が私達マチマチ開発チームのテストのやり方です。みなさんのチームのテストの方針と比べてどうでしょうか?異論・反論・賛同の声などありましたら、ブコメでもツイートでも直接ご連絡でもよいので、ぜひとも教えてください。何しろ少人数のチームなのでどうしても考えが極化しがちなので、外部の方の意見は自分たちのやっていることを客観視できるのでとてもありがたいです。また、ここはもっと聞きたい、という所があればできる限り追記しますので、遠慮なくお声掛けください。