HTTPリクエストテスト:開発者のサバイバルツール
公開: 2022-03-11テストスイートが実行できない場合の対処方法
私たち(プログラマーやクライアント)が、期待される成果物とその成果物の自動テストの両方を作成するためのリソースが限られている場合があります。 アプリケーションが十分に小さい場合は、機能を追加したり、バグを修正したり、リファクタリングしたりするときにコードの他の場所で何が起こるかを(ほとんど)覚えているので、手抜きをしてテストをスキップできます。 とは言うものの、私たちは常に小さなアプリケーションで動作するとは限りません。さらに、それらは時間の経過とともに大きくなり、より複雑になる傾向があります。 これにより、手動テストが困難になり、非常に煩わしくなります。
私の最後のいくつかのプロジェクトでは、自動テストなしで作業することを余儀なくされました。正直なところ、コードをプッシュした後、コードに触れていない場所でアプリケーションが壊れているとクライアントにメールを送ってもらうのは恥ずかしいことでした。
そのため、クライアントに自動テストフレームワークを追加する予算や意図がない場合は、個々のページにHTTPリクエストを送信し、応答ヘッダーを解析して「200」を探すことで、ウェブサイト全体の基本機能のテストを開始しました。応答。 わかりやすくシンプルに聞こえますが、テスト、ユニット、機能、または統合を実際に作成しなくても、忠実度を確保するためにできることはたくさんあります。
自動テスト
Web開発では、自動テストは、単体テスト、機能テスト、統合テストの3つの主要なテストタイプで構成されます。 多くの場合、単体テストを機能テストおよび統合テストと組み合わせて、アプリケーション全体としてすべてがスムーズに実行されることを確認します。 これらのテストが同時に、または順番に(できれば、単一のコマンドまたはクリックで)実行されると、ユニットかどうかに関係なく、自動テストと呼ばれるようになります。
これらのテストの主な目的は(少なくともWeb開発では)、すべてのアプリケーションページが問題なくレンダリングされ、致命的な(アプリケーションの停止)エラーやバグがないことを確認することです。
ユニットテスト
ユニットテストは、コードの最小部分であるユニットが正しく動作するかどうかを個別にテストするソフトウェア開発プロセスです。 Rubyでの例を次に示します。
test “should return active users” do active_user = create(:user, active: true) non_active_user = create(:user, active: false) result = User.active assert_equal result, [active_user] end
機能テスト
機能テストは、システムまたはソフトウェアの機能をチェックするために使用される手法であり、障害パスや境界のケースを含む、すべてのユーザーインタラクションシナリオをカバーするように設計されています。
注:すべての例はRubyです。
test "should get index" do get :index assert_response :success assert_not_nil assigns(:object) end
統合テスト
モジュールの単体テストが完了すると、モジュールは1つずつ統合され、組み合わせの動作をチェックし、要件が正しく実装されていることを検証します。
test "login and browse site" do # login via https https! get "/login" assert_response :success post_via_redirect "/login", username: users(:david).username, password: users(:david).password assert_equal '/welcome', path assert_equal 'Welcome david!', flash[:notice] https!(false) get "/articles/all" assert_response :success assert assigns(:articles) end
理想的な世界でのテスト
テストは業界で広く受け入れられており、それは理にかなっています。 良いテストはあなたにさせます:
- 品質は、最小限の人的労力でアプリケーション全体を保証します
- コードがテストの失敗からどこで壊れているかを正確に把握しているため、バグをより簡単に特定できます
- コードの自動ドキュメントを作成する
- Stack Overflowの一部の人物によると、「コーディング便秘」は避けてください。「次に何を書くべきかわからない場合、または目の前で困難な作業をしている場合は、最初に小さな文章を書くことから始めてください。 。」
私はテストがどれほど素晴らしいか、そしてそれらがどのように世界とヤダヤダヤダを変えたかについて何度も続けることができました、しかしあなたは要点を理解します。 概念的には、テストは素晴らしいです。
実世界でのテスト
3種類のテストすべてにメリットがありますが、ほとんどのプロジェクトでは記述されていません。 なんで? さて、それを分解させてください:
時間/締め切り
誰もが期限を持っており、新しいテストを書くことは、期限を守る妨げになる可能性があります。 アプリケーションとそれぞれのテストを作成するには、1時間半(またはそれ以上)かかる場合があります。 さて、最終的に節約された時間を引用して、これに同意しない方もいらっしゃいますが、そうではないと思います。その理由を「意見の相違」で説明します。
クライアントの問題
多くの場合、クライアントはテストとは何か、またはテストがアプリケーションにとって価値がある理由を実際には理解していません。 クライアントは製品の迅速な納品に関心を持つ傾向があるため、プログラムによるテストは逆効果であると見なされます。
または、クライアントがこれらのテストを実装するために必要な余分な時間の支払いをする予算がないのと同じくらい簡単かもしれません。
知識不足
現実の世界には、テストが存在することを知らないかなりの数の開発者がいます。 すべての会議、交流会、コンサートで(私の夢の中でも)、テストの書き方、テストの内容、テスト用のフレームワークのセットアップ方法などを知らない開発者に会います。オン。 テストは学校で正確に教えられているわけではなく、テストを実行するためのフレームワークを設定/学習するのは面倒な場合があります。 そうです、参入障壁は明確です。
「それはたくさんの仕事です」
テストの作成は、初心者と経験豊富なプログラマーの両方にとって、世界を変える天才タイプであっても、圧倒される可能性があります。さらに、テストの作成はエキサイティングではありません。 「クライアントを感動させるような結果をもたらす主要な機能を実装できるのに、なぜわくわくしない忙しい仕事に従事する必要があるのか」と考える人もいるかもしれません。 それは難しい議論です。
最後に、大事なことを言い忘れましたが、テストを書くのは難しく、コンピュータサイエンスの学生はそのための訓練を受けていません。
ああ、ユニットテストでリファクタリングするのは楽しいことではありません。
意見の違い
私の意見では、単体テストはアルゴリズムロジックには意味がありますが、生きているコードを調整することにはあまり意味がありません。
人々は、テストの作成に前もって余分な時間を費やしていても、後でコードをデバッグまたは変更するときに何時間も節約できると主張しています。 私は違いを求めて、1つの質問を提供します:あなたのコードは静的ですか、それとも常に変化していますか?
私たちのほとんどにとって、それは常に変化しています。 成功するソフトウェアを作成している場合は、常に機能を追加したり、既存の機能を変更したり、削除したり、食べたりすることになります。これらの変更に対応するには、テストを変更し続ける必要があり、テストの変更には時間がかかります。
しかし、あなたはある種のテストが必要です
どんな種類のテストも欠いていることが最悪のケースであると誰も主張しません。 コードに変更を加えた後、実際に機能することを確認する必要があります。 多くのプログラマーは、基本を手動でテストしようとします。ページはブラウザーでレンダリングされますか? フォームは送信されていますか? 正しいコンテンツが表示されていますか? などなどですが、私の意見では、これは野蛮で非効率的で労働集約的です。
代わりに使用するもの
Webアプリを手動または自動でテストする目的は、特定のページがユーザーのブラウザーで致命的なエラーなしにレンダリングされ、そのコンテンツが正しく表示されることを確認することです。 これを実現する1つの方法(ほとんどの場合、より簡単な方法)は、アプリのエンドポイントにHTTPリクエストを送信し、応答を解析することです。 応答コードは、ページが正常に配信されたかどうかを示します。 HTTPリクエストの応答本文を解析し、特定のテキスト文字列の一致を検索することで、コンテンツを簡単にテストできます。または、一歩進んでnokogiriなどのWebスクレイピングライブラリを使用することもできます。
一部のエンドポイントでユーザーログインが必要な場合は、ログインの機械化や特定のリンクのクリックなど、対話を自動化するために設計されたライブラリを使用できます(統合テストを行う場合に最適です)。 実際、自動テストの全体像では、これは統合テストや機能テストによく似ていますが(使用方法によって異なります)、作成がはるかに速く、既存のプロジェクトに含めたり、新しいプロジェクトに追加したりできます。 、テストフレームワーク全体を設定するよりも少ない労力で。 スポット!
エッジケースは、値の範囲が広い大規模なデータベースを処理するときに別の問題を引き起こします。 予想されるすべてのデータセットでアプリケーションがスムーズに機能しているかどうかをテストするのは困難な場合があります。
それを実行する1つの方法は、すべてのエッジケースを予測し(これは単に難しいだけでなく、多くの場合不可能です)、それぞれのテストを作成することです。 これは簡単に数百行のコードになり(恐怖を想像してください)、維持するのが面倒になる可能性があります。 それでも、HTTPリクエストと1行のコードで、開発マシンまたはステージングサーバーにローカルにダウンロードされた本番環境からのデータでこのようなエッジケースを直接テストできます。
もちろん、このテスト手法は特効薬ではなく、他の方法と同じように多くの欠点がありますが、これらのタイプのテストは、記述と変更がより速く、より簡単であることがわかります。
実際:HTTPリクエストを使用したテスト
付随するテストなしでコードを書くことは良い考えではないことをすでに確立しているので、アプリケーション全体の非常に基本的なテストは、HTTPリクエストをすべてのページにローカルに送信し、応答ヘッダーを解析することです。 200
(または望ましい)コード。

たとえば、上記のテスト(特定のコンテンツと致命的なエラーを探すテスト)を代わりに(Rubyで)HTTPリクエストを使用して作成すると、次のようになります。
# testing for fatal error http_code = `curl -X #{route[:method]} -s -o /dev/null -w "%{http_code}" #{Rails.application.routes.url_helpers.articles_url(host: 'localhost', port: 3000) }` if http_code !~ /200/ return “articles_url returned with #{http_code} http code.” end # testing for content active_user = create(:user, name: “user1”, active: true) non_active_user = create(:user, name: “user2”, active: false) content = `curl #{Rails.application.routes.url_helpers.active_user_url(host: 'localhost', port: 3000) }` if content !~ /#{active_user.name}/ return “Content mismatch active user #{active_user.name} not found in text body” #You can customise message to your liking end if content =~ /#{non_active_user.name}/ return “Content mismatch non active user #{active_user.name} found in text body” #You can customise message to your liking end
行curl -X #{route[:method]} -s -o /dev/null -w "%{http_code}" #{Rails.application.routes.url_helpers.articles_url(host: 'localhost', port: 3000) }
は多くのテストケースをカバーしています。 記事のページでエラーを発生させるメソッドはすべてここで検出されるため、1回のテストで数百行のコードを効果的にカバーします。
特にコンテンツエラーをキャッチする2番目の部分は、ページのコンテンツをチェックするために複数回使用できます。 (より複雑なリクエストはmechanize
を使用して処理できますが、それはこのブログの範囲を超えています。)
これで、特定のページが大規模で多様なデータベース値のセットで機能するかどうかをテストする場合(たとえば、記事ページテンプレートが本番データベースのすべての記事で機能する場合)、次のことができます。
ids = Article.all.select { |post| `curl -s -o /dev/null -w “%{http_code}” #{Rails.application.routes.url_helpers.article_url(post, host: 'localhost', port: 3000) }`.to_i != 200).map(&:id) return ids
これにより、レンダリングされなかったデータベース内のすべての記事のIDの配列が返されるため、特定の記事ページに手動で移動して問題を確認できます。
スタンドアロンスクリプトのテストやメールの送信など、特定のケースではこのテスト方法が機能しない可能性があることを理解しました。テストごとにエンドポイントを直接呼び出すため、単体テストよりも間違いなく低速ですが、単体テスト、機能テスト、またはその両方を行うことはできません。これは、何もないよりはましです。
これらのテストをどのように構成しますか? 小さくて複雑でないプロジェクトでは、すべてのテストを1つのファイルに書き込み、変更をコミットする前に毎回そのファイルを実行できますが、ほとんどのプロジェクトでは一連のテストが必要になります。
私は通常、テスト対象に応じて、エンドポイントごとに2〜3つのテストを作成します。 個々のコンテンツをテストすることもできますが(単体テストと同様)、すべてのユニットに対してHTTP呼び出しを行うため、冗長で時間がかかると思います。 しかし、その一方で、それらはよりクリーンで理解しやすいものになります。
テストを通常のテストフォルダーに入れて、主要なエンドポイントごとに独自のファイルを作成することをお勧めします(たとえば、Railsでは、各モデル/コントローラーにそれぞれ1つのファイルがあります)。このファイルは、内容に応じて3つの部分に分割できます。テスト中です。 私はしばしば少なくとも3つのテストを行います:
テスト1
致命的なエラーなしでページが戻ることを確認します。
Post
のすべてのエンドポイントのリストを作成し、それを繰り返して、各ページがエラーなしでレンダリングされることを確認したことに注意してください。 すべてが順調に進み、すべてのページがレンダリングされたとすると、ターミナルに次のように表示されます➜ sample_app git:(master) ✗ ruby test/http_request/post_test.rb List of failed url(s) -- []
いずれかのページがレンダリングされていない場合は、次のように表示されます(この例では、 posts/index page
にエラーがあるため、レンダリングされません): ➜ sample_app git:(master) ✗ ruby test/http_request/post_test.rb List of failed url(s) -- [{:url=>”posts_url”, :params=>[], :method=>”GET”, :http_code=>”500”}]
テスト2
期待されるすべてのコンテンツがそこにあることを確認します。
期待するすべてのコンテンツがページにある場合、結果は次のようになります(この例では、 posts/:id
に投稿のタイトル、説明、ステータスがあることを確認します): ➜ sample_app git:(master) ✗ ruby test/http_request/post_test.rb List of content(s) not found on Post#show page with post id: 1 -- []
期待されるコンテンツがページに見つからない場合(ここでは、ページに投稿のステータスが表示されると予想されます-投稿がアクティブな場合は「アクティブ」、投稿が無効な場合は「無効」)結果は次のようになります: ➜ sample_app git:(master) ✗ ruby test/http_request/post_test.rb List of content(s) not found on Post#show page with post id: 1 -- [“Active”]
テスト3
ページがすべてのデータセット(存在する場合)にわたってレンダリングされることを確認します。
すべてのページがエラーなしでレンダリングされると、空のリストが表示されます➜ sample_app git:(master) ✗ ruby test/http_request/post_test.rb List of post(s) with error in rendering -- []
一部のレコードのコンテンツでレンダリングに問題がある場合(この例では、ID 2と5のページでエラーが発生しています)、結果は次のようになります➜ sample_app git:(master) ✗ ruby test/http_request/post_test.rb List of post(s) with error on rendering -- [2,5]
上記のデモコードをいじりたい場合は、これが私のgithubプロジェクトです。
では、どちらが良いですか? 場合によります…
HTTPリクエストのテストは、次の場合に最善の策となる可能性があります。
- Webアプリを使用しています
- あなたは時間の危機に瀕していて、何かを速く書きたいと思っています
- テストが作成されていない既存のプロジェクトである大きなプロジェクトで作業していますが、コードをチェックする方法が必要です。
- コードには単純な要求と応答が含まれます
- あなたはあなたの時間の大部分をテストのメンテナンスに費やしたくありません(私はどこかでユニットテスト=メンテナンス地獄を読みました、そして私は彼/彼女に部分的に同意します)
- アプリケーションが既存のデータベースのすべての値で機能するかどうかをテストする
従来のテストは、次の場合に理想的です。
- スクリプトなど、Webアプリケーション以外のものを扱っています
- 複雑なアルゴリズムコードを書いています
- テストの作成に専念する時間と予算があります
- ビジネスにはバグのない、またはエラー率の低いものが必要です(財務、大規模なユーザーベース)
記事を読んでくれてありがとう。 これで、デフォルトで使用できるテスト方法が用意されているはずです。これは、時間に追われているときに信頼できる方法です。
- パフォーマンスと効率:HTTP/3での作業
- 暗号化して安全に保つ:ESNI、DoH、およびDoTの操作