2024.12.10
“放置系”なのにサイバー攻撃を監視・検知、「統合ログ管理ツール」とは 最先端のログ管理体制を実現する方法
次世代タクシー配車サービス「MOV」におけるテスト事例紹介(全1記事)
リンクをコピー
記事をブックマーク
toku_bass氏(以下、toku_bass):「次世代タクシー配車サービス『MOV』におけるテスト事例紹介」というテーマで発表します。オートモーティブ事業本部、Twitterアカウントは@toku_bassです。よろしくお願いします。
(会場拍手)
社会人になってからやっていた言語は、C・Perl・Elixir・Goで、Goは1年ぐらいです。なので型のある言語は久しぶりです。ディー・エヌ・エーには入ってまだ1年経っていないです。
最初にMOVの紹介をさらっとして、どういう環境で開発をしているのか。あとは「テストを書く上でこういうことは気にしている」という方針の話と、並列テストをするために必要なテストデータの生成。残念ながらDIできないコードが存在するので、その打開の方法と、その他時間があれば発表します。
MOVの紹介です。ざっくりいうと、App StoreやGoogle Playストアでユーザアプリをダウンロードして、ユーザさんがすぐにタクシーを呼べるというサービスです。2018年4月に神奈川県限定でリリースされました。
僕が入社したのが2018年11月で、その1ヶ月後に東京でリリースされ、バンバンテレビで放送され、すごく負荷がきました。2019年7月に大阪と京都でもリリースされ、またテレビで放送され、またわりと負荷がきました。
配車アプリは「タクシー呼ぶだけかな?」という感じがあるんですけど、タクシーの中にも複数のAndroidの端末がありますし、あとは事業者様のところに管理画面があるので、それのためにやることがいっぱいあります。
あとはタクシーやユーザがどこにいるのかを管理しているサーバもありますし、いろいろなサーバがあります。今回の話は、サービス初期から存在する一般的なAPIサーバで使っているテストです。
環境としては、App EngineのGoの1st generationを使っていて、DBはCloud SQLの2nd generationを使っていて、あとはCloud Datastoreがあります。
テストの実行方法は、ローカルの手元のPCでgoapp testを実行する方法とGitHubのプッシュに連動してCircleCIで動くテストと、あとはchatbotE2Eです。E2Eといってもサーバで完結しているんですけど、配車系のテストをしています。
QAを依頼したものの、バグがあってQAが止まっちゃうことがあったので、そういうことをなくすために作られました。
テストを書く上での方針ですけど、今見ているテストケース以外に依存している暗黙のデータというものを用意してはダメで、テスト全体が始まる前にユーザを2人ぐらい追加しますみたいな、そういうfixtrueを使わない。あとはGoだとtesting.TのParallelがあって、単体テストが並列で回せるんですけど、これをちゃんと使っていく方針です。
テスト全体に関わるfixtureを使わないものです。マスターデータは別に使ってもいいです。マスターデータはゲームで言うところのアイテムの攻撃力や回復量などの企画で決まっているデータです。
マスターデータ以外の「ユーザを2人入れておきます」みたいなデータは、ユーザを1人追加するAPIがあってそれをテストして、「ユーザが3人になりました。OKです」というコードが書かれ始めて破滅します。
「そんなバカな」「ちょっと考えすぎだろ」みたいなことも思うかもしれませんけど、前職で10年物のすごいやつをメンテナンスしていてかなり大変だったので、本当に気を付けたほうがいいです。
並列でテストを実行するんですけど、APIのテストをDBまで共通してやろうと思うと、グローバルな状態を持っているDBがネックになってきます。なので、user_id=1みたいな固定値をテストでは書かれたくないと。今いったんは書かれているんですけど、ランダムにデータを生成するためのbxcodecさんのfakerというライブラリがあります。ざっくり言うとfactorybotみたいなものです。
このときはまだfakerに不満があって、いろいろ手を尽くすんですけど、数ヶ月前にv3が出てこれはけっこういい感じです。ユーザ定義のpluginも簡単に書けるのでこれがおすすめです。v2の頃からfaker以外のライブラリはあまり良いものがないと思っていたので、たぶんこれしかないんじゃないかなと思います。
実際にどうやって使うのかと言いますと、Userというstructにfakerというkeyでgotagがあるんですけど、emailというgotag valueを入れておくとemailの形式で適当なemailを生成してくれます。emailはfakerの持っているデフォルトのプラグインです。とくに何も単語を書かないとString型に合わせてそのデータを生成してくれます。
ここでemail以外にも作りたいとなってきたら自分で使うgatag valueを作れるので、これでごりごり書いていけます。
MOVでの利用例なんですけど、さきほど構造体のgotagにfakerと書くと言ったんですけど、チームメンバーから「プロダクトコードにテストのための記述は嫌だ」という意見が出て、そうだと思いまして。静的にコードを解析して関数名のprefixにfakeとついている構造体を生成して、それにgotagを付けました。
あと、プライマリキーはランダムに生成すると偶然被ることもあります。前職で大きいテストを回していたんですけど、そこそこ被ります。なのでtodoなんですけど、id採番のアルゴリズムを採用して原則的に被らないようにしていこうとしています。
次は「DIできないコードと戦う」。一般的にDIとよく言われるんですけど、Goではtime.Now()がどうしても撲滅することができなくて、絶対出てきます。
こいつをどうしたかというと、time.Nowの関数定義自体をどこかのpackage変数に保存しておいてラッパー関数を経由して呼び出します。それでテストのときだけpackage変数を書き換えられるsetterをビルドに含めます。この方法としてbuildタグを使っています。
ラッパーはどこのご家庭にもあるutilパッケージを作って……このnowFuncという変数にinit時にtime.Nowの関数を入れておいてプロダクションコードでutil.Now()と書いておくと、ふだんはtime.Nowが呼ばれるんだけど、テスト時はSetNowFuncという関数が使えるので、好きな固定値の時間を設定できます。
buildタグとはファイルの一番上の行にあるbuild testという記述の部分です。go testコマンドに--tagsオプションがあるのでそこにtestと書くとビルドの対象になります。
使っているところはこんな感じで、見ての通りでロックしているので、この手法を使うデメリットとしては並列で動かない点です。そこは甘んじて受け入れようかなと思っています。
tipsとして1つ言っておきたいのは、export_test.goを用意してsetterを作っていたブログを参考にして、util.Nowを作りました。だけど、うまく動かなくてすごいハマって「なんでだろう?」と思っていました。
utilに作っているので、例えばこのコントローラーのテストでutil.Nowを使いたい時にcontroller_test.goとかはビルド対象に含まれるんですけど、utilはテスト対象じゃないから、utilのディレクトリの下にexport_test.goというファイルを配置しても、それがビルドされないんですよね。なのでbuildタグで強制的にビルドに含めています。
あとは、これは完璧に力技なんですけど、Client実装をファイル単位で偽装します。これもbuildタグでやっていて、これはあまり説明したくないんですが(笑)。
シンプルなものだとこんな感じです。本来ならTagメソッドがオリジナルで呼ばれるんですが、package変数のmockTagが呼ばれるようになっていて、テスト時に、package変数に好きな関数を設定するやり方です。
これはかなりの業技なので、これを使わないといけない悲しみというのはあるんですが、「プロダクションコードでif(isTest)みたいなものは混ぜない」「リファクタリングをしないとテストが書けません」みたいなことは言わないという心意気はわかってほしいなと思います。
その他のTipsについて、よくあるのがtesteratorとpstestなので、その話をしようと思います。testeratorは有名なので知っている人もいるかもしれないんですけど、GAE用にテストサーバを上げると3秒ぐらいかかるんですよ。これをテストごとに上げ下げしていると、とてもじゃないけどやってられないので、ずっと上げっぱなしにするものです。
SpinUp関数を呼ぶと、すでにサーバが起動していたらそのコンテキストかサーバの情報を返してくれて、起動していなければ改めて起動をしてから情報を返してくれます。
検索すると、はてブとかQiitaが引っかかるんですけど、たまにテストサーバが落ちてまた起動に3秒かかるので「なんでだろうな?」と思ってソースコードの中を見てみると、内部カウンターを持っていて、SpinUpで1上がってSpinDownで-1になるんですけど、これが0になるとサーバが落ちるんですよ。
なので、テストの実装者が自分でSpinUpして行儀よくSpinDownを書くと落ちるんです。テストを始めるときに+1しておいてもいいんですけど、SpinDownが何故か2回書かれていることが往々にして世の中にはあるので、package全体で利用できるラッパー関数を用意しておいて、それで中央管理をしていきます。なのでSpinUpを直接呼ばないで「この関数を使ってね」としています。
最後です。Cloud PubSubのテストでpstestがすごい便利で、pstest.NewServer関数が擬似サーバを返してくれるんです。これにクライアントがリクエストを投げると結果が返ってきます。
これを見つけた経緯がエディターの定義ジャンプでPubSubのライブラリにとんだら、そこにテストファイルがあったので、「どうやってテストしているんだろう」と思って見たらpstestを使っていて、「あぁ、こういうふうに公式のライブラリを見に行くのはいいなぁ」と思って、この場で知見を共有しておこうと思いました。
ただ、BigQueryとかにはなかったです。公式のテストを見に行っても公式でも「TODO:このテストはまだできていません」とコメントが書かれている現実が8割ぐらいです。
以上です。ありがとうございます。
(会場拍手)
司会者:すみません。自分から質問です。pstestは公式パッケージ上の中に入っているパッケージみたいなやつですか?
toku_bass:そうですね。
司会者:ありがとうございます。もう1件質問が届いております。「idを採番するのにsnowflakeを使うのは?」という質問です。「他に例えばUUIDとかstandardぽいものがありますけど、そういったものはご検討されましたでしょうか?」
toku_bass:現状はUUIDじゃなくて数字で管理をしているので、snowflake的なやつがいいです。
司会者:はい。どうもありがとうございました。
2024.12.10
メールのラリー回数でわかる「評価されない人」の特徴 職場での評価を下げる行動5選
2024.12.09
10点満点中7点の部下に言うべきこと 部下を育成できない上司の特徴トップ5
2024.12.09
国内の有名ホテルでは、マグロ丼がなんと1杯「24,000円」 「良いものをより安く」を追いすぎた日本にとって値上げが重要な理由
2023.03.21
民間宇宙開発で高まる「飛行機とロケットの衝突」の危機...どうやって回避する?
2024.12.10
職場であえて「不機嫌」を出したほうがいいタイプ NOと言えない人のための人間関係をラクにするヒント
2024.12.12
会議で発言しやすくなる「心理的安全性」を高めるには ファシリテーションがうまい人の3つの条件
2024.12.06
嫌いな相手の行動が気になって仕方ない… 臨床心理士が教える、人間関係のストレスを軽くする知恵
PR | 2024.11.26
なぜ電話営業はなくならない?その要因は「属人化」 通話内容をデータ化するZoomのクラウドサービス活用術
2024.12.11
大企業への転職前に感じた、「なんか違うかも」の違和感の正体 「親が喜ぶ」「モテそう」ではない、自分の判断基準を持つカギ
PR | 2024.11.22
「闇雲なAI導入」から脱却せよ Zoom・パーソル・THE GUILD幹部が語る、従業員と顧客体験を高めるAI戦略の要諦