SSブログ

バックエンドサービスのテストコード [API仕様ファースト開発]

バックエンドサービスを開発する際に、そのバックエンドサービスがデータベースと接続して独自のデータを保持していることが多いです。そのようなバックエンドサービスをテストするためのテーストコードの作成において、テスト対象の機能をテストするために、事前にデータベースへデータを設定する必要があります。

API仕様ファースト開発でE2Eテストフレームワークを構築してからE2Eテストを作成する場合、サービスを一から開発するのであれば、最初はある程度、データベースへ直接データを設定する必要があります。なぜなら、必要なデータを設定するためのエンドポイントがまだ実装されていないかもしれないからです。ある程度エンドポイントの実装が進めば、既存のエンドポイントを呼び出してデータを整備することが可能になっていきます。

しかし、単体テストや内部の機能テストとして、テストコードを作成すると、必要なデータをテストコードから直接データベースへ設定してテストが作成されることも多いと思います。そして、すべてのテストが同じように作成されてしまうこともあるかと思います。

データベースへの直接設定する問題点

テストコードからデータベースへ直接レコードを挿入する場合、サービスの成長い伴ってテーブルが発展していく際に、以下の問題が発生します。
  • レコードのカラム変更(追加、削除、型の修正、etc)が行われた際に、すべてのテストコードが正しく対応できていない可能性がある
  • エンドポイント経由ではないため、正しくないレコードが作られている可能性がある
このような状況では、テストに対する信頼性が徐々に低下していきます。

テストを並列に実行できない要因

バックエンドサービスの機能が増えていけば、テストコードも増えてきます。エンドポイントを直接呼び出すE2Eテストであれば、テストコードを並列に実行しても問題ないはずです。しかし、個々のテストが必要なデータを直接データベースへ設定するテスト群では、並列に実行できない場合があります。

テストコードで以下の処理を行っていると、テストを並列に実行できません。
  • レコードをINSERTする際に、プライマリーキーをハードコードして指定している
  • テーブルが空である必要があるテストとなっており、個々のテストの実行で、最初にテーブル内のレコードをすべて削除している
プライマリーキーを指定してINSERTできるデータベースは多くあります。また、データベースによっては、すでに同じプライマリーキーと同じ値のレコードが存在する場合、更新操作にするINSERT構文が存在します。TypeScript用のtypeormではsave()を呼び出した際に、すでにレコードが存在していれば更新になってしまいます。

このような処理を行っているテスト群を作り続けていると、テストを並列実行できずに、逐次実行していく必要があるため、すべてのテストの実行が完了するのに30分を超えてしまうことも珍しくなくなります。

まとめ

私自身は、バックエンドサービスを開発してきて、開発を担当するサービス用のテストコードへの信頼を失うような開発はしたくないですし、手元の開発マシンですべてのテーストを短時間に実行しながら開発するのを好みます。つまり、テストコードからデーターベースへ事前データを直接設定するのは避けた方がよいですし、テストコードは並列に実行できた方がよいです。
コメント(0) 

22冊目の技術書の翻訳に着手します [技術書の翻訳]

Go言語 100Tips ありがちなミスを把握し、実装を最適化する』を昨年の8月に出版してから、何も翻訳していませんでした。22冊目となる技術書の翻訳作業に着手します。どの本であるかは、翻訳が進んで出版日が決まったころにお知らせします。
コメント(0) 

4月から『効率的なGo』を読んでいきます [読書会]

効率的なGo ―データ指向によるGoアプリケーションの性能最適化

効率的なGo ―データ指向によるGoアプリケーションの性能最適化

  • 出版社/メーカー: オライリー・ジャパン
  • 発売日: 2024/02/24
  • メディア: 単行本(ソフトカバー)

横浜Go読書会では、4月からこちらの本をオンラインで読んでいきます。

コメント(0) 

バックエンドサービス開発で当たり前に行ってきたこと [プログラマー現役続行]

2000年以降に継続的インテグレーションが広まっていき、今ではCIということで当たり前に行われています。Go言語でウェブサービスのバックエンドサービスを開発するようになって6年ほど経過していますが、日々の開発の中で私自身が当たり前と思って行ってきたことがいくつかあります。
  • ローカル実行:自分の開発用マシン(MacBook Pro)で、開発しているサービスの自動テストをローカルで実行できる
  • カバレッジ:テストを実行した結果、どの行が実行されなかったかをカバレッジで確認できる
  • 並列実行:Go言語の`testing`パッケージの`t.Parallel()`を使って並列にテストを実行することで、テスト対象のサービスに並列にリクエストを処理させる
  • 長時間ランニングテスト長時間ランニングテストができる
これらすべてができないとバックエンドサービスが開発できないわけではありません。そして、次のように反論されるかもしれません。
  • ローカルで実行できなくても、CIですべてのテストを実行できています
  • どの行が実行されていないかを確認しなくても、CIですべてのテストがPASSしているので問題ないのでは?
  • 逐次的にすべてのテストがCIで実行されてPASSしているので問題ないのでは?
  • ローカルで長時間ランニングテストをしなくても、CIでテストが1回PASSしているので問題ないのでは?
おそらく、この反論に対して相手が納得する説明を行うのは容易ではないです。なぜなら、私自身が当たり前に行ってきたことが何をもたらすかは、実際に行ってみてどのような問題を見つけるかを経験してみないと、容易に納得できないからです。
コメント(0) 

株式会社令和トラベルを退職します [転職]

2023年12月1日から働き始めた株式会社令和トラベルを3月19日付けで退職します。1984年4月1日から社会人として働き始めてから9社目の会社でした。9社の中で最も在籍期間が短かった会社となります。

私自身は、今年の11月で65歳になります。ウェブサービスの業界で働き続けるとしたら、API仕様ファーストおよびE2Eテストによるテストファースト開発を経験するエンジニアを増やしていければと思っています。もちろん、私自身もソフトウェア開発を続けたいのは以前と変わりません。しかし、私自身が正しいと思わない方法でソフトウェア開発を続けたくなかったので退職することにしました。

API仕様ファーストとその仕様をテストする自動テストを(テストファースト開発で)整備しながら開発をするというのは、私自身はウェブサービスのバックエンドサービス開発に従事してから始めたことではありません。API仕様をきちんと記述するというのは1993年頃から行っており、API仕様を自動テストするといのは、2003年以降に富士ゼロックスおよびリコーでの2つのデジタル複合機のコントローラソフトウェア開発で行ってきたものです。

何歳までソフトウェア開発に従事できるかは分かりませんが、同じような経験を積んでくれる開発を一人でも増やせればと思っています。

良かったこと

令和トラベルで働いてよかった点を挙げると、以下のとおりです。
  • TypeScriptおよびGraphQLを学ぶきっかけになりました。どちらも、専門家にはまだまだほど遠いですが、強制的に学ぶきっかけになりました。
  • メルペイやカウシェでGoで構築したE2Eテストフレームワークの基本的な考え方を適用して、TypeScript/JestでE2Eテストフレームワークを作成して、新たな機能をE2Eテストを書いて実装しました。
モックを多用した既存の単体テスト(結合テスト?)から、長期的にE2Eテストを整備することへシフトしないことになったのは残念です。E2Eテストフレームワークに関しては、外部サービスのフェイクサービスを実装するところまではいきませんでした。

Jestテストフレームワークでは、テストファイル単位でプロセスが生成されて実行されるため、外部サービスのフェイクサービスを同じプロセス内で動作させることはうまくいきません。したがって、別プロセスとしてフェイクサービスを起動して、テストコードとそのフェイクサービス間で通信をしながらレスポンスやエラーを設定をする必要があるのは分かっていたのですが、そこまでは着手できませんでした。

※ 私は、モックフレームワークを多用したテストは、昔から好きではないです。『Googleのソフトウェアエンジニアリング ―持続可能なプログラミングを支える技術、文化、プロセス』の「13.5 本物の実装」(p.307)で議論されています。

今後

退職後は、業務委託でどこかの会社を手伝うことはあっても、会社勤めはしない予定です。
コメント(0) 

公開API経由のテスト [API仕様ファースト開発]

「API仕様ファースト開発」では、バックエンドサービスが提供するフロントエンド向けのAPIの仕様を策定して、そのAPI仕様に記述されたエンドポイントを直接呼び出すE2Eテストを作成していきます。つまり、バックエンドサービスの公開API経由のテストとなります。

公開API経由のテストに関しては、『Googleのソフトウェアエンジニアリング』の12.2.2「公開API経由のテスト」に記述されています。そこからいくつか抜粋して紹介します。

Googleのソフトウェアエンジニアリング ―持続可能なプログラミングを支える技術、文化、プロセス

Googleのソフトウェアエンジニアリング ―持続可能なプログラミングを支える技術、文化、プロセス

  • 出版社/メーカー: オライリージャパン
  • 発売日: 2021/11/29
  • メディア: 単行本(ソフトカバー)

テスト対象システムの要件が変化しない限りテストが変化する必要がないことを保証するためのプラクティスをいくつか見ていこう。このことを保証するのに群を抜いて最も重要な方法は、テスト対象システムのユーザーが呼び出すのと同じ方法でシステムを呼び出すテストを書くことである。それはつまり、システムの実装の内部的詳細部分ではなく、システムの公開APIに対して呼び出しを行うということだ。

公開APIのみを利用するテストは定義上、テスト対象システムに、そのシステムのユーザーがアクセスするのと同じやり方でアクセスする。そのようなテストは明示的な契約を結ぶので、より現実的であり、脆さがより低い。つまり、そのようなテストが破綻するなら、システムの既存ユーザーの活動もまた破綻するだうということを必然的に意味する。それらの契約のみをテストするというのは要するに、テストにつまらない変更を加えた結果についていちいち心配する必要なしに、システム内部のリファクタリングはどんなものでもやりたいようにやれるということだ。

Googleでは、公開API経由のテストは実装詳細に対するテストより優れているという点を納得させるために、エンジニアを説得しなければならない場合があることがわかっている。エンジニアの気が進まないのは理解できる。自分が書いたばかりのコードに専念するテストを書く方が、そのコードがシステム全体にどう影響するか理解するよりずっと楽な場合が多いのだ。それにもかかわらず、そのようなプラクティの奨励には価値があることがわかっている。そのプラクティスに従うという追加的な労力を先行して費やすと、保守の負担が減るという形で、かけた労力の何倍もの見返りがある。公開APIに対するテストは、行うことでテストの脆さが完璧に防げるわけではない。しかし、システムに対して意味のある変更が起こった場合のみテストが失敗するよう担保するために行える対策として、最も重要なのは、公開APIに対するテストだ。

コメント(0) 

Hotfixとテスト失敗 [プログラマー現役続行]

何らかの緊急の障害に対して、Hotfixとして緊急に修正がリリースされることはよくあると思います。そのHotfixリリースのPull Requestを見たときに、既存のテストの修正が含まれていない場合、次のことが起きていることが分かります。
  • 修正に該当する部分の機能をテストしているテストコードが存在しなかった。存在していればそのテストが失敗するので、一緒に修正されているはずです。
  • 既存のテストが存在しないため、既存のテストに追加/修正を加えるといった再現テストの作成ができなかったし、新たに作成する時間もなかった。
緊急なので、後者も仕方ないです。しかし、リリース後に不足しているテストコードを作成したいものです。ソフトウェアエンジニアなら、後からでもよいので、テストを追加する習慣を身に付けるようにしてください。
コメント(0) 

Mission Abandoned [API仕様ファースト開発]

2017年8月末に株式会社リコーを退職し、ソラミツ、メルペイ、カウシェとバックエンドサービス開発に従事してきました。最初の頃は、初めてのバックエンドサービス開発なので、どのように行うのがよいのかは手探りに近かったのですが、メルペイで現在も運用されているマイクロサービスの1つを開発してから、さまざまなバックエンドサービス開発を通して、得た知見や行ってきたことを雑誌の書籍やTechブログとしてまとめてきました。その主なものは次の通りです。
  • 「API仕様ファースト開発」
  • 1つのサービスを単独でテストするE2EテストフレームワークとE2Eテストの整備
カウシェを退職した理由は、カウシェではこの2つが、かなりきちん開発プロセスとして定着させることができたので、別の会社で同様な経験を持つエンジニアを増やしたいというものです。特に「伸ばすのが難しい能力」で述べた最初の2つ(「API仕様をきちんと書ける」「テストファースト開発ができる」)を経験したエンジニアを増やしたいというものです。

WEB+DB PRESS Vol.134

WEB+DB PRESS Vol.134

  • 出版社/メーカー: 技術評論社
  • 発売日: 2023/04/22
  • メディア: Kindle版

記事「実践API設計」では、E2Eテストを書いて実際にどのコードが実行されたのかをカバレッジで確認することも重要だと述べています。

しかし、これらを全く行っていなくても、サービスの開発はできます。だから、経験していない開発者が多数いるわけです。今回は、きちんと経験した開発者を増やして、私が健全だと思う開発プロセス構築を目指すというMissionはAbandonedとなります。
コメント(0) 

腰部脊柱管狭窄症の再発(その後) [脊柱管狭窄症]

昨年11月に再発した腰部脊柱管狭窄症ですが、一時期かなり悪い状態になりそうでしたが、その後回復してきました。

天気がよい日は自宅でのエアロバイクではなく、ウォーキングを最近はするようになったのですが、少し前までは、2kmを歩くのに途中3回休憩しないといけない状態でした。歩いているうちに足が痺れてきて辛くなるので、休んでまた歩きます。いわゆる「間欠跛行(かんけつはこう)」状態です。

それでも今週はかなりよくなって、足の痺れは少しありますが、途中休憩なしで歩けるようになりました。このまま良くなれば、もう少し歩く距離を伸ばしていきたいと思っています。
コメント(0) 

健全なバックエンドサービス開発 [API仕様ファースト開発]

ウェブサービスのバンクエンドサービス開発に従事してすでに6年以上経過しています。そして、最初から「API仕様ファースト開発」を行っています。「API仕様ファースト開発」は、私自身の造語です。

バックエンドサービスを開発する際に、(無意識に)私にとって当然だと思っていたのは次の2つです。
  • フロントエンドを提供するバックエンドサービスのAPIでは、その仕様をきちんと記述する
  • API仕様通りに個々のエンドポイントが動作するかを検証する自動テスト(E2EテストフレームワークとE2Eテスト)を整備する
この2つが最初にありきなので、実際のバックエンドサービスの構造をどうするのか、内部の各モジュールのテストをどうするのかといったことは、最優先とはなりません。

一般的には、単体テスト・結合テストを開発しながらソフトウェアは行うと考えられていますが、上記2点を優先して開発した場合、E2Eテストが実行されて、カバレッジが確認できれば、そこからあえて、単体テストや結合テストを書くことはしません。

なぜなら、常に、API仕様に基づいてテストファーストでE2Eテストを開発し、その後実装することを行っているからです。E2Eテストですでに機能の確認が行われていれば、さらに単体テストや結合テストを作成することは無駄であり、その工数を費やすことに意味がないからです。
私自身は、API仕様を記述せずに、1つのバックエンドサービスとしてそのエンドポイントを直接呼び出す自動テストコードも存在しないことが正しい開発だと思わないだけです。私は、そのような開発をしていたら、不快に思うだけです。

不健全なバックエンドサービス開発

私にとって不健全に思われるバックエンドサービス開発は、次の通りです。
  • API仕様が記述されず、バックエンドサービスのエンドポイントのテストはフロントエンドを接続して手作業で行う開発。
  • 仮にAPI仕様が記述されていたとしても、バックエンドサービスのエンドポイントのテストはフロントエンドを接続して手作業で行う開発。
  • 単体テストや結合テストでテストしてるから「大丈夫なはず」という開発。
    • API仕様が記述されているなら、バックエンドサービスのエンドポイントを直接呼び出すE2Eテストで検証する方が素直
    • API仕様が記述されていない場合、単体テストや結合テストの妥当性をきちんと確認する方法がない

私自身は、過去2回行ったテスト駆動開発によるデジタル複合機のコントローラソフトウェア開発(通算、約6年)でもテストコードはすべてAPI仕様に記述された呼び出しポイントを呼び出すテストを書いたり、書くことを指導してきました。その経験から、ウェブサービスのバンクエンドサービス開発に従事するようになっても、最初に述べた2点を優先して、当然だと思ってきました。
コメント(0)