目次
■信頼性と品質 / システム運用と管理
2022.06.08 2024.03.05 約8分
近澤:ユニットテストでそこまで時間がかかるようになるまでは相当なケース数が必要になりますが、E2Eテストだと比較的すぐに到達してしまうので課題になりますよね。毎回全部回す必要はなく「実行順序を決める」ことを大事にしてみるのはどうでしょうか?
要は実行順序を決め、コントロールして、出来るだけ早く問題を検出できるようにします。ツール使用の有無に関わらず、怪しいところを優先的にテストすることが大事だと思います。
また、普通にパラレルにするのも手段ですが、札束で殴るみたいな話になってしまいますね(笑)和田さんはどうでしょうか?
和田:まず整理すると、実行に1日はかかり過ぎです。
私は現場で絶対防衛ラインを10分にしています。全件を10分で動かします。10分を超える場合は、10分以内に収まるように並列化して対策をします。
遅くなる原因として、このあたりが考えられます。
和田:上記であげた問題は中長期的にピラミッド型にしていく必要があります。E2Eテストでカバーしている論理的な重複を減らし、ユニットテストやインテグレーションテストを書ける体制にしていくべきでしょうね。
ただし、これは時間がかかるので短期的にはテストの実行を間引きするしかありません。毎回全件動かすと時間がかかるので、いくつかアプローチを紹介します。
和田:Googleはテストの実行結果を1件ごとにデータベースに格納し、分析しています。
つまり、テスト実行結果自体をデータとしてデータエンジニアリングを行っています。こうした結果に基づいて、テストの度に全件実行ではなく最も有効なテストのサブセットをどのように計算するかを Google は取り組んでいます。
要するに毎回全件動かしていると多すぎるので、どうやって有効なサブセットを算出し、実行するかということです。
和田:Googleのように大規模なデータエンジニアリングまでは行わずに簡単にサブセットを作るには、一番簡単なところだとディレクトリツリーやツリー構造になります。あるディレクトリ以下のテストだけを実行するというようなものですね。あるいはテストサイズをS・M・Lのようにタグ付けをしてフィルタリングして実行するやり方も有効です。
タグはツリーと違って、様々な切り口を作れます。「このタグはデータベースを使っています」や「この機能のテストです」といったように細かくタグ付けをしていきます。該当するタグだけテストを回したり、外したりといったことが可能になります。
つまり、色々なサブセットを作る為のメタデータをテストコードに与えていくことでより良いサブセットが作れるのではないでしょうか。
末村:結局網羅性を高めていくとテストが増えるのは必然かと思います。特にE2Eテストだとよくあることですが、組み合わせ爆発が起きてしまうこともあるでしょう。そうした際には必要に応じてモックを作ったりして、テスト自体を小さくする対策を取るものだと思いますが、いかがでしょう?
和田:ユニットテストとE2Eテストを同じ感覚で設計すると、個々のE2Eテストが小さくなりすぎる傾向があります。E2Eテストは1回の実行にかなり時間がかかるため、ユニットテストのような細分化したテストよりもっと長いシナリオテスト的な形であるべきです。E2Eテストはケース数を少なくして、中はがっつり書かれている形で問題ありません。
和田:ユニットテストは価値観が全く逆で、がっつり書かれているユニットテストは悪で、1つの内容を1つのテストにしていることが望ましいです。テストコード設計のポリシーが異なりますので、同じ感覚で粒度だけ異なるテストコードを書いてしまうと、小さいE2Eテストが量産され、それが全体の実行時間を蝕むことが多いです。
末村:あるあるですね。
ちなみに詳しくない方に説明すると、先程からお話に出ているテストピラミッドはこの図の通りです。これは初出はマーティン・ファウラーでしたっけ?
和田:いえ、マイク・コーンが2004年頃に提唱し、書籍としては『Succeeding with Agile』が初出です。そこから広まって今に至ります。
末村:テストピラミッドも今は派生系もたくさんありますね。この図に縛られるわけではなく、和田さんが仰ったようなポリシーを定めるような話になると思います。
末村:ケースバイケースな回答になる気がしますが、和田さんが仰られていたようなユニットテスト、E2Eテストなどテストのレベルでバランスをつける対応方法があると思います。
その他、抜け漏れに関する網羅性についてアイディアありますか?
近澤:どのレベルの話ですかね?ユニットテストであればとにかく、カバーしていく話になりますね。
和田:ホワイトボックステストであれば機械的に測ってカバーすれば良い話ですが、大体問題になるのは仕様のカバレッジの方ですね。これはプロダクトコードからは見えてきません。
私の場合はテストの実行結果をツリー形式にして仕様のツリー構造と対応付けています。
ライブコーディングの中でも、後半はテストをドキュメント(動作する仕様書)にすることを行っています。これが何かというと、テストの実行結果って普段はドット1つだったりしますよね。で、失敗したら赤いFが出てくる、みたいな。
しかし、テストの実行結果をテストクラス名やテストメソッド/関数名でツリー状に表示してくれるオプションもあることが多いんです。そのオプションを活用して、テストすべき内容がテストされているかを確認します。要するにプロダクトコードやテストコードではなくテストの実行結果を見て、テスト対象の仕様の構造に対応しているか、抜け漏れがないかを見ます。
プルリクでテストコードのレビューを依頼された時もテストの実行結果をプルリク本文に貼り付けるようにお願いしています。そうすることで抜け漏れなくテスト出来ているか、ツリー構造におかしいところがないかをチェックしています。
末村:仕様に対してテストが書かれているかを見るのですね。
和田:具体的なテストコードの書き方とは別に、テストケースの設計、何をテストするか、仕様の整理整頓などがソフトウェアテスト技法を学ぶことで身に付きます。
末村:ここでテスト技法と呼んでいるのは、どういうことでしょうか?
和田:そうですね。例えば同値分割、境界値分析、デシジョンテーブル、状態遷移、ペアワイズ法や直交表などですね。またクラシフィケーションツリーは正にツリー構造で仕様を整理してテストと対応させる技法です。
末村:先程、E2Eテストで細かくバリデーションを書く話がアンチパターンとして挙げられていました。確かにE2Eテストで同値分割などを活用するとテストケースが膨れ上がってしまうため、そうしたものを確認したい際はユニットテストを活用するといった想像が出来ました。
和田:E2Eで網羅性を求めるのはやめようという話を現場ではよくします。
E2Eは深いシナリオが大事です。昔、海面レベルのユースケースという言葉がありました。今でいうとクリティカル・ユーザー・ジャーニーです。目的を持ったユーザーが自分たちのサービスにアクセスしてきて、目的を叶えて去っていくまでをテストしようと伝えています。バリデーションの様な細部ではなくシナリオをテストするのが重要です。
和田:どのテストではどういう目的でどういったバグを見つけにいくのか、もしくは何を担保するかをチームで合意して、設計することが大事です。E2Eでは何を狙うのかが定まっている状態が望ましいですね。
分業体制になってしまうと、どうしても自分の責任範囲で全てカバーしようとするため、見えている範囲で網羅しようとしてしまいます。特にE2Eテストだけを書く人が独立したチームにいるとこのアンチパターンに陥りがちです。
そうではなく、チーム全員で様々なレベルのテストを書くことが大事です。
和田:これはレガシーコードと呼ばれる、いわゆるテストコードのないコードのことですね。レガシーコードの手強いところは、テストのことが考慮されてないので、後からもテストを書きにくいことです。そもそもテストが書きにくい構造なため、構造を変えないとテストが書けないパターンも多いです。
安全に構造を変えるにはテストで安全性を担保したいが、そのテストを書くには構造を変えないといけないというデッドロックに陥ってしまいます。これがレガシーコードのジレンマです。これを解決するには、ある程度無理を通す前提で2つの戦略があります。
和田:1つはテストを書きにくいのを承知の上で大外からテストを書いて、カバー範囲を広げることで内部の構造をテストが書けそうな形にリファクタリングする方法です。この手法ではE2Eテストが活躍します。
どれだけ Fat Controller だろうと、コードが汚かろうと、ブラウザレベルで見ればリクエストしたらレスポンスが返ってくるという一種の割り切りで進められます。そのため、対レガシーコード戦においてE2Eテストの役割は通常よりも大きくなります。
ここでは、E2Eテストがリアーキテクチャを支える柱になるのですが、その勢いで全てをE2Eでカバーしようとするとテストピラミッドが崩れてしまい、不安定なテストが増えてしまいます。ですが、対レガシーコード戦の初期フェーズにおいて、E2Eテストで砦を作ることは非常に有効です。
E2Eテストでカバーしている範囲をリファクタリングしていくとユニットテストが書きやすい構造に比較的安全に移行していけるので、その段階になれば不安定になっているテストピラミッドを再構築することができます。そして、ユニットテストを下から積み上げていくフェーズになったら、E2Eテストはあまり増やさない方が良いです。
初期フェーズでカバー範囲を広げる為に作ったE2Eテストもインテグレーションやユニットテストに移植して、E2Eテストを消すことで健全なピラミッドのバランスを構築できます。これがまず有効な戦略その1です。
和田:2つ目はテストコードが無いのを承知で、比較的安全と思われる手法で既存のコードに穴を開け、そこからテストコードを書いていく戦略です。これは『レガシーコード改善ガイド』の中でも数多く紹介されているテクニックです。例えば、静的解析や IDE など安全にコードを改変できるツールとペアにしてテストコードを書けるレベルまでテストコードなしで既存コードに手を入れていくのも有効な手法です。
現実的にはこの2つを組み合わせて取り組むことになります。E2Eテストで外からカバーしながら、中身は比較的安全な手法でコードの変更に取り組んでいくといった合わせ技を取ることが多いですね。
近澤:戦略1は特に私も共感します。レガシーコードのジレンマに陥っているお客様とお話することは私たちも多いです。そうした中で Autify を使ってもらい、最初に E2Eテストから作って、内部改善を始めて、レガシーからモダンなスタックに変えるケースが多くあります。
テストピラミッドもピラミッド構造から考えると、ユニットテストから埋めなければならないと思いがちですが、既にアイスクリームコーン型になっているものは上から攻めて、次に上下同時に攻めていき、段階的にピラミッド型にしていくことが現実的な解決策になると再認識しました。その点を踏まえると、今回のお話は実際に困っている人も多いと思います。
末村:この話と関連して、私が初めて書いたテストコードの話を笑い話として供養させてください。私が PHP のエンジニアを始めて、まだ1年経っていない頃の話です。前任者から引き継いだ for loop が四重になっていて、その中にデータベースのアクセスが入っているコードがあって……。
近澤:ヤバい(笑)
末村:そうなんです。本番環境のコードを差し替えるぐらいしかできない状況でした。
cURL で変更前後の HTML を引っ張って、符合したらユーザーに「大丈夫だったよ」とメールすることをやってました。そんなガバガバなテストですが、私にとっては初めてのE2Eテストかつレガシーコードとの戦いでした。レガシーコードと戦う際にE2Eテストを入れることは実体験を通してよく理解が出来ました。
和田:実は今のお話はとても示唆的です。
対レガシーコード戦の場合、どうあるべきかは既に失われていることが多いです。ですので「どうあるべきか分からないが、動きが変わったことは分かる状態」を目指します。正しいかは別として動きが変わったことが分かる状態を築けば、少なくとも既存の動きを変えずにコードに触れる範囲が広がります。このように、現在どう動いているかを写しとるテストをレガシーコード改善の作戦の中で仕様化テスト(キャラクタライゼイションテスト ) といいます。
この仕様化テストは対レガシーコード戦において筆頭の武器の一つです。とにかく、既存の動きをなぞって、カバー範囲を広く取っていく。E2Eやインテグレーションテストのレベルで広くカバーすることができれば、もぐらたたき的にバグが生じることが少なくなります。
そもそも、既存の動きが壊れたかどうか分かるようになっているので、壊れていないうちはコードに手を入れることが可能です。
こういったテストを対レガシー戦の初期では書きます。そして、テストのタグにcharacterizationと目印を付けておきます。そうすることで後からコードを見た人は、それがあるべき論で書かれたテストなのか、そうではなく現状の動きが変わっていないかを調べる為に既存の動きをテストしているのかを見分けることができます。
末村:ありがとうございます。
今、話を聴きながら Autify は現在レコード&プレイバック形式、つまり、実際のアプリケーションを操作して、それを基に実際のテストケースを作成するアプローチを取っています。これが仕様化テストの作成には非常に適していると感じました。
レコード&プレイバック形式も議論の的になりやすく、便利という人もいれば使いづらいという人もいますが、このお話を聞いて使うタイミングによってはいずれにも転ぶものだと感じました。
そのため、レガシーコードと戦う皆様には是非 Autify を……。
あれ、これ宣伝して良い回でしたっけ?(笑)
近澤:一応ね(笑)
ただ、インプットとアウトプットがあっていれば良し、という考え方に対しては実際にレコード&プレイバック形式は有効です。
末村:片想いしてますねぇ〜
近澤:でも、E2Eテストだけに想いを馳せる必要はないですよね。
そもそもユニットテストは今すぐ書くべきです。それこそ「ユニットテストを書けば、最終的に楽できるよ」というのがそれこそ和田さんのお話に繋がるところなので。
和田:難しいのが「疎かにする人に能動的に書いてもらう」ことです。
テストを書き慣れている人はテストを書いた方が早いことをよく知っています。ですが、テストコードを書き慣れていない人はテストを書くのに時間がかかる、あるいはそもそもテストコードの書き方を学ぶ必要性があるかもしれません。
つまり、学習コストと実装コストが両方かかる状態な訳です。こうなってくるとテストコードを書く方が遅くなってしまいます。そのため、2人の価値観や意見が食い違うことはよくあるパターンです。
このようなパターンに対しては学習コストを軽減しないといけません。そして、学習コストを軽減する上で一番良いのは真似してもらうことです。「コピペでいいのでこんな感じのテストを書いて欲しい」という形でお願いすることです。
もしくはペアプロやモブプロのような形から入るのもいいと思います。
最初から網羅性など難しいことを考える必要はありません。動作確認レベルの簡単なテストを書いて動かすだけで分かる軽微なミスがプログラミングには多く存在します。ハッピーパスや正常系だけでも良いのでまずは真似してもらい、コピペしてもらえる様に背中を見せることが重要です。
近澤:私もエンジニアをやってた時はテストから書いた方が早いということを理解してやっていました。キッカケは入った会社でペアプロをしてくれた人が最初にテストから書いたんです。テストを書いて、回しながら実装を進めていて、「こっちの方が断然楽だな」というのを見て学びました。
末村:私は、2冊あります。1冊目は自動テストの本ではないですが『Agile Testing Condensed』という本です。日本語版もブロッコリーさんこと風間 裕也さんが翻訳されているのでもしよろしければ手に取ってみてください。
もう1冊がBDD Booksというシリーズ本です。BDD というのは Behaviour Driven Development、振る舞い駆動開発と呼ばれる自動テスト方法論の1種です。こちらの本は3部作になっているのですが、このシリーズの第一部には BDD の話はあまり載っておらず、とにかく、どうやってテストケースを探していくか。自分たちのプロダクトの仕様や、達成しようとしているものを皆で話し合ってテストケースを考えることだけにフォーカスしている本です。
こちらにも翻訳本が出ています。こちらの本に関しては翻訳レビューに私も携わっておりますので是非、読んでみてください。
和田:入門書としては2017年出版のジョナサン・ラスムッソンが書いた『初めての自動テスト: Webシステムのための自動テスト基礎』をおすすめします。
ちなみにジョナサンは『アジャイルサムライ』を書いた人でもあります。また、『ユニコーン企業のひみつ』も彼の作品です。とにかく、彼は初心者向けにわかりやすく説明するのが抜群に上手いので、Webシステムの自動テストを理解するための1冊としてとてもオススメです。読書会形式の勉強会をやるのであればこちらの1冊が良いです。
あとは、皆で問題を実際に解いたり、手を動かしながらやろう、ということであれば『ソフトウェアテスト技法練習帳~知識を経験に変える40問〜』がオススメです。これは書き込み式のドリルみたいな感じです。ソフトウェアテストの設計を手を動かしながら学べます。
そして、当たり前ですが回答例があるので、自分が分からなくても本が答えてくれます。そのため、先程お話したような分からないなりに手が動く、手を動かしているうちに理解する体験が得られる本になっています。近い本だと、秋山(浩一)さんが書かれた『ソフトウェアテスト技法ドリル: テスト設計の考え方と実際』も良いと思います。
末村:質問者の方に1つアドバイスすると、ソフトウェアテストを学ぶ時にソフトウェアテスト設計や、テスト技法を学ぶことはとても良いことだと思います。ですが、開発プロセスそのものに対する理解度が浅いとテストのところだけに焦点が当たりすぎてしまいます。
先程『Agile Testing Condensed』を紹介したのもその意図があって、この本はアジャイル開発の中でテストをどうしていくかという話なのでバランスが良い本になっています。
そのため、テストの勉強をするといってもテストにこだわりすぎず、開発プロセスの中でどうやって質を上げていくか、あるいは質とスピードを上げていくかに注意をして勉強会を進めていくと良いと思います。
和田:本当に仰る通りですね。
テストフェーズ、テストプロセスによって質を上げる、あるいは保つのではなくて、開発プロセス全体の中で取り組んでいくことが重要です。そもそも後からテストをする時代ではなく、全体から質を作り込んでいくことが今の主流です。
そもそも不具合の少ないソフトウェアを作り、不具合の少ないソフトウェアに対してテストを行っていく、双方的なプロセスになっていくので全体感というのは大事ですね。
末村:テストで品質は上がらないのは原則の1つですからね。
和田:最終的にはこの図(holistic testing)のようになっていくことが理想ですね。
近澤:質を確保するための予算計上や負債解消のための予算確保は技術がわかっていれば理解できますが、技術がわからない経営層に説得するのは確かに難易度が高そうです。
末村:制約が多く厳しい状況ではありますね。少ない予算でコードも綺麗にして、ビジネスも成長させろ、という話ですよね。どこに投資するかを皆で考える前提が出来ていないのかなと感じます。
近澤:やはり負債解消に対する予算確保はどうしても技術が分からないと納得してもらうのは難しいと思いますが、和田さんの場合どういう風に説明されますか?
和田:これは実際、難易度が爆上がりするんですよね。(笑)
最近、技術畑出身の経営者や CTO がいる会社が増えてきて、前よりは話が通りやすくなったと思いますが、それでもやはり通じない会社もまだまだあります。
基本的には相手にわかる語彙で話をすることが大事だと思います。分かりやすいのはお金と時間の話に換算することです。
例えば時間の話にすると技術的負債の影響で1年前は4週間で開発出来ていたものが6週間かかるようになってしまっています、といった話をします。
相手の語彙で話すことは翻訳コストが非常にかかりますが、避けては通れないことでもあります。そのため語彙を手に入れる必要があります。例えば広木大地さんの『エンジニアリング組織論への招待』が1冊目としては良いのではないでしょうか。
とにかく相手の立場に立ってコミュニケーションを取ることが重要です。ただ、申し上げた通り、観測範囲では技術理解の高い経営者がとても増えているので昔よりはだいぶ楽になったと思います。
近澤:経営者にIT理解がないとそもそもビジネスが上手く回らない時代なのかもしれませんね。
末村:これは私が知りたい質問でピックアップしました。例えば、和田さんが編集された『Engineers in VOYAGE』は私の好きな1冊でもあります。
和田:VOYAGE グループ(現:CARTA HOLDINGS)は良いですよね。あそこの良いところは技術者が事業を考えていることです。事業の問題を技術で解決する、という順番になっているので、皆が事業に対してオーナーシップを持っており、自分ごととして取り組む文化がはっきりしているので良いですね。
あと、ドキュメント系やグループウェア系のサービスを作ってる会社はドキュメントを作ったり、それをメンテナンスしたりするのが上手いと思いますね。
はてなさんは取引がある訳ではなく、ゲストとしてお邪魔しただけですが、そういったシーンが垣間見えましたね。また、サイボウズさんもそうですね。サイボウズさんは SQL アンチパターンの社内勉強会にお邪魔しました。
ドキュメントには社内の SQL アンチパターン事例がまとまっているのですが、その際の意思決定をハイパーリンクで全て遡れるようになっていました。全ての意思決定を過去方向に遡れるようになっていて、これは良い文化だなと印象に残っています。
末村:継続的にテストコードを増やすことが目的であれば、「コードを変更するときに、カバレッジを下げない」を目標にしてはどうでしょうか?根拠のない目標に右往左往するよりも、品質を保ち、上げていくモチベーションに繋がると思います。
末村:当たり前ですが、Autify も万能ではありません。E2EでやるべきではないテストをE2Eで無理やりやってしまうとどこかで無理が生じます。それよりも、「メンテナンスがしんどい」という問題にしっかり向き合って、内部品質を高めていくのが一番の早道になると思います。
末村:テストレベルにもよりますが、読んで理解できなかったり、修正の邪魔になるテストコードはもはや誰の役にも立っていないので、消してしまったほうが良いです。消しても安全なテストコードかどうか自信が無い場合は、より小さいテストレベルに分割して移植した上で、元のテストコードを削除するのが良いと思います。
和田:スピードはあまり細分化することなく、4 keys におけるリードタイム(あるいはサイクルタイム)とデプロイ頻度で測るのが良いと考えています。現状が分かった上で、明らかなボトルネックの解消のために、どの工程にどのくらい時間がかかっているかを調べることもあります
和田:flaky なテストはタグをつけて管理していきます。非決定的な動作をする flaky テストは放置しておくとテストスイート全体の信頼性を損ねます。タグをつけることで flaky なテストたちを「隔離スイート」で動作させることができます。 flaky タグのついたテストは実行対象から除外したり、逆に flaky タグのついたテストだけを再実行したりなど、テストにメタデータがついていればいろいろやりようはあります。
和田:おっしゃる通りで、特に MTTR に関しては深く関係します。しかし、SRE チームに丸投げして任せるのではなく、全員で質とスピードに関して責任を負っていくのが重要であると考えています。
和田:いまのところ全くありません。なぜなら、私の今回の講演は新規性があるわけではなく、書籍から得られた知見をピックアップしてまとめ、方向付けする性質のものだからです。