Help us understand the problem. What is going on with this article?

React Componentにおけるテスト方針

フロントエンド(React)のテストの方針を決めるにあたって、さまざまな文献を呼んている過程で、いくつかのテストの考え方に出会いました。

そして方針を決める中で、最も大きな影響を受けたのはKent C. Dodds(以下、Kent)のアイデアです。

KentはTesting Libraryという現在Enzymeに迫る勢いを持つフロントエンドのテストライブラリを開発した人物であり、Testing Trophyという既存のEnzyme主流のテスト方針を代替するテストコンセプトの考案者でもあります。

彼のテストに関する記事はどれも、多くの学びを得れるものだったのですが、
あまり日本語の記事で、Kentの考え方などがまとまったものがなかったので、
今回は、このKentの考え方におけるフロントエンド(特にReact Component)のテスト方針をシェアしたいと思います。

テストの方針の結論

まず、Kentが考えるテストの方針の結論ですが、
「テストがパスすることによって、ソフトウェアがちゃんと動くという自信を得れるようなテストを書こう!」
です。

テストは、テストカバレッジを上げるために書いているのではありません。
本番にリリース後、そのソフトウェアが「ちゃんと想定通り動くのかどうか」という観点が最も大切であり、
それを担保できるテストであるべきだということです。

これを、Kent流に言い換えると「(ソフトウェアがちゃんと動くという)自信を与えるテストを書こう」ということになります。
この「ソフトウェアがちゃんと動くだろう」という自信は、グラデーションであり、より大きな自信を与えてくれるテストほど、より良いテストとなります。

では、どのようなテストを書くべきか?

テストの種類には様々なものがあり、どうせテストを書くなら「より意味のあるテスト」に時間を使いたいものです。

そして「どのテストにどれだけ工数をかけるべきか?」に対する一つの考え方として、Testing Pyramidというコンセプトがあります。

▼Testing Pyramid
2.png

まずこの図に出てくる用語の説明をします。

End to End (E2E)

E2E(イー・ツー・イー)と略します。Functional Testと呼ばれたりもします。
これは実際にユーザーがアプリをクリックして一連の機能を利用する振る舞いを再現し、テストをするものです。

Integration

Integration Testは、複数のUnit Testをかけ合わせて、
想定した挙動になるかを確かめるテストです。

Unit

Unitテストは、単体のメソッドの挙動を確かめるテストです。

Testing Pyramidが表していること

Testing Pyramidが表していることは以下の3つです。

1. テストを走らせたときの処理スピード

まずは、この図のピラミッドの左側には、カメとうさぎの矢印がありますね。
これはテストを動かしたときの処理にかかるスピードを示しています。

カメは遅いこと、うさぎは速いことを表しています。

つまりE2Eが最もテストを走らせるのに時間がかかり、
Unitが最も時間がかからず、
Integrationがその中間あたりに位置するよ
ということです。

2. テストを書くのにかかるコスト(時間)

ピラミッドの右側には、ドルマーク($)がありますね。
これはそのテストを書くのにかかるコスト(エンジニアの工数)を示しています。

下にいくほど、コストが少なくなります。

3. 書くべきテスト量の比率

そして最後に、「どの種類のテストを、どれだけ書けばいいのか?」ということを、ピラミッドの面積で示しています。

ピラミッドの面積は、大きい順から、
Unit > Integration > E2E
となっているので、「Unitテストを最も書こうね」というメッセージになっています。

Testing Pyramidが考慮していない「自信」という観点

KentはこのTesting Pyramidでは考慮できてない点があり、不完全だと言っています。

それはそれぞれのテストが、「ソフトウェアがちゃんと動くことをどれだけ担保しているのか?」(どれだけ自信を与えてくれるのか?)という観点が抜けていることです。

Testing Pyramidはテストを書くコストや、テストを走らせるスピードなどの「効率」については考慮できていますが、
「どれだけソフトウェアがちゃんと動くことを担保できているのか?」という「自信」の観点については考慮できていません。

Testing Trophyという新しいコンセプト

KentはTesting Pyramidを刷新し、「自信」の観点を考慮に入れたTesting Trophyというコンセプトを考案しました。

▼Testing Trophy
confidence-coefficient.png

ここでは先程なかった「自信」の観点が加わったほか、他にもいくつか違いがあるので、
どこが同じで、どこが変わったのか説明します。

同じ点: 両端の矢印である「コスト」と「スピード」は同じ

トロフィーの左側にあるお金マークは、テストを書くコスト(工数)を表し、下にいくほどコストがかからないことを表しています。
トロフィーの右側にあるカメとスポーツカーは、下にいくほどテストを走らせたときのスピードが速いことを表しています。

つまりこの2つの矢印は、下にいくほどBetterだということです。

この2点はTesting Pyramid と同じですね。

同じ点: E2E, Integration, Unitも同じ

これもTesting Pyramid同じテストの種類を表しています。

違う点: テストの種類に「Static」が増えた

これはlinterや、TypeScriptなどの静的チェックを指しています。
これはトロフィーの中で一番下に位置しているので、最もコストが小さく、スピードも速いことを示しています。

違う点: 矢印に「自信係数」が増えた

真ん中に一つ矢印が増えて、上部に「Big Problems」、下部に、「Simple Problems」とあります。
これをKentは "confidence coefficient"(自信係数) と呼んでおり、
上に行くほど、テストが自信を大きく与えると言っています。

つまりこの「自信」の矢印は、上にいくほどBetterだということです。
他の2つの「スピード」と「コスト」の矢印とは逆ですね。

3つの矢印の「トレードオフ」を考えよう

Testing Pyramidは「コスト」と「スピード」の2つの矢印しかなく、
この2つの矢印は両方とも、下に行くほどBetterということになります。
つまりこの2つの矢印しか読み取らないのであれば、Unitテストばかりに時間を費やせばいいことになります。

しかし冒頭で述べた「テストの方針の結論」でも述べたように、
結局、書いたテストが「どれだけしっかりとソフトウェアがちゃんと動くことを担保するのか?」(自信を与えるのか?)ということがとても重要になってきます。

そしてTesting Trophyもこの「自信」の係数を考慮しています。

つまりTesting Trophyのコンセプトにおいては、
「自信」vs「コスト&スピード」のトレードオフを考慮して、どのようなテストをどれだけ書くかを考慮しようという話になります。

そして、Testing Trophyではその結論を、トロフィーの各部の大きさで表しています。

トロフィーの各部の大きさが、書くべきテストの総量を表している

Kentは自信とスピード、コストを考慮して最もトレードオフのバランスがよいIntegrationテストを最も書くべきと言っています。
その次にE2Eと、Unit。

Staticは問答無用で導入しておけばよいと思います。

Testing Trophyの概念に従った、テストの方針

テストの方針のコンセプトの説明を終えたので、
ここからは、そのコンセプトに従うためのより具体的なテストの方針を記載していきます。

implementation details(実装の詳細)をテストしない

implementation detailsとは、ユーザーがアプリ上で普段利用したり、見たりすることがないものを指します。

例えば、ユーザーから見えるHTMLの要素(ボタンやインプット要素、表示されているテキスト)などは、ユーザーがアプリ上から見えるものですよね。
対して、Stateだったり、Component内のメソッドは、ユーザーからは見えません。

なのでテストを書く際はこのような、ユーザーから見えない部分は触れずに、
ユーザーから見える部分のみを触れるようにしようということになります。
(そうすると自然とUnitTestやImplementationDetailsのTestはしにくくなり、Integrationテストに寄ってくると思います。)

implementation detailsが悪いと考えられてる理由は下記2点です。

  • リファクタした際にFalse Negativeを生む。
  • アプリが壊れた際にFalse Positiveを生む。

リファクタした際にFalse Negativeを生む

implementation detailsは実装の詳細をテストしているということなので、リファクタをしてその実装の詳細部分を変更すると、
返り値は変わらず、想定通りの動きをしているのに、途中の詳細部分が違うせいでテストが壊れてしまうことがあります。

このように、アプリとしては期待通りの動きをしているのに、テストが間違ってエラーを出してしまうことを「False Negative」と呼びます。

アプリが壊れた際にFalse Positiveを生む

implementation details(実装の詳細)しかテストをしていないと、その詳細ごとの動きしか確認できず、
本番のようにそれぞれの機能が連動して動いたときの挙動が壊れても、テストは通ってしまうことがあります。

このように、実際のアプリの全体の挙動は壊れているのに、テストが間違って通ってしまうことを「False Positive」と呼びます。

つまり Unitテストだけだと不十分で、しっかりIntegrationテストや、重要な部分はE2Eテストを書こう!という話になります。

このFalse Positive状態を比喩した、面白い動画があります。

False Positiveはこのような状態を指しています。

常に"what user should see"(ユーザーから見えるもの)という視点でテストを書く

Implementation Detailsをテストしてしまうことを避けるひとつのポイントとして、
テストをするのは「ユーザーから見えるもの」に絞ると良いとのこと。

React Componentにおいては、Unitテストではなく、Integrationテストを最も書くようにする

細かく分割されがちなReact Componentでは、Unitテストが Implementation Detailsになりがちです。
なので、基本的にはIntegrationテストをメインで書くようにしましょう。

直接StateやComponentのメソッドをテストでいじらない

これをすると、Implementation Testにつながるからです。
Stateや、メソッドはユーザーから見えません。

代わりに、あるボタンがクリックされたとき、Aという表示がBに切り替わるといった
常に「ユーザーから見えるもの」という視点でテストを書いていきましょう。

EnzymeのShallow Renderingは使わないようにしよう

Shallow Renderingは一つのコンポーネントのみしか反映せず、
直接stateや、インスタンスを生成してメソッドをいじるということが容易なため、
結果的にImplementation Detailsをテストしてしまいがちです。

ちなみにKentは、このshallowが嫌いすぎて、そもそもshallowがないテストライブラリを作ればいいじゃん!となり、
自らTesting Libraryというライブラリを作っています。

Testing Libraryにはshallowが無く、代わりに"what user should see"に関するメソッドが多くなっており、
Kentのコンセプトに沿ったテストがとてもしやすくなっています。

Mockingはできるかぎり少なくしよう

Mockをするほど、テストを偽装しているので、テストに対する自信係数が下がります。
Mockは本当に必要な場面のみに絞って使うようにしましょう。

外部システムに依存するテストはしない

Mockingを使うべきタイミングは、この外部システムに関する部分です。
外部システムの部分は、Mockにて置き換えるようにしましょう。

Code Coverageより、Use Case Coverageを意識する

テストカバレッジ(Code Coverage)の目標値を超えるためにテストを書くのではなく、
どれだけのユースケースをカバレッジしているかという「ユースケースカバレッジ」を意識して書こうとのことです。

最後に

Kentの考え方をざっとまとめてみましたが、Kentのブログにはもっと様々な視点からテストに関する考えを書いており読むだけでとても勉強になります。
ぜひ一読してみてください。

▼Kentのブログ
https://kentcdodds.com/blog

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away