支払い.comのカオスで整備中なエンジニア組織について

支払い.com で主にバックエンド全般を担当しているエンジニアの水村です。

支払い.comのエンジニア組織はまだまだ整備中で課題も多く、カオスな状況です。そんなカオスな状況を改善するため、やらないようにしていることや、意識的にやっていることなどをまとめました。

支払.com のサービスについては、支払い.comのサイトをご覧いただけるとめちゃくちゃ嬉しいです。(https://shi-harai.com/

TL;DR

  • 我々がやらないようにしていること
  • 意識的にやっていること
  • 開発フロー
  • エンジニア紹介

やらないようにしていること

いきなり話がブッ飛びますが、エンジニア組織でやらないと決めていることは次の通りです。また、いくつかの項目については詳細を記載しました。

アジャイル開発やスクラム開発をベースに開発を行っているのですが、デイリースクラムやスプリントなど、検討した結果採用していない手法もあります。

  • エンジニア抜きでの意思決定
  • スプリント
  • 無駄な会議
  • デイリースクラム的な毎日のミーティング、稼働の管理
  • 過剰なタスクの管理
  • 休日、深夜の Slack メンションや DM を遠慮して控えること
  • Slack DM によるコミュニケーション
  • ダメな物事を無視する行為
  • 偉そうにすること
  • 聖域を作ること
  • 働きすぎること

エンジニア抜きで意思決定をしない

エンジニアが意思決定に大きな裁量と権限を持っているので、基本的に意思決定にはエンジニアが関与します。

エンジニアが意思決定に大きな裁量と権限を持っているのは、カード側の事業も同じです。支払い.com事業でも同様に、ビジネスサイドの偉い人がこういったからそれに従う、といったような組織ではありません。

よって、エンジニアには自立的に動いて意思決定をすることが求められます。言い方を変えると、自走力のあるメンバーであれば、正社員でなくとも一定以上の権限と裁量を持って取り組むことができる環境です。

むしろ、このように大きな権限と裁量を持っているフリーランスや副業のエンジニアが多いのがUPSIDERという会社の特徴の一つです。

スプリントをやらない

フルリモート、フルフレックス、コアタイムなし、稼働時間に制限がなくバラバラ、副業が多いという環境であるからこそ、スプリントを採用することが難しいので採用していません。

スプリントを採用した場合、毎週リリースしなければいけないというプレッシャーから、品質がおろそかになったり、テストがおざなりになって本番障害につながるというリスクがあると思っています。

微妙な状態でリリースするのかどうかという判断を毎回するのも面倒ですし、バグがある状態でリリースブランチにマージされていると revert するのも面倒ですし、とにかく面倒なことが多いわけです。

リリース対象が増えれば増えるほどリリースに対する負荷が高くなり、テスト対象が増え、影響範囲も大きくなるので、細かくテストして細かくリリースできた方が高い品質を維持できるという認識からスプリントを採用していません。

ですが、スプリントを採用することによって開発サイクルにリズムが生まれて開発がやりやすくなるというメリットがあることも認識しているので、スプリントで開発することが最適だと思ったら採用すると思います。

無駄な会議、デイリースクラム的な毎日のミーティング、稼働の管理

会議などにより必要以上に時間を拘束することは、エンジニアという職種の特性上、業務に支障がでることが多いので、無駄な会議を行わないようにしています。

必要に応じて会議は設定していますが、副業でジョインしているメンバーが多いためデイリースクラムを行うことは実質不可能です。働く時間帯がメンバーそれぞれ異なるため、作業を開始するときに Slack で「稼働開始、終了」といった報告も不要としています。

各々のエンジニアの稼働については、性善説に基づいています。不正をしたところで github のコントリビュートが少なければわかりますし、その人が働いているかどうかは、一緒に働いていれば大体わかります。

ただ、デイリースクラムができないことにより、日々のコミュニケーションが少なくなってしまうことや、エンジニア同士の関係性を築くことが難しくなってしまうと思っています。

そのために、エンジニア同士が会話をしやすいような雰囲気づくりを心がけたり、ちょっとした雑談をするような任意参加のミーティングを設けたりするといった施策もおこなっています。

休日、深夜の Slack メンションや DM を遠慮しない

休日、深夜の Slack メンションなどは基本的に気を遣う必要はないというスタンスです。

理由は以下の通りで、どうしても今すぐに反応してほしい緊急事態の場合は電話をする運用になりました。

  • 非同期コミュニケーション前提なので、後で返事すれば問題ない
  • 通知は各自でオフにできるので休日、深夜はオフにしても問題ない

また、Slack DM に関してはあまり使用を推奨していません。会社的にもオープンなコミュニケーションを行うようにしたいという考えがあるので、基本的に Slack チャンネルでの発言を推奨しています。

ダメな物事を無視しない

ダメな物事を無視していると、割れ窓理論的に秩序が保たれなくなり、優秀な人がいなくなるので、なるべく早く手を打つように心がけています。

ですが、ある程度放置しても問題なさそうなことに関してはあえて放置して、成り行きに任せるケースもあるので、その辺りの見極めが必要だなと思っております。また、問題によっては「この問題は放置しても問題ないよね」という合意が必要だと思うので、事前に合意するという運用が必要になってくると考えています。今のところまだそのケースは発生していないですが、人が増えてくると必要になりそうかなという認識でいます。

偉そうにしない

まず、UPSIDERでは偉い人という概念がありません。基本的に全員フラットで役割の違いがあるという状態を目指しています。

「Tomo さん(弊社代表の水野)が言っているから正しい」というようなことはなく、間違っていると思ったら反対意見を言います。関係性としてはフラットですが、お互いへのリスペクトや礼節はある、という組織です。

あとは、パフォーマンスや日々の行動によって、自然発生的にリーダーやマネージャー的なポジションが決まっていっていくような感じです。実際、僕はリーダーをやってくださいと任されたわけではないですが、自然とそうなっています。もっとイケてるリーダーは他にたくさんいると思うので、いつでも代わってもらえるように環境を整備中です!

聖域を作らない、働きすぎない

これらの施策はまだ取り組み中ですが、目指している課題でもあります。

主体性と属人化、コードオーナーと責任感は、複雑に絡み合って切り離すのが難しいと個人的には考えています。ですが、同時にその人にしか作業できない聖域を作ってしまうので、共有したい業務の仕様を少しずつ切り出して他の人にやってもらったり、モブプロを行って他の人に仕様を共有する、ドキュメントを整備するなどで対応していっている最中です。

働きすぎない、というのは文字通り働きすぎないようにすることです。仕事の Slack を昼夜休日問わず見続けたり、仕事が無限にあるからといって毎日遅くまで働くと脳がおかしくなるので、働かない時間を意図的に設けています。スタートアップではこれがかなり難しくなってしまう傾向があるので、意識的にやる必要がある認識です。


意識的にやっていること

エンジニア組織で意識的にやっていることを次に記載しました。

  • 小さく頻繁にリリースする
  • リファクタリングを頻繁に行う
  • 技術負債を早めに解消する
  • コミュニケーションしやすい環境づくりをする
  • ジョインした人がミスマッチだった場合はすぐに対応する

小さく頻繁にリリースする

支払い.comでは早く作って早くリリースすることを心がけています。

リリースサイクルは特に定めていないので、機能が出来上がってテストが完了したら、すぐにリリースすることが多いです。簡単な修正や小さな機能は、すぐにテストが完了することも多いので、週に数回リリースすることも割とあります。スプリントを採用していないのでリリースまでの待ち時間(リードタイム)がほぼ存在しません。全体に関わる影響が大きい機能に関してはリリースタイミングの調整が必要になるので、もちろんそのようなケースではリリース時期を調整することはあります。

リファクタリングを頻繁に行う

細かいリファクタリングを頻繁に行うようにしています。

放っておくと負債はどんどん積み上がっていってしまい、品質を維持できなくなり、かつ素早くリリースすることが困難になるためです。というよりも、エンジニアが自発的にリファクタリングを行なって、コードベースをきれいな状態に保っているので、気がついたらなんかコードベースが綺麗になっていてありがたいという感じです。

もしかしたら AI が勝手にコードを綺麗にしているのかもしれません。

技術負債を早めに解消する

大きめな技術負債は早めに解消するように心がけています。

ローンチ当初は Nuxt.js とAnt Design という UI ライブラリを採用して画面を開発していたのですが、Nuxt.js が2系で Ant Design のレイアウト制約などがきつくて両方とも技術負債になってしまいました。

そこで、Nuxt.js と Ant Design をやめるという判断をローンチから3ヶ月ほど経過した際に行い、1ヶ月ほどかけて Nuxt.js をやめて、 Next.js にリプレイスを行いました。機能がまだそれほど多くなかったので、約1ヶ月という短期間でリプレイスは完了し、かつReact に強い優秀なエンジニアが何名かジョインしてくれたので、Next.js にリプレイスしてよかったと思っています。Ant Design をやめてからは、基本的に UI ライブラリは使用せず、必要に応じて小さなライブラリを導入するようにしています。管理系の画面は Nuxt.js のままなので、こちらもそのうちリプレイスするか、Nuxt.js の3系にバージョンアップしたいところです。

コミュニケーションしやすい環境づくり

フルリモート、フルフレックス、コアタイムなし、働く時間も自由という環境は、とても自由度が高く働きやすい環境ではあるのですが、コミュニケーションが希薄になってしまい、チームの一員として動くことに障壁が存在します。

僕自身も多くのメンバーとリアルで会ったことがありません。よって、コミュニケーションをしやすくするための施策として、定期的に雑談をする任意参加のカジュアルなミーティングを設定しました。

English-speaker が何名かいるので、基本的には英語で話すことを推奨しています。また、英語が苦手な人でも話しやすいような雰囲気づくりを心がけたり、仕事の話だけではなく、お互いのプライベートな話や最近の出来事などを英語で話すようにしています。こうすることによって、仕事のちょっとした相談などをしやすくしたり、ミーティングの場でも話しやすい環境づくりにつながればと思っています。

このような施策以外にも、例えば新しくジョインした人に対しては、毎日軽く話すような軽めのミーティングを設定することも考えています。さらに、UPSIDER 全体の施策としては、毎週木曜日 WeWork にエンジニアが集まってカジュアルに話す WeWork day というのを設けていますので、もし UPSIDER のエンジニアとちょっと話してみたい方がいらっしゃいましたら、是非ご連絡ください。いつでもウェルカムです。

ジョインした人がミスマッチだった場合はすぐに対応する

正直なところ、面談をしただけでは判断できないことが多く、一緒に働いてみないとわからないことが多いです。

一緒に働いていると、成果物が出るまでの早さや適切なタイミングでのコミュニケーション、コードの品質など、いろいろとわかってくることが多いので、そこで初めてちゃんと評価ができる認識でいます。もちろん、面談時に判断できることも一定あるので、面談の際にはあらかじめ決めた質問をして、人によって面談の内容にばらつきが出ないように標準化して面談をするようにしています。

ミスマッチが発生した場合は早めに手を打つようにしており、こちら側で改善が可能なことに関しては改善し、相手に改善が必要だと思ったことは率直に伝えるようにしています。結果はどうあれ、なるべく早く対策を打つことが、お互いにとってポジティブであると考えています。


開発のフローについて

特にこれといって変わったことをやっているわけではなく、一般的な開発フローです。

git のブランチ構成は develop, main ブランチと各機能単位のブランチを作成する一般的な構成です。タスクの管理は主に github project を issue で管理しており、Epic issue を作成し Epic issue の中に各種 sub task の issue を作成して追記するようにしています。Epic issue のサンプルは次のような記載です。English-speaker のエンジニアがいるため、基本的に全て英語で Issue を記述するようにしていますが、限定的に日本語で書いても OK としています。

開発のフローは notion に定義し、どのような流れで機能を開発するのか、意思決定の基準は何かなどを記載しています。ただ、一般的な開発の流れだと思うので、普通のエンジニアであれば迷うことなく開発が進められる認識です。


エンジニア紹介

支払い.comで活躍しているエンジニアの特徴を紹介します。

  • 素早く機能を作り上げること
  • コードの品質が高くバグが少ないこと
  • 自立性があること
  • 重要な部分に関してはきちんと相談ができており、その判断を間違えることがない
  • 相談の必要ない部分に関しては自分で判断して機能を作る

これだけ書くとものすごく仕事ができて、人間的にも非常に優れているように思うかもしれません。ですが、実際のところ、新しいゲームが発売されると気配が消えるエンジニアもいますし、部屋がめちゃくちゃ汚かったり、そもそも机と椅子がない状態でベッドの上働いているエンジニアもいたりします。

また、学歴がとても良いというわけではなく、僕は IT 系の専門学校卒(日本電子専門学校という素晴らしい専門学校です)ですし、新しいゲームが発売されると気配が消えてしまうエンジニアも同じ専門卒です。高卒のエンジニアもめちゃくちゃ活躍してますので、学歴はぶっちゃけ関係ないなって感じです。

エンジニアとして濃い経験をしているメンバーが多いので、自走力やタフネスなど、それぞれがそれぞれの強みを持ったチームだと自負しています。ブラック企業で叩き上げられたエンジニアが多いので、とにかくタフネスが違います。

このように、非常に優秀だけどどこか欠点がある(?)エンジニアに支えられています。

支払い.comのエンジニアを何名かピックアップして簡単にご紹介します。エンジニアの多くが30代であり、経験豊富なメンバーが多く、ほぼ全員がフリーランスです。とてもいい人が多いので大変助かっておりますし、みんな優秀で自律的にいろいろと動いて取り組めるタイプです。

フロントエンド

  • Oさん
    • フロントエンドの全般を担当
    • とにかく実装がめちゃくちゃ早くてクオリティも高いし、レスポンスも早い
    • 副業で週2-3日の稼働だが、夜に働いていると思って翌朝に気づいたらフロントエンドの機能がほぼ出来上がっているので妖精かもしれない
  • Oさんが連れてきたOさん
    • 副業でジョイン(週1ぐらいの稼働)
    • 実装が早くバグが少なくて最高
    • Slack のアイコンが猫
  • Mさん
    • 副業でジョイン(週1の稼働だがおそらく週2〜ぐらい稼働している)
    • タスクをお願いして気づいたら終わっている
    • OpenAPI のスキーマ分割をお願いしたら、なんか知らんけどいつの間にか終わっていた
  • 僕の友達のOさん
    • 別プロジェクトで Go をメインに書いているが、 React の経験豊富なのでジョインしていただいた
    • 今は稼働少なめだが、能力はめちゃ高いので将来的にいろいろやってもらえそうな感じ

バックエンド

    • バックエンド全般を担当していたが人が増えたので最近はマネジメントを担当
    • エンジニアとビジネスサイドが協調して働ける環境整備をおこなっている
    • 最近ジョインした English-speaker のエンジニアと3歳児並みの英語力で週に2-3回打ち合わせをしている(大変ご迷惑をおかけしています。。)
  • Aさん
    • バックエンドの支払い登録周りの API など、コアな機能を担当
    • 三十代最後の夏が終わった
    • 大作のゲームが発売されると1ヶ月ほど姿が消える
    • ちゃんと働いているときはきちんと成果を出すので黙認している
    • 自宅でとんこつラーメンのスープを作りながら MacBook で仕事をしていたらとんこつスープの脂分で MacBook のファンが壊れて MacBook も壊れた
  • もう一人のOさん
    • 別の会社で正社員をやりながら副業で支払い.comを手伝っていただいている(週1ぐらいの稼働)
    • バックエンドを主に担当していただいているが、僕からの無茶振りで管理画面(Nuxt.js)も作成していただいており、積極的にいろいろ手を動かしていただいている
    • 医療系の業界からエンジニアに転身してエンジニアのキャリアはまだ浅いがすごくしっかりしている
  • Vさん
    • 最近ジョインした外国籍で English-speaker のエンジニア
    • 別の会社で正社員をやりながら副業で支払い.comを手伝っていただいている(週2ぐらいの稼働)
    • 本業では PM, スクラムマスターなどマネジメントを担当されているが、支払い.comではバックエンドの api 開発などを担当していただいている
    • マネジメントができてコードも書けるので、あとはとにかく僕らが英語をがんばろうという気持ちです
  • Hさん
    • Hさんも最近ジョインした English-speaker のエンジニア
    • 技術レベルが高く、機械学習をやったり不正利用検知などを担当していただいている
    • ヴィーガンのため日本のレストランで食事をするのに苦労している
  • Yさん
    • バックエンド全般を担当
    • 最も複雑な支払い登録周りの処理を素早くキャッチアップしていただいている
    • 支払い.comのエンジニアチームの中では数少ない英語がちゃんと話せる日本人エンジニア(余談ですが、僕の英語レベルはだいたい3歳児並みで、週に2〜3回 English-speaker なエンジニアとミーティングをしています)
    • 来年ごろカナダに移住する予定
    • カナダはビザを取るのがそんなに難しくないらしい
    • カナダには雄大な自然がある

あとがき

エンジニアが働きやすい環境を作るためには、チームみんなの協力が不可欠なのですが、いろいろな改善案などを提案してくれたり、みんなに協力してもらってめちゃくちゃ助かっています。

まだまだ改善中でカオスなエンジニア組織ですが、みんなが働きやすい環境になるように、これからも継続して改善していきたいと思っています!

最後までお読みくださり、ありがとうございました。UPSIDER はエンジニアやプロジェクトマネージャー、プロダクトマネージャーなど幅広く募集中ですので、もしご興味がありましたら、ぜひ一度カジュアルにお話しましょう!

career.up-sider.com

herp.careers

決済チームがテストコードを書く際に気を付けていること

こんにちは。決済チームでエンジニアとして働いている芦川です。

UPSIDER Tech blog 第2弾として「決済チームがテストコードを書く際に気をつけていること」を紹介しようと思います。

TL;DR

  • 100%のテストカバレッジを目指す
  • テストはブラックボックスを優先して記述、どうしても到達できない場合はホワイトボックス
  • 最初のテストケースは、テスト対象が動作する最も一般的なケースであるべき

私たちは日々大量のコードを書いており、そのシチュエーションは多岐にわたります。

そういった環境において、動作確認からのコード改修のコストを考えた場合、自動テストの有無によって生産性に大きく差が出ることは容易に想像ができます。また、既存のサービスに改修を加えるために、そのサービスの概要を把握したい場合、良いテストコードはドキュメントとして役立ちます。

以前、私はテストコードを一切書かないプロダクトの開発に従事していたこともありますが、今となってはどういう理屈でプログラムが正常に動作していたのか不思議でなりません。おそらく超自然的な何らかの力が作用していたものと考えられますが、その点についての言及は題とずれますので今回は控えさせていただきます。

というわけで、今回は私たちがテストコードを書く場合、指標として取り扱っている項目のうち、代表的なものを3つほど紹介しようと思います。


100%のテストカバレッジを目指す

これは私たちのチームが最も重要視している指標の一つです。

私たちが提供している決済サービスが正常に動作していない場合、お客様に与える影響は計り知れません。例えば、「お客様が本来拒否すべきである決済を誤って成立させてしまう」といったことも考えられます。

UPSIDERには、日次のリミットを設定できる機能があります。これは1日に利用できる金額に制限を設けるといったものです。上限を超えるような金額の決済が発生した場合に、それが正常に処理されなかったとすると、お客様からすると想定外の利用が発生してしまったことになります。

もしこういったことが頻繁に起こると、お客様に新しい機能を提供した際にも信用低下を原因に利用していただけず、より良い体験が提供できなくなってしまいます。そのようなことが起こらないよう、お客様に信用いただける品質の機能を提供するために、私たちは自動テストを重厚に書いています。

どうしても到達できないコード以外は絶対にテストケースを書きますし、ストアされているデータのパターンについても複数パターンを用意してテストします。

計測手法としてgo testカバレッジを利用するのは有用ですが、それだけを満たせば良いというわけではありません。決済システムにおいては、より網羅性の高いテストを目指すべきなので、go testで得られるようなC0のステートメントカバレッジを高い基準で満たすのは当然のこととして、C1やC2といったより複合的なカバレッジについても100%を満たすよう努力しています。


テストはブラックボックスを優先して記述し、どうしても到達できない場合にホワイトボックステストを書く

Go言語においてテストを書く場合のパッケージについて、大きく二つに分けることができます。packagename_testパッケージ名でファイルを作成する方法と、packagenameのパッケージ名でテストファイルを作成する方法であり、前者をブラックボックステスト、後者をホワイトボックステストと呼んでいます。

ホワイトボックステストの場合、そのパッケージ内のプライベートな関数や変数に直接アクセスすることができるため網羅性も高まりますし、基本的にテストコード自体の書きやすさも得られます。

しかし、ホワイトボックステストの採用には、以下のような欠点もあります。

  1. アクセシビリティを意識しないことから、必要以上の情報を公開してしまう恐れがある
  2. エッジケースの判断が難解になる
  3. 本来必要ない実装を見逃す恐れがある

1, 2は直感的なことだと思いますので、3つ目の観点を深掘りたいと思います。

例えば、以下のようなコードがあったとします。

package pkg

import (
    "errors"
)

func GetUserWithCurrentUsage(userID string) (*User, int, error) {
    u, err := getUser(userID)
    if err != nil {
        return nil, 0, err
    }
    cu, err := getCurrentUsage(userID)
    if err != nil {
        return nil, 0, err
    }
    return u, cu, err
}

func getUser(userID string) (*User, error) {
    if userID == "" {
        return nil, errors.New("userID is empty")
    }
    req := &GetUserRequest{
        UserID: userID,
    }
    return callUserService(req)
}

func getCurrentUsage(userID string) (int, error) {
    if userID == "" {
        return 0, errors.New("userID is empty")
    }
    req := &GetCurrentUsageRequest{
        UserID: userID,
    }
    return callUsageService(req)
}

これはユーザのデータと現在の使用量を取得する関数群です。

ホワイトボックスで全てのテストを記述した場合、容易に100%のカバレッジを得ることができるでしょう。しかし、ブラックボックステストのみを記述した場合、それは不可能です。

よく見てみるとgetCurrentUsageでのuserIDのバリデーションは、すでにgetUserで同様のバリデーションが行われていることがわかります。そのため、公開されている関数であるGetUserWithCurrentUsageのみをテストするとその条件分岐が不要であることがわかります。

この結果を経てリファクタリングすると、以下のようなコードになるでしょう。

package pkg

import (
    "errors"
)

func GetUserWithCurrentUsage(userID string) (*User, int, error) {
    // add
    if userID == "" {
        return nil, errors.New("userID is empty")
    }
    u, err := getUser(userID)
    if err != nil {
        return nil, 0, err
    }
    cu, err := getCurrentUsage(userID)
    if err != nil {
        return nil, 0, err
    }
    return u, cu, err
}

func getUser(userID string) (*User, error) {
    // remove
    // if userID == "" {
    //     return 0, errors.New("userID is empty")
    // }
    req := &GetUserRequest{
        UserID: userID,
    }
    return callUserService(req)
}

func getCurrentUsage(userID string) (int, error) {
    // remove
    // if userID == "" {
    //     return 0, errors.New("userID is empty")
    // }
    req := &GetCurrentUsageRequest{
        UserID: userID,
    }
    return callUsageService(req)
}

これによりブラックボックステストのみ記述することで、100%のカバレッジを達成することができます。こういった背景から、私たちは基本的にブラックボックスの形でテストコードを記述しています。

その上で、到達できないエッジケースのテストをホワイトボックスに閉じ込めることで、ブラックボックステストのコードを陳腐化させず、かつドキュメンテーションの役割を持たせることも意識しています。


最初のテストケースは、テスト対象が動作する最も一般的なケースであるべき

前提として、私たちはテストケースをテーブルドリブンで記述しています。

テーブルドリブンテストはGo言語のテストコードで広く採用されている手法で、以下のようにケース単位でテスト内容を記述する方法です。

テーブルドリブンテストをmapではなくsliceで定義する方法もありますが、テストケースにタイトルは必須だと考えているため、弊社ではmapで表現しています。

func TestSum(t *testing.T) {
    cases := map[string]struct {
        num1 int
        num2 int

        want    int
        wantErr error
    }{
        "Success case: 1 + 2": {
            num1: 1,
            num2: 2,
            want: 3,
        },
        "Success case: 106 + 24": {
            num1: 106,
            num2: 24,
            want: 130,
        },
        "Fail case: Max value that can be represented by an int is exceeded": {
            num1   : 9223372036854775807,
            num2   : 1,
            wantErr: invalidLengthErr,
        },
    }

    for name, tc := range cases {
        t.Run(name, func(t *testing.T) {
            res, err := mypackage.Sum(tc.num1, tc.num2)

            // 実際のアサーションにはgo-cmpを利用しています
            if res != tc.want {
                t.Errorf("received data didn't match: want %#v, got %#v", tc.want, res)
            }
            if !errors.Is(err, tc.wantErr) {
                t.Errorf("error didn't match: want %#v, got %#v", tc.wantErr, err)
            }
        })
    }
}

テーブルドリブンテストの効力として、うまく活用すればテストケースがそのパッケージのドキュメンテーションになりうるというものがあります。上述の例でも何に利用するかが明確で、かつどう言った場合にエラーとなりうるかを一覧化することができています。

そのため、パッケージの利用者はテストコードを閲覧することで、利用方法と利用に際して気を付けるべきことを簡潔に理解することができます。

さて、この利点を最大限に活かすために気をつけるべきことが、タイトルの通り「最初のテストケースは、テスト対象が動作する最も一般的なケースであるべき」というものです。

以下の例をご覧ください

func TestSetTransactionUpperLimit(t *testing.T) {
    cases := map[string]struct {
        userID string
    limit  int

        want    int
        wantErr error
    }{
        "Success case: Simple case": {
            userID: "some-user-id",
            limit : 1,

            want: 1,
        },
        "Fail case: Specify a negative value": {
            userID: "some-user-id",
            limit : -10000,

            wantErr: errNegativeValue,
        },
    }

    for name, tc := range cases {
        t.Run(name, func(t *testing.T) {
            res, err := userpackage.SetTransactionUpperLimit(tc.userID, tc.limit)

            if res != tc.want {
                t.Errorf("received data didn't match: want %#v, got %#v", tc.want, res)
            }
            if !errors.Is(err, tc.wantErr) {
                t.Errorf("error didn't match: want %#v, got %#v", tc.wantErr, err)
            }
        })
    }
}

これは、決済あたりの取引額を制限する機能のセットアップ部分のテストコードを極度に簡略化したものです。このテストコードは実際に動作するでしょうし、カバレッジは満たしています。

しかし、ドキュメンテーションとしての能力は低いと言わざるを得ません。パッケージの利用者は、このテストケースを見ただけでは、関数の利用シーンについて混乱してしまうでしょう。

お分かりかと思いますが、その理由は上限の設定にあります。

実際にこの機能の利用シーンを想定した場合、ユーザは1円を決済あたりの取引額の上限に設定するでしょうか?もしかしたらそういった場合もあるかもしれませんが、基本的にそれはエッジケースでしょう。

このテストの最初のテストケースは、より高額な値であるべきです。利用シーンは複数想定されるため、絶対的に正しい値というのはありませんが、より実務に沿った値とすることが重要です。一例として、私たちの実際のテストコードの最初のケースでは300000を設定しています。

このように、テストを書く際に実際のユースケースを想定して記述することで、テストコードに単なる単体テストで終わらせる以上の価値を付与することを重要視しています。


今回は以上となります、お読みいただきありがとうございました。今後も共有の価値があるインサイトがあれば、また記事にしようと思っていますので、その際はまたご覧いただけると嬉しいです。

宣伝

UPSIDERでは、成長企業のための法人カード「UPSIDER」と、すべてのBtoB取引でクレジットカードを利用できるビジネスあと払いサービス「支払い.com」を提供しております。

まだまだプロダクトで実現したいことがたくさんあり、プロダクトの急激な成長に伴う課題も増えている中で、一緒に前に進めてくれるエンジニアを絶賛募集中です。

カジュアル面談もやっておりますので、少しでもご興味のある方は、ぜひご連絡ください!!!

career.up-sider.com

herp.careers

「Tech Meetup 〜Goで作る決済サービス〜」にUPSIDERのメンバーが登壇しました

2022年8月4日(木)にオンラインで開催された株式会社KanmuさまとBASE株式会社さまとの合同Meetup 「Tech Meetup 〜Goで作る決済サービス〜」にUPDIERから2名登壇しました!

upsider.connpass.com www.youtube.com

イベント内容

今回のイベントは「Go×決済サービス」というテーマに沿って、カード決済事業を行う3社がそれぞれLT及びパネルディスカッションするものでした。

このイベントでのTwitterハッシュタグ、 #go_tech_meetup もとっても盛り上がりましたので、ぜひ当日のツイートも見てみてください!なかなかニッチな決済の話や決済システムあるあるの呟きが出てきて面白いです。

ご一緒させていただいたKanmuさま、BASEさま改めてありがとうございました!

セッション

UPSIDERのセッションはMiki(@m_miki0108)から、決済金額のリミット機能のうち、月間と日次の取引金額リミットを取り上げてどんな実装をしているのか、実際のGoのコードを紹介しながらお話しさせていただきました。

www.slideshare.net

金額に関わるコアな部分を触るうえで、何を考慮してこのような設計にしたのか、ケースの実例を交えながら決済システムにあまり関わったことがない人でもとっつきやすいようにまとめてみました。Go言語で書いてよかったことに関しては、他のプログラミング言語の経験から比較的最近Go言語を使い始めた私の所感も多めに語らせていただきました。

そして決済金額の日次のリミット機能については、なんとイベントの当日にリリースした機能です!とてもホットな内容をお届けすることができて良かったです。

これをきっかけにGoや決済システム、そしてUPSIDERにも興味を持ってもらえる人が増えるといいなと思ってます。

パネルディスカッション

三社で「Go × 決済」というテーマでパネルディスカッションをしました。

UPSIDERからは、Ryoya(@sekino_pii)がパネラーとして登壇しました。

以下の4つのテーマで、各社のアプローチや経験談を紹介しながら、ディスカッションを行いました。

三社の間で面白い共通点や相違点を見出すことができて、非常に盛り上がりました。

その中でも、決済の”理不尽な仕様”への想いは全員同じで、盛り上がりすぎて止まらない勢いでした。

パネラーのRyoyaは、決済の理不尽な仕様と三年間格闘してきた中で、BASEさま、Kanmuさまからもわかりみのあり過ぎる話が出てきたので(あと登壇中に飲んでいた檸檬堂が拍車をかけて)、テンションがあがって話しすぎてしまいました。

ちなみに、パネルディスカッションで冒頭で紹介した、UPSIDERのカードのアーキテクチャ図はこちらになります。

今後も技術イベントをやっていきます

UPSIDERでは、今後も技術面での発信に力を入れていきます! まだまだ少人数のエンジニアチームではありますが、各メンバーが日々の開発を通して得られた知見を発信していく予定です。

今回のようなイベント形式のものもまた開催していきます。もし「こういうイベントをやってほしい」などあれば、ぜひご連絡ください! Gopherの原作者は Renee French さんです。

宣伝

UPSIDERでは、成長企業のための法人カード「UPSIDER」と、すべてのBtoB取引でクレジットカードを利用できる決済サービス「支払い.com」を提供しております。

まだまだプロダクトで実現したいことがたくさんあり、プロダクトの急激な成長に伴う課題も増えている中で、一緒に前に進めてくれるエンジニアを絶賛募集中です。

カジュアル面談もやっておりますので、少しでもご興味のある方は、ぜひご連絡ください!!!

career.up-sider.com

herp.careers

meety.net