最新のソフトウェア エンジニアリング - パート II: テスト

デバッグがソフトウェアのバグを取り除くプロセスであるなら、プログラミングはバグを埋め込む行為でなければなりません。」 - Edsger Djikstra

自動化されたソフトウェア テストを作成することは、自分自身と電話ゲームをするようなものです。情報の内容を誤解した場合、責任を負うのは自分だけです。自分のコードのテストを作成している場合、これは十分に難しいことですが、最初からテストされることのない、他の人が書いたコードのテストを作成していることを考慮してください。これは、ブルー ジーンズのポケットに入れて 3 回洗濯された紙に書かれたメッセージを理解しようとするようなものです。これはのために書かれたテストです!

これは、テスト対象のコードが作成された後に作成されるテストです。次に、最初にテストを書くことについて考えてみましょう。それは、書こうとしているソリューションが「正しい動作をする」ことを確認するために、最初に意味のある仕様やテストを書いて、自分自身と勝負するようなものです。しかし、コンピューターサイエンスを学んだ人にとって、これは失速問題を解決するのとよく似ているように思えますが、さらに悪いことに、自分がやりたいことが正しいことを自分自身だけでなくコンパイラー/インタープリターに対しても証明する必要があるからです。

では、なぜ過去 20 年間、テストが現代のソフトウェア エンジニアリングの実践に不可欠な要素となっているのでしょうか。最初にテストするか最後にテストするかにかかわらず、私たちプロのソフトウェア エンジニアは、要件を満たすためにソフトウェアをテストおよび検証する方法を考える必要があります。

はじめにはじめに

またまたお話の時間です。

前回のパート 1では、スケーラビリティ、信頼性、可用性、保守性、セキュリティに関する現代の要件に対応するシステムの設計方法をどのように学んだかについて書きました。ソリューションの設計には限界があります。なぜなら、最終的にはソリューションを実装する必要があり、場合によっては 1 つまたは複数のチームで実行する必要があるからです。

ご想像のとおり、チーム間の仕事の調整は大きな問題の原因になりますが、この負担を軽減するために何かできることはあるでしょうか? ここで、自動テスト、特にテストの実装ではなく動作を指定する種類のテストが登場します。

Friendster で働いていたとき、私は自分が行っていたサービスの顧客が何を期待しているかを正確に知っていました。ただし、これは完全に指定されているわけではありません。従うプロトコル (これはプロトコル バッファーが普及する前のことです) と、これらのクライアントによって呼び出されるいくつかの URI があります。セマンティクスは完全には詳しく説明されていませんが、私たちには熱心な読者がいます。私はクライアントのコードを読んで、現在の実装に何が期待できるかを理解できます。

これは重要です。完全に新しいプロトコルを作成したり、新しいコントラクトを作成したりする代わりに、自動テストとして作成できる既知の要件から始めます。私が最初に行ったいくつかのことの 1 つは、これらのテストをプログラム可能な仕様に変換し、徐々に実装を要件を満たすレベルにまで引き上げることでした。この作業により、次の 2 つの製品が生まれました。

  • C++ ネットワーク ライブラリ — 書き換えているサービスがその上に統合される、HTTP クライアントとサーバーの適度なパフォーマンスの C++ 実装。
  • memcache++ ライブラリ — シャーディングと仮想ノード プールをサポートする、C++ で実装された適度なパフォーマンスの memcache クライアント。

どちらのオープンソース ソリューションも、内部で定義された技術要件の結果です。私たちは既存のシステムから始めて、それをコンポーネント部分に分割し、ビジネスに不可欠ではない部分をオープンソース ソフトウェアとして共有できるようになるまで、徐々にソリューションを実装します。

なぜテストから始める必要があるのか​​と疑問に思われるかもしれません。なぜなら、テストにより、段階的かつ予測可能な方法でニーズを満たすソリューションを埋めることができるからです。テストを使用すると、私と私が作成したコードをレビューする人は、要件が何であるかを理解し、テストを実行することで自動的に検証できます。これにより、必要な自信が得られ、ニーズを満たすソリューションを得ることができました。

テストを実施すると、必要かつ十分な機能を提供するテストに集中できると同時に、自信を持ってソリューションをリファクタリングして改善し、コーディング要件に違反していないことを迅速に確認できます。要件をテストでカバーすることで、途中で恐れることなくリファクタリングを行い、多くのバグを発見し、機能を迅速に提供することができました。

これは 2007 年から 2008 年頃のことで、テスト駆動開発や動作駆動開発などの概念の多くは、一般的にエンタープライズ ソフトウェア業界で普及し始めたばかりでした。ここでは、これらの良いアイデアをいくつか取り上げ、マイクロサービスと水平スケーラブルなシステムに適用します。

数年が経ち、現在は 2023 年です。テストは一部のサークルで汚い言葉になり (TDD と BDD は多くの人を燃え尽きさせる傾向がありますが、これは主に原則の誤解が原因です)、思いつきで、私たちは副操縦士に、私たちが書いたコードの単体テストを行うように依頼しました。これは少し残念なことです。なぜなら、パフォーマンスの高いソフトウェア エンジニアリング チームが、変化する要件に適応し、ソリューションの実装を改善するために適切なタイプのテストを採用する自由は非常に価値があり、早期に投資をしないチームは、多くの場合、それに気づいているからです。テストによって大きな失敗や、本番環境に忍び寄るバグによる眠れない夜、あるいはソリューションの品質と速度の低さによるビジネスの損失を回避できたかもしれないのに、遅すぎました。

この記事では、現代のソフトウェア エンジニアリングにおけるテストの役割と、テストを正しく行うことでどのようにあなたとあなたのチームがこの業界で成功できるようになるかについて詳しく書きます。

テストレベル

次に進む前に、テストのさまざまなレベルまたはカテゴリを理解することをお勧めします。これまでにテストを作成したことがない場合は、少なくともその用語を中心に起こっている議論についていくことができるように、テスト用語のかなり堅牢な分類法があることを知っておくと有益かもしれません。

  • ユーザー受け入れテスト (UAT) - 通常は、ソフトウェア システムがエンド ユーザーの要件を満たしていることを確認する自動テストです。エンド ユーザーのシミュレーションには、通常、ソフトウェアのユーザー インターフェイス (ブラウザベースの Web ユーザー インターフェイスの自動テスト、ネイティブ アプリケーション ユーザー インターフェイス用のアプリケーション ドライバー、API サービス クライアントなど) を操作して、ユーザーが行うことを実行し、その結果を観察することが含まれます。アクションは、ソフトウェアの受け入れ基準を満たしているかどうかを確認することです。これは通常、最高レベルのテストであり、ソフトウェア システム全体のすべてを対象とします。
  • システム テスト — 通常、システムの機能的属性と非機能的属性をテストする自動テスト。ここで、システムは、完全に統合されたアプリケーション、または関連コンポーネントを備えたサブシステムの場合があります。システム テストは通常​​、ユーザー受け入れテスト (UAT) よりも包括的です。
  • 統合テスト - 通常、統合環境 (通常はテスト ハーネスまたはアプリケーション ラック) でサブシステムとして動作する複数のコンポーネントの相互作用をテストする自動テスト。これにより、統合コンポーネントのルーティングとテストが容易になります。統合テストでは通常、連携して特定の機能セットを提供する完全なソリューションの論理サブシステムをテストします。
  • 単体テスト - 単一コンポーネント (単一クラスである必要はない) の機能を分離してテストする典型的な自動テスト。これらの依存関係を制御された (場合によっては意図的な) 方法でシミュレートするために、依存関係を機能的に同等の実装で置き換えることができます。

場合によっては、予測不可能な、または組み合わせ的に大きな可能性のある領域をカバーするために、手動または人間主導のテストが必要になることがあります (コンピューター ゲーム、人工知能モデル、制御システムなどを考えてください)。これらは依然としてソフトウェア エンジニアリング業界で良い位置を占めていますが、この記事では自動テスト ケースに焦点を当てます。

いくつかの定義を理解したところで、最新のソフトウェア エンジニアリングのテスト方法と、それが問題解決方法にどのような変化をもたらしているかを詳しく見ていきましょう。

テスト駆動開発 (別名 TDD)

テスト駆動開発 (TDD) は、最初にテスト (または仕様) を実行可能コードに書き込み、テストが失敗することを確認し (最初は赤色)、要件を満たすソリューションを実装し、テストが成功することを確認する (緑色) ことによってソフトウェアを実装する方法論です。テストを正常に実行し (グリーン状態を維持)、反復しながら、可読性と柔軟性を高めるためにソリューションをリファクタリングします。この方法論の各ステップについて詳しく説明します。

  1. 要件を表すために失敗するテストを作成しますこれは、まだ存在しないクラス、まだ実装されていないメソッド、既存の実装で処理されていない状況、またはシステムがまだ実装していない新しい動作を使用する可能性があります。新しい要件が何であれ、この要件を実行可能なものとして表すテストを作成しますが、最初は失敗しました。このステップでは、不足している機能と、それが任意のレベルでどのように使用されるかを考えることができます。テストには、UAT、システム テスト、統合テスト、単体テストなどがあります。
  2. ニーズを満たすソリューションを実装します (緑色)要件を満たす最初の実装は、おそらく最も簡単な作業、または単純な「テストが期待するものを返す」 (不正行為のように感じますが、プロセスを信頼してください...) であり、テストが見えるようにするだけです。緑"。このステップでは、次のステップに進んでサイクルを再度実行できるように、問題を解決する最も直接的な方法を考える必要があります。
  3. テストをグリーンに保ちながら、容赦なくリファクタリングします (テストをグリーンに保ちます)ソフトウェア エンジニアリングの核心はこのステップで行われるため、ここではステップ 2 をパスしないでください。ここでは、テストと実装で使用されるインターフェイスを見て、より保守可能で柔軟なソリューションに近づいているかどうかを確認できます。パターンを見つけるにはさらにテストが必要です。システムの機能をカバーするテストが増えれば増えるほど、実装だけでなくテストも含めてより多くのリファクタリングが必要になります。ドメイン駆動設計にも従う場合は、システム内のモデルを改良することができます。ソリューションをより深く理解できるようにするため、モデルが変化するにつれて理解も進化し続けます。
  4. 反復しますシステムの機能要件と非機能要件をさまざまなレベル (UAT、システム テスト、統合テスト、単体テスト) でカバーするようになると、必然的に一部の要件が要件ではなくなったり、既存の要件に若干の変更が生じたりすることがわかります。新しいビジネス要件があり、特定のサブシステムからやり直す必要がある場合があります。いつテストを追加するか、テストを削除するか、パフォーマンスや効率を最適化するか、あるいは単に終了して次に進むべきかを認識することは、プロセスの重要な部分です。まだ完了していない限り、ステップ 1 に戻ります。

テストのないコード ベースがすでにある場合でも、TDD に従い始めることができることに注意してください。トップダウン (UAT から単体テストへ) またはボトムアップ (単体テストから UAT) に進むことができ、途中でインターフェイスのリファクタリングを開始できるため、インターフェイスが論理コンポーネントまたはドメイン モデルを表していると確信できるようになります。

最初から TDD に従うことには多くの利点があります。

  • テストを作成するときは、要件と設計を考慮する必要がありますテストが難しいコードは、通常、適切な設計慣行に従っていないことを意味します。テストをうまく表現できない場合は、要件をよく理解していないことを意味するため、テストを作成する前に、まず要件が何であるかを理解する必要があります。
  • 本番環境に到達する前であっても、所有するソリューションが要件を満たしているという確信が高まります。問題を事前に発見することで、本番環境で予見されるトラブルを回避できます。また、ニーズが満たされているかどうかを確認するために長時間待つのではなく、すでに存在するコーチングの問題の解決に集中することができます。デバッグに時間を費やす代わりに、他の問題を解決し、自信を持って段階的に価値を提供することに時間を費やしてください。
  • 整理して美しくする時間はありますTDD は、開発プロセスの一部をリファクタリングする時間を後回しにするのではなく、明示的に時間を確保します。時間がなくてリファクタリングを延期する必要がある場合は、テストは要件の状態を表し、プロセスの一部として実装の品質を向上させることができるため、後で実行することもできます。

それも問題ありませんが、TDD のコストといくつかの欠点も認識する必要があります。

  • 自動テストの作成と実行は決して安くありません一部の種類のテストは他の種類よりも作成が難しく、すべてが同じ値を生成するわけではありません。UAT を作成するには、特定のテスト フレームワークに関する専門知識が必要になる場合があります。また、実稼働環境のデプロイメントをシミュレートする場合は、すぐには入手できない特殊なハードウェア (強力な GPU や FPGA など) の使用が必要になる場合があります。一部のサービスでは、複数のサービスを完全に展開する必要があり、テスト目的でセットアップするには費用対効果が低い可能性があるため、いくつかの回り道が発生する可能性があります。
  • コードをテストすることの価値を示すのは、特にそれが機会費用として見られる場合には困難です重要なことは動作するものを出荷することであるため、テストは時間の無駄であると多くの人がまだ考えています。本番で失敗した場合は、資金が不足しないように十分なお金を稼ぐ必要があるため、ハッキングします。残念ながら、コードをテストではなく本番環境に出荷すると、コードや新機能をデプロイするたびにソリューションとビジネスの有効性が危険にさらされることになります。TDD は良い実践であり、TDD に従うことが良いアイデアである理由を示す成功事例はたくさんありますが、効果的なテスト範囲が将来の失敗や要件の変更に対する保険として見なされない限り、それは問題点になります。
  • 効果的なテストと 100% のテスト カバレッジを混同すると、大変なことになりますTDD は 100% のテスト カバレッジを達成することではなく、要件を実行可能なテストに表現することに重点を置いています。構築しているシステムの重要な側面を表している限り、テストは必要なだけ行うことができます。テスト カバレッジが 100% であるということは、テストがソリューションの重要な側面を表すのにどれだけ効果的であるかを示すものではありません。おそらく、解決しようとしている問題が確実に解決されるかどうかを確認するには、小規模なテスト セットが最も効果的です。

TDD は、この世界で遭遇するすべてのソフトウェア品質問題に対する万能薬ではありません。ただし、これは、ニーズを満たすシステムを自信を持って設計できるように、重要な焦点を維持するのに役立つ実践です。

自動テスト自動テスト

すでに TDD に従っている場合は、問題ありません。しかし、そうでない場合でも、次の場合に自動的に実行できるテストを用意することが重要です。

  • 開発中、「内部開発者ループ」内。統合開発環境またはワークステーションでテストを実行して、ソリューションが期待どおりに動作していることをすぐに確認できない場合は、ひどい目に合うことになります。自動テストを迅速に構築/実行でき、重要な要件を表現できるようにすることは、生産性を大幅に向上させるものであり、投資する価値があります。開発者がワークステーションで実行できる自動テストを作成する以上のことを行うと、自動テストの利点の 80% を達成したことになります。
  • 回帰テストスイートを維持します。バグが発生または発見された場合、最初に行うべきことは、失敗したテストでバグを再現することです。このようにして、バグを修正するプロセスを別の要件として管理し、回帰を検出するテストとして表現できます (つまり、ソフトウェアは過去に修正されたバグを示さないことになります)。より多くのバグを回帰テストに回すほど、システム上の実際の要件をより広範囲に表現でき、将来の再発を防ぐことができます。
  • システムの非機能的な側面もテストします。非機能要件とは、スループット、遅延、リソース消費、最小負荷要件、その他の観察可能な属性など、機能に厳密に関連付けられていないシステム品質を指します。これらの要件を自動化すると、それらの要件を設計要件と実装要件の一部にすることができるため、システムに変更が加えられたときに常に考慮されるようになります。

自動テストは、特に今日の最新のソフトウェア エンジニアリングの実践において、競争力のある高品質のソフトウェア システムを提供するための重要なツールになりつつあります。私たちが構築および展開しているシステムの複雑さと重要性を考慮すると、自動テストなしでどのように今後の作業を管理できるかを理解するのは困難です。

最新のテスト技術

自動テストを実装していると仮定すると、UAT、システム テスト、統合テスト、および単体テストを実行できます。また、回帰テスト スイートや自動テストとして表現される非機能要件もあります。テストの実践を現代のソフトウェア エンジニアリングの時代にどのように取り入れていますか?

特に、コントロール プレーンがワークロードとリソースの配置と管理をそれぞれ管理する Kubernetes のような環境で調整され、クラウド内の分散システムとして開発および展開されるソフトウェアの場合、パブリック クラウド プロバイダーはネットワークの存在と地理的多様性を提供します。管理リソースの提供に伴い、アプリケーションのアーキテクチャはますます複雑になっています。これらのアプリケーションのテストは非常に困難になり、コストがかかります。

この複雑さを管理し、世界中で大規模に利用できる最新のサービスの需要に確実に対応できるようにするには、次の点を考慮する必要があります。

  • 継続的インテグレーションと継続的デリバリに投資しますソフトウェアを実稼働環境に導入する前にテストしても、できることは限られていますが、実稼働環境の現実が開発中に予期されることはほとんどありません。統合環境でテストされたコードを、制御された安全な方法で本番環境に一貫して出荷する方法を確保することが、ソリューションを本番環境の現実に適応させるための鍵となります。テストに投資しているため、運用環境で見つかったバグは失敗したテストとして表現され、継続的インテグレーション (CI) および継続的デプロイメント (CD) パイプラインを通じて自動的に実行される可能性があります。これにより、市場投入までの時間が短縮され、エンジニアリング チームのフィードバック サイクルが短縮されます。
  • ファジー テストと自動障害検出に投資しますリモート API サービスであれ、内部コンポーネントであれ、依存するシステムに自動的に障害を挿入するソリューションは数多くあります。ファジングは、ランダムに生成された入力を使用して、潜在的なセキュリティ ホールや予期しない問題を見つけるテスト方法です。これらのテストは手書きのテストに代わるものではありませんが、システム用に作成する要件主導型のテストを強化して、開発プロセスの早い段階で潜在的な障害を検出できます。
  • 実稼働環境での小規模テスト (カナリア テストとも呼ばれます) では、システムの最も現実的なテストが行​​われます。運用環境でのテストをアプリケーションの配信および展開パイプラインの重要なリンクにします。
  • 人工知能と LLM を活用してテスト範囲を拡大します。GitHub Copilot または同様のテクノロジーにアクセスできる場合は、(会社とコードへの影響について法律顧問に相談した後) 既存システムの単体テスト、統合テスト、およびシステム テストを行うために、それらの使用を検討してください。または、TDD の使用を開始する場合は、AI 自動化の使用を検討して、内部ループでのこれらのテストの開発に費やす時間を削減します。結局のところ、開発者がテストのコーディングに費やす時間が問題である場合、AI はこのコストを削減する良い方法となるはずです。)

システムが分散され、処理規模が増大することでシステムがより複雑になるにつれ、私たちが開発しているさまざまな対話型システムの品質と正確性を保証するための自動テストの重要性はますます高まっています。

要約する

ソフトウェア システムの主要な要件を表す効果的な自動テストを作成および維持することは、今日のソフトウェア エンジニアリングの専門家にとって追求され、重要なスキルになりつつあります。テストの専門家がいる時代は終わり、今では誰もが開発者兼運用エンジニアです。現代のソフトウェア エンジニアリングでは、すべてのソフトウェア エンジニアリング担当者が自動テストの価値と、それが顧客に提供するソフトウェア システムの堅牢性、品質、有効性にどのような影響を与えるかを理解し、理解する必要があります。

結局のところ、ソフトウェア エンジニアリングとは、適切な問題を解決するために適切なものを構築することです。問題を効果的に解決するには、問題を解決するために何が必要かを知ることが重要です。

読んでくれてありがとう!

 最新のソフトウェア エンジニアリング - パート 1: システム設計

おすすめ

転載: blog.csdn.net/jeansboy/article/details/131703234
おすすめ