Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

スナップショットテストの意義

スナップショットテストとは?

レンダリングした結果のDOM構造をテキストベースで書き出し、その結果が前回と変わっていないかをチェックするテストです。DOM構造が一切変化してないことを保証できます。

スナップショットテストの意義

A. コードを変更した時、DOMツリー(あるいは仮想DOMツリー)が変化してない = ユーザーから見たときに画面が崩れていないことを保証できるのが意義

DOMツリーもCSSも完全に同じであれば、ユーザーから見える画面は同じままのはずです。逆に、画面が変更になったのにスナップショットテストが落ちなかったら、コードがどこかで間違えていることがわかります(こっちが有難いことあまりないけど)

スナップショットテストは特に、単なるプレゼンテーショナルコンポーネントのテストとして有効です。ボタンなど動作が絡むコンポーネントがなければスナップショットテストだけ書けば十分なこともあります。

無限のQAリソースほし〜〜〜〜〜〜〜

無限のQAリソースで出来ること

無限のQAリソースと無限の時間さえあれば、こんなことができます!

  • 躊躇なくライブラリをアップデートできる (なぜならQAリソースが無限だから)
  • 抜本的なリファクタリングができる (なぜならQAリソースが(略))
  • アーキテクチャを丸ごと書き換えられる (なぜならQA(略))
  • リポジトリ丸ごと捨てて新しく書き直してもいい (なぜなら(ry
  • テストを書かなくていい (ry

いかがでしたでしょうか?

現実はそう甘くはない

現実を見よう。QAリソースは有限だし、QAに無限の時間はかけられない。ちゃんとテストを書いて、QAコストを下げよう。

競プロとアプリ開発はテストの書き方が違う

tl; dr アプリ開発でコーナーケースを網羅する必要はない。 まず正常系を一通りテストするのが最重要。

競プロでは「関数がインプットに対して正しくアウトプットできているか?」に意識が向きがちだが、この意識はアプリ開発には向かないんじゃないか?というお話。

そもそも全ての関数をテストしなくていい

そもそも全部の関数をいちいちテストしなくていいんじゃないか。なぜなら 労力に対して見返りが少ないから。 もちろん、各関数にテストがあればそれに越したことはない。が、関数よりもっと上の層、つまり関数を使って作るアプリケーションをテストすれば十分なはず。そして、アプリのテストは一般的に「関数の入出力のテスト」とは見た目が少し異なる。

アプリの入力は「ユーザー操作」、出力は多種多様

アプリのテストにおいて、「ユーザーが行った操作」が入力、「それに対するアプリのアクション」が出力だ。そして、アプリの出力はかなり多種多様である

ざっと例を挙げてみる。

  • 画面を再描画する
  • サーバー上のデータを変更する
  • ページを移動する
  • ダイアログを表示してユーザーにさらに入力を促す

もちろん、サーバーのデータ変更 や 画面の再描画 はさらに無数に分解できる。

出力が多様なので アプリのテスト方法は多岐にわたる。 関数のテストは入力に対して出力を確認すればいいが、アプリの場合は確認方法がそれぞれ異なる。「ページ遷移が発生するか?」「サーバーAPIを叩いたか?」「画面が期待通りに変わったか?」など、それぞれ書き方が違う。

そのため、アプリの正常系をテストするだけでもかなり大変である。エッジケースのテストを追加する余力はほとんどの場合ない。

まとめ: アプリのテストはまず正常系をしっかり確認しよう

これまで見てきたように、assert(solve(arg1, arg2, arg3) == expected) のような単純なテストをアプリ開発で書く機会は殆どない。コーナーケースへの意識は重要だが、コーナーケースを網羅することだけがアプリのテストでないのは確かだ。

まずは正常系のテストをちゃんと整備しよう。そして余力があればテストパターンを増やそう。ただし、たくさん増やしてもテストの成果は劇的には上がらないことに注意しよう。

Prettier は結局何をやっているのか?コードの動作は変わってしまわないのか?

結論: AST を作って、それを元にコードをフォーマットしているから、プログラムの動作は一切変わらない

結論をもう書いたので、意味が分かった人はここより下は不要。ちょっとだけ用語とかの解説をする。

そもそも AST とは?

AST とは Abstract Syntax Tree のこと。超簡単にいえばインデントなどを消して、動作に関係する箇所だけを抜き出した構文木のこと。

たとえば以下の3つはいずれも同じ AST になる。

(1 + 2) * 3
(1+2)*
3
(((1+2))*3)

上の図はJAVASCRIPT AST VISUALIZER で生成した AST。3つとも同じ AST が生成される。このように、AST にはインデントや余分なカッコなどの余計な情報が一切含まれていない。

AST が同じなので、Prettier にかけてみると同じコードが出力される。Prettier にかけた結果がこちら

つまり Prettier は一旦正規化してからコードを生成している

AST について分かってしまえば Prettier が何をやっているかはすぐ理解できる。元のコードから AST を生成して(=正規化)、それをもとに整形コードを組み立てているのだ。一旦 AST の状態にすることでインデントなど整形に関する情報をそぎ落とし、そこから組み立てることで一貫性のある整形済みコードが生まれる。

以上の説明から分かる通り、Prettier はプログラムの動作に関係ない部分だけ変更している。 AST に変換するときもそうだし、AST からコードを生成するところもそうだ。コードのフォーマットに関する箇所しか変更していない。

ちなみに: Prettier は純粋な AST を組み立ててる訳ではない

Prettier はあくまで AST が変わらないように変換するだけで、内部では空白情報を持った AST を使っているそうです。

参考資料

Prettier のしくみ - Speaker Deck

↑ Prettier の内部動作については、Prettier コントリビュータの方のこの資料が分かりやすい。