短時間でのテスト実行

和田卓人氏:ということで、じゃあ、次にいきます。短い時間で到達するというアジェンダ、3ポチ目ですね。

「信頼性の高い」、これはテストの結果に嘘がないという話でした。「実行結果」、これは信号として、また問題箇所の絞り込みとしてのテストの実行結果にこだわろうという話でした。そういったテストを、短い時間で到達する、信頼性の高い結果に短い時間で到達する状態を保つ。短い時間で。

ユニットテストの定義の曖昧さ

ということで、ここでですね、会場のみなさんにですね、ちょっと手を動かしていただこうかな、体を動かしていただこうかなと思いまして、5問、問題を出します。オンラインでやる時は、よく「Y」とか「N」とか書いてください、とお願いします。もしオンラインの中継にコメント欄があったら、「YYNYN」とか、みなさんに5文字書いていただきたくて。

これからみなさんにこの5問出します。今もうこれ、画面が、見えていますね。Yesだと思ったら「Y」、Noだと思ったら「N」。会場のみなさんは、Yesだと思ったら手を挙げていただいて、Noだと思ったら手を挙げないみたいな感じでやっていきます。例えばオンラインの方は「1人5文字書いてください」みたいな感じになるわけですね。

データベースにアクセスする。「ユニットテストとは何ですか? あなたにとってのユニットテストとは何かとか、あなたのチームにとってのユニットテストとは何か」。ユニットテストであってもデータベースにアクセスしてもいいよって思う人、手を挙げてください。そういうチームもあると思います。例えばRailsのテストとか、そうですよね。

ネットワークにアクセスするのはユニットテストですか? Yes、No。はい。Yesでもいいです。ちなみにこれ、答えに正解はないですよ。何をやろうとしているかというと、「ブレブレです」ということをやろうとしています。

ファイルにアクセスするユニットテストはOKですか? これはOKな人、多いですよね。半分ぐらい挙がりますね。現在時刻はどうでしょう。currentTimeMillisみたいなの、ありますよね。

もう最後。最後はちょっとだけ特殊で、なんかこの真ん中の、この緑のひし形がテストコードだと思ってください。テストコードでテストしようとしている対象がいます。テストしようとしている対象が依存しているやつがいます。モジュールAがあって、モジュールAはモジュールBとCに依存しています。よく、このモジュールBとCを本物をそのまま使うか、それともテストダブルという偽物、モックとかスタブと言われる偽物に置き換えるか。

これ、けっこうスタイルが分かれるし、チームによって本物使うのはユニットテストじゃない、偽物に置き換えるのがユニットテストだよと定義付けするチームも、流派もあったりします。みなさんはどっちだと思いますか?

ちなみに、「依存先のモジュールに本物を使うのはユニットテストだと思う」。Yes、No。これも割れますね、半分ぐらい。というわけなんですね。

これがですね、オンライン講演で5文字、「みなさん1人5文字書いてください」みたいな感じで、その後SORTしてUNIQUEすると大変なことに、おもしろいことになるんですよね。バラバラになります。あえて言うなら「ネットワークにアクセスするのはユニットテスト?」で、Yesと答える人が若干少ないかな、ぐらいなものなんですけど、ほかはバラバラです。

というぐらいバラバラの歴史を、実は経てきました。ユニットテストっていう言葉がそもそも合意が取れていないんですよね。合意が取れていないのに「チームでユニットテストを書いていきましょう」って言っても、そもそも合意が、みんなイメージしているものがバラバラだったところで「ユニットテストを書いていきましょう」って言っても、そもそもがバラバラだからバラバラの方向に進んでいってしまいますねという話になります。

テスト駆動開発の流派

これは、自動テストの界隈とか、あるいは、私はテスト駆動開発に関して日本でまあまあ詳しい人間だと思うんですけど、テスト駆動開発の、何ていうか、流派としてもだいたいなんか2つぐらいあってですね。

なるべく本物を使えるんだったら本物、例えばさっきの依存先の話ですね。モジュールAのテストをしようとしています。B、Cも本物を使えるんだったらなるべく本物を使うほうがいいよ派の人、流派もあれば、「いやいや、いやいやいや、違う違う違う違う。なるべく偽物にすることによって初めてモジュールAだけを分離したテストができるんですよ。それがユニットテストです」という派閥もあります。どっちが正しいっていうのはないです。

なんですけど、こういう考え方っていうのが大きく2つ分かれたりしていて、どっちも栄えた結果、混乱する。どっちで育ったかによってバラバラの見解を持つというような時代になってしまいました。

ちなみにこっち側の、「分離すること大事」派の人は、中心がロンドンで始まっているのでロンドン学派っていったりします。ロンドンスクールっていったりします。このモックオブジェクトという概念をそもそも考えた人たちがロンドンの人たちだったんですね。

Steve FreemanとかNat Pryceとか、そういう人たちが中心になって、なるべく分離性の高いテストを書いていこう、それがオブジェクト指向設計を育てる、という流派がロンドン学派で、それもとてもいい考えだったんですけど。

こっちはどっちかというと、元祖テスト駆動開発流派っていうか、Kent Beckとかそのへんがいたシカゴとデトロイトとかから、デトロイト学派と呼ばれています。「なるべく本物を使えるんだったら本物がいいんじゃない?」ぐらいの感じの、古典派と呼ばれていたりしますというところなんですね。

これ、ちょっと脱線でした。というので、「ユニットテストのユニットって何?」っていうのは、そもそもブレブレです。そうすると、議論が前に進まないし統一見解も前に進まない、困った。という話なので、もうちょっと曖昧さのない明快な基準があって、かつそれが開発生産性とかCIの速さとか開発のリードタイムとかスループットとかそういったものに直接影響を及ぼすような明快な基準はないかなというところで。

Googleのテストサイズ概念

またさっきの本が出てきましたね。『Googleのソフトウェアエンジニアリング』という本に、テストサイズという考え方が出てきました。ユニットテストとかインテグレーションテストとかE2Eテストみたいなものではなくて、ちょっと別軸で考えてみようと。

テストにはサイズっていうやつがあります。Small、Medium、Large。小、中、大というわけですね。Small Testの定義は何か? Small Testの定義は、テストの実行が1つのプロセスに収まっているものをSmall Testというようにしようと。

1つのプロセスには収まっていないけど、1つのマシンに収まっているやつをMedium Testと呼ぼうと。1つのマシンに収まっていないのをLarge Testと呼ぼうという、3つのサイズに分けよう。これは、ものすごく明確です。プロセスに閉じていればSmall、閉じてないけど1つのマシンに閉じていればMedium、閉じていなければLargeというわけですね。

Medium Testはけっこう応用が利きます。例えば「Docker Compose」とかで、データベースのコンテナを立てて、「Selenium」のブラウザのコンテナを立てて、テスト対象のサーバーのコンテナを立てても、Mediumですよね。1マシンに収まっているから。

MediumっていうことはCIに載るんですよね。「GitHub Actions」でも「CircleCI」でもMediumでガンガン回せます。Largeはそういうわけにはいかないよね、っていう話になっているわけですね。

じゃあ、テストの安定性とか速度に最も影響を与えるものは何かっていうと、トップはネットワークアクセスなんですよ。ネットワークアクセスが一番遅くて不安定です。flakyさを一番生むのはネットワークアクセス。ということは、ネットワークアクセスを狭めていけば、速く安定していくという話になるんですね。狭めていく時に、インテグレーションテストというようなテストの範囲の語彙じゃなくて、サイズの語彙を使うと明快になってくる。

例えばGoogleのAndroid開発チームにおけるテストサイズ運用は、こんな感じになっていました。Small Testは、ネットワークアクセスしない、データベースアクセスしない、ファイルアクセスしない、No、No、Noが並んでいる。ものすごく速く安定して動く。

Medium Testは、ネットワーク、ローカルホストはもちろんつなぎますよね。データベース、ローカルホストならOKです。ファイルシステム、つないでよし。外部システムは推奨しない。マルチスレッドOK、スリープOKみたいな感じ。ちょっと遅くなります。Large Testは全部本物を使う。全部本物である代わりに遅くて不安定というようなかたちになってくるわけですね。

テストサイズとテストスコープの関係

となると、これ、さっきの我々が従来よく言っていたユニットテスト、インテグレーションテスト、E2Eテストって言っているけど、実は定義はあんまりはっきりしていなかったやつらと3×3の関係になるねっていう話ですね。

便宜上、テストの範囲を、スコープとしましょう。テストスコープっていうやつと、今出したテストサイズっていうやつは3×3になります。3×3で、このマスの中にあるのは、これは私の個人の意見です。お勧め度みたいな感じですね。

だいたいこの斜めの線っていうのが、基本的なラインになります。ユニットテストはなるべく単一プロセスで動いてほしい。インテグレーションテストは、だいたい単一マシンでコンテナ化して動くようになってほしい。このへんが普通のライン。E2Eテストが、お客さまの実際の操作と同じレベルの抽象度で、実システムを動かすテストがLarge Testなのは、それはしゃあないですよね。

ただここでがんばりすぎるんじゃなくて、CUJ、Critical User Journey、超正常系っていうやつですね。これに、なるべく絞っていきたいというようなラインになります。

となると、この真ん中が中心のラインで、中心から離れていくとコスパが良いなっていう領域もあれば、悪いなっていう領域もあります。こっち(上側)のほうに行けば行くほどコスパが悪くなっていきます。コスパっていうのは、投資対効果が低い、あるいは、書いても助けにならないとか、足手まといになっちゃうとか、そんな感じです。

こっち側(下側)に行けば行くほど、書いておくとかなりお得。この上にあるものよりももっと同じテストでもカバー範囲が広い。同じテストでも速度が速い、安定している、みたいなかたちなので、こっちに行くとちょっとコスパが良い。けど、E2EでSmallは現状不可能に近いけど、小さいシステムならできるかもね、ぐらいの感じです。

そういうようなところで、お勧めのコスパ領域。ここですね。単一プロセスに収まっているインテグレーションテスト、ユニットより以上の、いくつかのものがしゃべり合っているようなテストっていうのはコスパが良くなりますと。

もう1つ言及したいのは、ここですね。Largeだけどユニット。最悪なんですけどなぜかよく見かけるんですよね。サイズがLargeっていうことは、実システムを使って外部のネットワークアクセスもある状態で、でもテストしているのがユニット相当のものっていうと何かっていうと、エンドツーエンドテストのツールを使って画面のバリデーションロジックとかを網羅的にテストしているみたいなのを、すごくよく見かけるんですよね。

これが、あんまりコスパの良くない領域。ふさわしくない道具を使って網羅的にやろうとしているというような感じになっている。なぜふさわしくないかというと、Largeだからめちゃくちゃ遅くて不安定で、結果的にはflaky testが多くなって信じられなくなってしまうから、ということですね。

テストピラミッドの概念

ということで、じゃあ、そういった状態を保つために、つまり短い時間で到達するためにどうやってその状態を保っていこうかという話になるわけです。じゃあ、望ましいテストの比率というものはどういったものだろうなということを話していきたいわけです。

ここで出てくるのが、有名な「テストピラミッド」という概念です。このテストピラミッドのモデルを使うことによって、自分たちが書いてきたテストを中長期的に信頼性の高い状態を保とう、健康な状態を保とうというわけですね。

このテストピラミッドっていうのは、よく説明されるのはこの図です。E2Eテストがあってインテグレーションテストがあってユニットテストがあって、ユニットテストがなるべく多くて、全体を支えているべきである。横幅は、テストケース数だと思ってください。インテグレーションテストはもうちょっと少なくて、E2Eテストが一番少ない。ただ、どれも書く。

E2Eテストは不安定だから書かないっていうんじゃなくて、E2Eテストはこの中だと一番、忠実性っていうやつが高いんですね。忠実性っていうのは何かって、本物っぽさです。本物らしさです。E2Eテストは、本物のインフラ、本物のシステムを使うので、一番本物っぽい。

ユニットテストは、なんならモックとかスタブとか使うし、開発者の妄想は混ざるし、なので本物っぽさは一番低いわけですよね。その代わり、「速度」「決定性」「結果の安定度」というのも一番高いという話になる。

上に行けば行くほどスピードは遅くなり、コードが悪いというのとは別の理由によって失敗したりするようになる。決定性がなくなってくるというような話になるわけですね。というのがテストピラミッドのモデルです。

テストピラミッドの進化と議論

このテストピラミッドのモデルっていうのは、2003年、2004年ぐらいにMike Cohnという人が出したと言われています。そこから始まっています。言ったら、じゃあ、20年前のモデルなわけで、「テストピラミッドはもう古いよ」みたいな議論というのもたくさん出てきたりしています。

かつ、テストピラミッド自体も、明らかなアンチパターンというのと対比されて説明されることが多くて、これ、テストピラミッドが良いモデルだとすると、このアイスクリームコーン型っていうのが悪いモデルであると言われています。

ユニットテストがぜんぜんなくて、インテグレーションテストも少なくて、画面を使った自動テストというのが全体を支えていて、それよりも全体を支えているのが、人間が手と目でがんばっているというようなテストなわけですね。というのが、あまり良くない比率だと言われているし、なんならアイスクリームのコーンがなくて具しかないみたいな組織もいっぱいあったりするわけです、という感じですね。というのが、このピラミッドとアイスクリームコーン。

しかも、そのピラミッドっていうやつも、さっき言ったように、「いや、もう20年前のモデルだし古いね」というような議論もいろいろ出てきたりしていて、「いや、ピラミッドじゃなくてトロフィー型がいいんだ」、Testing Trophyっていうモデルとか、あるいはTesting Honeycomb、「ハチの巣型がいいんだよ」というようなモデルも出てきたりしています。

この2つに共通するのが、ユニットテストよりもインテグレーションテストを分厚くしましょうみたいな議論だったりするんですよね。なので、そうすると今度は、一般開発者である我々が迷っちゃうんです。テストピラミッドを信じてやっていこうと思ったら、「テストピラミッドはもう古い」とか言われているし、「時代はトロフィーだ」とか言われているし、みたいな感じで困っちゃう。

けど、問題はここの語彙なんですよね。ハニカムとかトロフィーとかピラミッドとか全部、E2E、インテグレーション、ユニットとか、さっき言ったブレブレの軸で話をしている。

なので、「Testing Trophyのモデル、良し!」と言っている人の、あなたにとってのユニットテストとは何ですかっていうのを聞いてみると、こっちだったりするんですよね。こっちっていうのはつまり、ロンドン学派だったりすると。

そうすると、「もっとブレのない定義ってなかったっけ?」っていう話なんですよね。だから、このピラミッドも、この縦の軸にブレがあるじゃんっていう。でも、ブレのない基準っていうのは、我々は、もう話をしてきました。テストサイズですね。

テストサイズに基づくピラミッドの再構築

というので、テストサイズでピラミッドを構築したほうが、基準が安定しますし、議論のブレがなくなりますし、それだけじゃなくて、さっきのトロフィーとかハニカム、これ、ユニットってここのことを言っていたんですよね。でも、このなるべく本物を使うテストでもプロセス内に収まっていればSmall Testですよね。

というので、先ほどのトロフィーとかハニカムもテストサイズの観点で並べ直してみると、ピラミッド状に近づいていくというような話があるわけです。ユニットテストとかインテグレーションテストの解釈のブレによって形がブレブレになってしまったけど、テストサイズで並べ直すとおおむねピラミッド型に近づいていきます。

ということで、テストピラミッドを、サイズでピラミッドを作っていきましょうと。そうすると、ビルドパイプラインも最適化をすることができます。Small Testが失敗しているのにLarge Testを動かす意味なんてほぼないんですよね。

なので、テストサイズ自体は、例えばディレクトリ名とかタグ付けとかでSmall Testだけ最初に動かす。これ、あっという間に動きます。Small Testが失敗したら、もう打ち切りでいいですよね。Small Testが成功したらイメージのビルドを始めてMedium Testを動かして。

Large Testは不安定なので、なんならビルドパイプラインから外してクローンみたいなやつで動かしておくとかのほうが運用としては安定したりしますというような感じで、これがビルドのスループットそのものを高めることにつながっていきます。というところなので、サイズが、二重、三重にお勧めですという感じです。

(次回につづく)

<続きは近日公開>