Java開発者向けの構成テスト:実務経験



コードのテストでは、すべてが明確になります(少なくとも、それらを記述する必要があるという事実)。 構成のテストでは、その存在そのものから始めて、すべてがはるかに明白ではありません。 誰かがそれらを書いていますか? それは重要ですか? 難しいですか? 彼らの助けを借りてどのような結果を達成できますか?

これも非常に有用であることが判明しました。これを行うことは非常に簡単であり、同時に構成のテストには多くの微妙な違いがあります。 どのもの-実際の経験に基づいて猫の下に描かれています。

この資料は、 Ruslan cheremin Cheremin (ドイツ銀行のJava開発者)によるレポートの転写に基づいています。 次は一人称スピーチです。


私の名前はルスラン、ドイツ銀行で働いています。 これから始めます。



テキストはたくさんありますが、遠くからはロシア語のように思えるかもしれません。 しかし、それは真実ではありません。 これは非常に古くて危険な言語です。 私は簡単なロシア語に翻訳しました:



今日お話しすることを簡単に説明します。 コードがあるとします:



つまり、最初は何らかのタスクがあり、それを解決するためのコードを作成すると、おそらく収益が得られます。 何らかの理由でこのコードが正しく機能しない場合、間違ったタスクを解決し、間違ったお金を稼いでいます。 ビジネスはそのようなお金を好まない-彼らは財務諸表で見た目が悪い。

そのため、重要なコードにはテストがあります:



通常あります。 今、おそらく、ほぼ全員がそれを持っています。 テストは、コードが適切な問題を解決し、適切な収益を上げていることを確認します。 ただし、サービスはコードに限定されず、コードの横にも構成があります。



少なくとも私が参加したほとんどすべてのプロジェクトでは、この構成は何らかの形で行われました。 (初期のUIの場合、構成ファイルはありませんでしたが、すべてがUIを介して構成されていたいくつかのケースしか思い出せません)この構成には、ポート、アドレス、およびアルゴリズムパラメーターがあります。

構成をテストすることが重要なのはなぜですか?


ここに秘isがあります:構成のエラーは、プログラムの実行にコードのエラーと同じくらい害を及ぼします。 それらも、コードが間違ったタスクを実行する原因になる可能性があります-と上記を参照してください。

構成は通常コンパイルされないため、構成内のエラーを見つけることはコード内よりもさらに困難です。 プロパティファイルを例として引用しましたが、一般にさまざまなオプション(JSON、XML、YAMLに保存されます)がありますが、これらがコンパイルされず、したがってチェックされないことが重要です。 誤ってJavaファイルを封印した場合、ほとんどの場合、コンパイルに合格しません。 プロパティのランダムなタイプミスは誰も興奮させず、機能します。

また、IDEは、プロパティファイルの形式(たとえば)について最も基本的なもののみを知っているため、構成のエラーも強調表示しません。キーと値があり、それらの間には「等しい」、コロン、またはスペースがあります。 しかし、IDEは、値が数値、ネットワークポート、またはアドレスでなければならないという事実については何も知りません。

また、UATまたはステージング環境でアプリケーションをテストしても、何も保証されません。 原則として、各環境の構成は異なり、UATではUAT構成のみをテストしたためです。

もう1つの微妙な点は、実稼働環境でも、構成エラーがすぐに表示されない場合があることです。 サービスがまったく開始されない場合があります-これは良いシナリオです。 しかし、それは開始でき、非常に長い時間-瞬間Xまで、エラーが必要な正確なパラメーターが必要になるまで動作します。 そして、ここでは、最近実際には変更されていないサービスが突然機能しなくなったことがわかります。

結局、私が言ったように、構成のテストはホットなトピックになるはずです。 しかし、実際には次のようになります。



少なくとも私たちの場合はそうでした-ある時点まで。 そして、私のレポートの目的の1つは、あなたにとってもこのような見方をやめることです。 私はあなたにこれをプッシュできることを願っています。

3年前、ドイツ銀行の私のチームでは、Andrei SatarinがQAリーダーとして働いていました。 構成のテストのアイデアをもたらしたのは彼でした-つまり、彼は最初にそのようなテストを取ってコミットしました。 6か月前、前のHeisenbugで、彼は見たとおりに構成をテストすることについて講演しました。 科学記事の側面と、構成エラーとその結果に遭遇した大企業の経験の両方から、彼が問題を広く見てくれたので、私は見ることをお勧めします。

私のレポートはより狭くなります-実際の経験について。 開発者として、構成テストを作成したときにどのような問題に遭遇したか、およびこれらの問題をどのように解決したかについて説明します。 私の決定は最良の決定ではないかもしれません、これらはベストプラクティスではありません-これは私の個人的な経験であり、私は広く一般化しないようにしました。

レポートの概要:



最初の部分はやる気を起こさせるものです:私たちがすべてを始めた最も単純なテストについて説明します。 さまざまな例があります。 そのうちの少なくとも1つがあなたと共鳴すること、つまり、何らかの類似の問題とその解決策を目にすることを願っています。

最初の部分のテスト自体は単純で、原始的です-エンジニアリングの観点からはロケット科学はありません。 しかし、それらが迅速に行えるということだけが特に価値があります。 これは、構成テストへのこのような「簡単な入力」であり、これらのテストを書くには心理的な障壁があるため重要です。 そして、私は「これができる」ことを示したいと思います。今や、私たちにとってはうまくいきました。そして、誰も死んでいないのに、私たちは今3年生きています。

2番目の部分は、その後の処理についてです。 簡単なテストをたくさん書いたとき、サポートの問題が生じます。 それらのいくつかは落ち始めます、あなたは彼らがおそらく強調したエラーを理解します。 これは常に便利であるとは限りません。 そして、より複雑なテストを作成するという疑問が生じます。結局のところ、単純なケースをすでにカバーしているので、もっと面白いものが欲しいです。 そして、ここでもベストプラクティスはありません。私たちのために働いたいくつかのソリューションを説明します。

3番目の部分は、テストがかなり複雑で混乱しやすい構成のリファクタリングをどのようにサポートできるかについてです。 再びケーススタディ-どうやってやったのか。 私の観点から見ると、これは小さな穴を塞ぐだけでなく、より大きなタスクを解決するために構成テストをどのように拡張できるかの例です。

パート1.「そのようにできます」


現在、最初の構成テストが何であったかを理解するのは困難です。 アンドレイはホールに座って、彼は私が嘘をついたと言うことができます。 しかし、それはすべてこれから始まったように思えます:



状況は次のとおりです。1つのホストにn個のサービスがあり、それぞれがポートで独自のJMXサーバーを起動し、監視JMXをエクスポートします。 すべてのサービスのポートはファイルで構成されます。 しかし、ファイルはいくつかのページを占有し、他の多くのプロパティがあります-多くの場合、異なるサービスのポートが競合することがわかります。 間違いを犯しやすいです。 その後、すべては些細なことです。サービスに依存しない人のためにサービスが上昇した後、一部のサービスは上昇しません-テスターは激怒します。

この問題はいくつかの行で解決されます。 このテストは(私には思えますが)最初のテストで、次のように見えました。



複雑なことは何もありません。構成ファイルが置かれているフォルダーを調べ、それらを読み込み、プロパティとして解析し、名前に「jmx.port」が含まれる値をフィルター処理し、すべての値が一意であることを確認します。 値を整数に変換する必要さえありません。 おそらく、ポートのみがあります。

これを見たときの私の最初の反応はさまざまでした:



第一印象:私の美しい単体テストでは何ですか? なぜファイルシステムに登ったのですか?

そして、驚きが来ました:「それは何だろう?」

こういったテストを書くのを難しくする心理的な障壁があるように見えるので、私はこれについて話している。 それから3年が経ち、プロジェクトはそのようなテストで一杯になりましたが、構成の誤りにぶつかった同僚がテストを書かないことがよくあります。 コードについては、誰もがすでに回帰テストの作成に慣れているため、見つかったエラーは再現されません。 しかし、彼らは設定のためにそれをしません、何かが干渉しています。 対処する必要のある心理的な障壁があります。そのため、私はそのような反応について言及し、それが現れた場合に自分から認識できるようにします。



次の例はほとんど同じですが、少し変更されています-すべての「jmx」を削除しました。 今回は、something-there-portと呼ばれるすべてのプロパティをチェックします。 これらは整数値であり、有効なネットワークポートである必要があります。 Matcherの背後にあるvalidNetworkPort()は、カスタムhamcrest Matcherを非表示にします。これにより、値がシステムポートの範囲を超え、一時ポートの範囲を下回ることが確認されます。これはマッチャーです。

このテストはまだ非常に原始的です。 どの特定のプロパティをチェックしているかは示されていないことに注意してください-それは巨大です。 このような1つのテストでは、「... port」という名前の500個のプロパティをチェックし、必要なすべての条件で、すべてが目的の範囲の整数であることを確認できます。 彼らが書いたら、十数行-それだけです。 これは非常に便利な機能です。構成が単純な形式であるために表示されます:2つの列、キーと値。 したがって、大量処理が可能です。

別のテスト例。 ここで何を確認していますか?



実際のパスワードが本番環境に漏れていないことを確認します。 すべてのパスワードは次のようになります。



プロパティファイルのテストをたくさん書くことができます。 私はこれ以上例を挙げません-私は自分自身を繰り返したくありません、アイデアは非常に単純です、そしてすべてが明確でなければなりません。

...そして、これらのテストを十分に書いた後、興味深い質問が浮上します:構成とはどういう意味ですか、その境界はどこですか? プロパティファイルを構成と見なし、カバーしました-同じスタイルでカバーできるものは何ですか?

構成の考慮事項


少なくとも通常のビルドプロセスでは、プロジェクト内にコンパイルされていないテキストファイルが多数あることがわかります。 それらはサーバーで実行されるまで検証されません。つまり、エラーが遅れて表示されます。 これらのすべてのファイルは、ある程度拡張されていますが、構成と呼ぶことができます。 少なくとも、それらはほぼ同じようにテストされます。

たとえば、展開プロセス中にデータベースにロールされるSQLパッチのシステムがあります。



SQL * Plus用に作成されています。 SQL * Plusは60年代からのツールであり、あらゆる種類の奇妙なことを必要とします。たとえば、ファイルの終わりが新しい行にあることを確認するためです。 もちろん、人々は60年代に生まれたわけではないので、定期的に行末を付けるのを忘れています。



繰り返しますが、同じ12行で解決されます。すべてのSQLファイルを選択し、末尾にスラッシュがあることを確認します。 シンプル、便利、高速。

「テキストファイルのような」別の例はcrontabです。 crontabサービスが開始および停止します。 ほとんどの場合、2つのエラーが発生します。



まず、スケジュール式の形式。 それほど複雑ではありませんが、起動前に誰もチェックしませんので、余分なスペースやカンマなどを簡単に挿入できます。

第二に、前の例のように、ファイルの終わりも新しい行になければなりません。

そして、これはすべて確認が非常に簡単です。 ファイルの終わりは理解できますが、スケジュールを確認するために、cron式を解析する既製のライブラリを見つけることができます。 報告の前に、私はグーグルで調べました:少なくとも6人がいました。 これは6つ見つけましたが、一般にもっとあるかもしれません。 私たちが書いたとき、式の内容をチェックする必要はなく、構文の正確さだけを確認する必要があったため、cronが正常にロードしたため、見つかったもののうち最も単純なものを取りました。

原則として、より多くのチェックを終了することができます-あなたが正しい曜日に開始することを確認してください、あなたが仕事の真ん中にサービスを停止しないことを確認してください。 しかし、これは私たちにとってそれほど有用ではないことが判明し、私たちは気にしませんでした。

うまく機能する別のアイデアは、シェルスクリプトです。 もちろん、bashスクリプトの本格的なパーサーをJavaで書くことは、勇敢な人にとっては喜びです。 しかし、結論として、これらのスクリプトの多くは完全なbashではありません。 はい、コードが直接、地獄と地獄であるbashスクリプトがあり、1年に1回立ち寄って、誓って逃げます。 ただし、多くのbashスクリプトは同じ構成です。 目的の値に設定されたシステム変数と環境変数が多数あり、これらの変数を使用する他のスクリプトを構成します。 そして、そのような変数は、このbashファイルから簡単にgrepして、それらについて何かをチェックできます。



たとえば、JAVA_HOMEが各環境にインストールされていること、または使用するjniライブラリがLD_LIBRARY_PATHにあることを確認します。 どういうわけか、Javaのあるバージョンから別のバージョンに移行し、テストを拡張しました。JAVA_HOMEが環境のそのサブセットに「1.8」を含むことを確認し、徐々に新しいバージョンに移行しました。

以下に例を示します。 結論の最初の部分を要約します。



パート2.より複雑なケース


さらに複雑なテストに移りましょう。 ここに示されているような些細なチェックのほとんどをカバーした後、疑問が生じます:より複雑なものをチェックすることは可能ですか?

「より難しい」とはどういう意味ですか? ここで説明したテストは、おおよそ次の構造になっています。



特定のファイルに対して何かをチェックします。 つまり、ファイルを調べて、それぞれに特定の条件チェックを適用します。 したがって、多くのことを検証できますが、より有用なシナリオがあります。



たとえば、UIアプリケーションは環境サーバーに接続します。 最も可能性が高いのは、UIとサーバーが異なるモジュールであり、プロジェクトではないとしても、それらの構成が異なる場合、同じ構成ファイルを使用する可能性は低いことです。 したがって、1つの環境のすべてのサービスが、コマンドの配布に使用される1つのキー管理サーバーに接続されるように、それらをリンクする必要があります。 繰り返しますが、おそらく、これらは異なるモジュール、異なるサービスであり、一般に異なるチームがそれらを開発します。

または、すべてのサービスが同じデータベース、同じものを使用します-異なるモジュールのサービス。

実際、このような状況があります。多くのサービスには、それぞれ独自の構成構造があります。それらのいくつかを減らし、交差点で何かを確認する必要があります。



もちろん、それを正確に行うことができます。1つ目、2つ目をロードし、どこかで何かを引き出し、テストコードを接着します。 しかし、コードがどれだけ大きくなり、どれだけ読みやすくなるか想像できます。 私たちはこれから始めましたが、それがどれほど難しいかを実感しました。 より良い方法は?

あなたが夢を見ると、それはより便利だったので、私はテストが人間の言語で説明するように見えることを夢見ました:

@Theory public void eachEnvironmentIsXXX( Environment environment ) { for( Server server : environment.servers() ) { for( Service service : server.services() ) { Properties config = buildConfigFor( environment, server, service ); //… check {something} about config } } } 

環境ごとに、条件が満たされます。 これを確認するには、環境からサーバーのリスト、サービスのリストを見つける必要があります。 次に、構成をロードし、交差点で何かを確認します。 したがって、私はそのようなものが必要です、私はそれを配置レイアウトと呼びました。



このデータ構造を取得するために、どのサーバーにどのサービスをどの環境に配置するかという、アプリケーションのデプロイ方法にアクセスする機会をコードから得る必要があります。 そしてそれから進んで、構成のロードと処理を開始します。

配置レイアウトは、各チームおよび各プロジェクトに固有です。 私が描いた-これは一般的なケースです。通常、サーバー、サービスのセットがあり、サービスには1つだけではなく、構成ファイルのセットがある場合があります。 テストに役立つ追加のパラメータが必要な場合がありますが、追加する必要があります。 たとえば、サーバーが配置されているラックが重要になる場合があります。 Andreyのレポートでは、バックアップ/プライマリサービスを異なるラックに配置することがサービスにとって重要である場合の例を示しました。彼の場合、展開レイアウトでラックの表示を保持する必要があります。



私たちの目的にとって、原則として、特定のデータセンターであるサーバーの地域も重要であるため、バックアップ/プライマリは異なるデータセンターにあります。 これらはすべて追加のサーバープロパティであり、プロジェクト固有ですが、スライド上では共通点です。

配置レイアウトはどこで入手できますか? 大企業にはインフラストラクチャ管理システムがあり、すべてがそこに記載されているようです。

少なくとも、2つのプロジェクトでの私の実践では、最初にハードコーディングし、次に3年後にハードコーディングする方が簡単であることが示されています...

私たちはこのプロジェクトで3年間生きてきました。 第二に、それでも1年以内にインフラストラクチャ管理と統合するように思われますが、これらすべての年はこのように生きてきました。 経験から、できるだけ早く既製のテストを取得するためにIMとの統合タスクを延期することは理にかなっています。 そして、サーバー間のサービスの分散はそれほど頻繁に変更されないため、この統合はそれほど必要ではないことが判明する場合があります。

ハードコードは文字通り次のようになります。

 public enum Environment { PROD( PROD_UK_PRIMARY, PROD_UK_BACKUP, PROD_US_PRIMARY, PROD_US_BACKUP, PROD_SG_PRIMARY, PROD_SG_BACKUP ) … public Server[] servers() {…} } public enum Server { PROD_UK_PRIMARY(“rflx-ldn-1"), PROD_UK_BACKUP("rflx-ldn-2"), PROD_US_PRIMARY(“rflx-nyc-1"), PROD_US_BACKUP("rflx-nyc-2"), PROD_SG_PRIMARY(“rflx-sng-1"), PROD_SG_BACKUP("rflx-sng-2"), public Service[] services() {…} } 

最初のプロジェクトで使用する最も簡単な方法は、それぞれのサーバーのリストを使用して環境を列挙することです。 サーバーのリストがあり、サービスのリストがあるはずです、しかし、だまされました:開始スクリプト(これも構成の一部です)があります。



各環境に対してサービスを実行します。 そして、services()メソッドは、単にサーバーのファイルからすべてのサービスをgrep'aします。 これは、それほど多くの環境がなく、サーバーもほとんど追加または削除されないためです。しかし、多くのサービスがあり、頻繁にシャッフルされます。 ハードコーディングされたレイアウトを頻繁に変更しないように、スクリプトからサービスの実際のレイアウトをロードすることは理にかなっています。

このようなソフトウェア構成モデルを作成すると、楽しいボーナスが表示されます。 たとえば、次のようなテストを作成できます。



テストでは、すべての環境にすべての主要なサービスが存在します。 4つの主要なサービスがあり、残りはそうであるかもしれないし、そうでないかもしれないが、これらの4つがなければ意味がありません。 それらをどこでも忘れていないこと、すべてが同じ環境内にバックアップされていることを確認できます。 ほとんどの場合、これらのインスタンスのUATを構成するときにこのようなエラーが発生しますが、PRODにリークすることもあります。 結局のところ、UATのバグは時間とテスターの神経を浪費します。

構成モデルの関連性を維持するという疑問が生じます。 このためのテストを書くこともできます。

 public class HardCodedLayoutConsistencyTest { @Theory eachHardCodedEnvironmentHasConfigFiles(Environment env){ … } @Theory eachConfigFileHasHardCodedEnvironment(File configFile){ … } } 

構成ファイルがあり、コード内に配置レイアウトがあります。 そして、あなたは各環境/サーバー/などのためにそれを確認することができます 適切な構成ファイルがあり、必要な形式のファイルごとに、対応する環境があります。 1つの場所に何かを追加するのを忘れるとすぐに、テストは失敗します。

一番下の行は展開レイアウトです。


続けましょう。 テストが書かれた後、彼らは1年間「落ち着き」、いくつかは落ち始めます。 早く落ち始める人もいますが、それほど怖くはありません。 1年前に書かれたテストが失敗したとき、そのエラーメッセージを見て、理解できません。


これが無効なネットワークポートであることを理解して同意するとしますが、どこにありますか? 講演の前に、プロジェクト内に1,200のプロパティファイルがあり、90のモジュールにまたがっており、合計24,000行あることを確認しました。 (驚きましたが、数えれば、これはそれほど多くありません-4つのファイルに対する1つのサービスです。)このポートはどこにありますか?

assertThat()にメッセージ引数があることは明らかです。場所を識別するのに役立つ何かを入力できます。 しかし、テストを書くとき、あなたはそれについて考えません。 そして、あなたが考える場合でも、あなたはまだ1年でそれを理解できるように、どの説明が十分に詳細になるかを推測する必要があります。 この点を自動化したいので、エラーを見つけるための多少明確な説明を自動生成するテストを書く方法があります。

繰り返しますが、私は次のようなものを夢見、夢見ました。

 SELECT environment, server, component, configLocation, propertyName, propertyValue FROM configuration(environment, server, component) WHERE propertyName like “%.port%” and propertyValue is not validNetworkPort() 

これはそのような擬似SQLです。まあ、私はSQLを知っているだけで、脳は使い慣れたものから解決策を捨てました。 ほとんどの構成テストは、同じタイプの複数の部分で構成されているという考え方です。 最初に、条件によってパラメーターのサブセットが選択されます。



次に、このサブセットに関して、値に関して何かをチェックします。



そして、値が要求を満たさないプロパティがある場合、これはエラーメッセージで受け取る「シート」です。



かつては、SQLのようなパーサーを記述できるかどうかさえ考えていました。今は難しくないからです。 しかし、IDEはそれをサポートせず、それを提案しないことに気づいたので、人々はIDEプロンプトなしで、コンパイルなしで、検証なしで、この自作の「SQL」について盲目的に書く必要があります-これはあまり便利ではありません。 そのため、プログラミング言語でサポートされているソリューションを探す必要がありました。 .NETがあれば、LINQが役立ちます。これはほとんどSQLに似ています。

JavaにはLINQはありません。可能な限りストリームに近いものです。 これは、このテストがストリームでどのように見えるかです:

 ValueWithContext[] incorrectPorts = flattenedProperties( environment ) .filter( propertyNameContains( ".port" ) ) .filter( !isInteger( propertyValue ) || !isValidNetworkPort( propertyValue ) ) .toArray(); assertThat( incorrectPorts, emptyArray() ); 

flattenedProperties()は、この環境のすべての構成、すべてのサーバー、サービスのすべてのファイルを取得し、それらを大きなテーブルに展開します。 これは基本的にSQLに似たテーブルですが、Javaオブジェクトのセットの形式です。 そしてflattenedProperties()はこの文字列のセットをストリームとして返します。



次に、このJavaオブジェクトのセットにいくつかの条件を追加します。 この例では、propertyNameに「ポート」を含むものを選択し、値が整数に変換されないか、有効な範囲から変換されないものをフィルターします。 これらは誤った値であり、理論的には空のセットである必要があります。



それらが空のセットではない場合、次のようなエラーがスローされます。



パート3.リファクタリングのサポートとしてのテスト


通常、コードテストは最も強力なリファクタリングサポートの1つです。 リファクタリングは危険なプロセスであり、多くのやり直しが必要です。そのため、アプリケーションが引き続き実行可能であることを確認したいと思います。 これを確認する1つの方法は、最初にすべてをテストでオーバーレイし、次にそれでリファクタリングすることです。

そして今、私の前に構成をリファクタリングするタスクがありました。 7年前に1人の賢い人によって書かれたアプリケーションがあります。 このアプリケーションの構成は次のようになります。



これは一例であり、もっとたくさんあります。 トリプルレベルの入れ子順列。これは構成全体で使用されます。



構成自体にはいくつかのファイルがありますが、それらは互いに含まれています。 iuプロパティの小さな拡張機能-Apache Commons Configurationを使用します。これは、括弧内の包含と許可のみをサポートします。

そして、著者はこれら二つのことだけを使って素晴らしい仕事をしました。 彼はそこにチューリングマシンを構築したと思います。 いくつかの場所では、彼は包含と置換を使用して計算を行おうとしているようです。 このチューリングシステムが完全かどうかはわかりませんが、彼は、私の意見では、これがそうであることを証明しようとしました。

そして男は去った。 アプリケーションが動作し、彼は銀行を去りました。 すべてが機能し、構成を完全に理解しているのは誰もいません。

別のサービスを利用する場合、10個のインクルージョン、3倍の深さ、すべてを展開すると合計450個のパラメーターが含まれます。 実際、この特定のサービスはそれらの10〜15%を使用し、残りのパラメーターは他のサービス用です。ファイルは共有されているため、いくつかのサービスで使用されます。 しかし、正確に10〜15%がこの特定のサービスを使用していることを理解するのはそれほど簡単ではありません。 著者は明らかに理解した。 とても賢い人です

それぞれのタスクは、構成とそのリファクタリングを簡素化することでした。 同時に、この状況ではこの可能性は低いため、アプリケーションを動作させ続けたいと思いました。 私が欲しい:


問題は、システムが非常に冗長であるため、それらが現在どの程度適切に接続されているかがわからないことです。 たとえば、将来を見据えて、リファクタリング中に、実稼働構成の1つでバックアップクリップに4つのサーバーが必要であることが判明しましたが、実際には2つありました。 冗長性のレベルが高いため、これに気づいた人はいませんでした-エラーが偶然に表面化したのですが、実際には冗長性のレベルは予想よりもずっと低くなっています。 ポイントは、現在の構成がどこでも正しいという事実に頼ることができないということです。

新しい構成と古い構成を比較することはできないという事実につながります。 同等の場合もありますが、同時にどこか間違っています。 論理コンテンツを確認する必要があります。

最小プログラム:必要な各サービスの個別のパラメーターを分離し、ポートがポートであるか、アドレスがアドレスであるか、TTLが正の数であるなど、正当性を確認します。 そして、サービスが基本的にメインエンドポイントで接続する主要な関係を確認します。 少なくともこれを達成したかった。 つまり、前の例とは異なり、ここでのタスクは個々のパラメーターを検証することではなく、完全なネットワークのチェックで構成全体をカバーすることです。

それをテストするには?

 public class SimpleComponent { … public void configure( final Configuration conf ) { int port = conf.getInt( "Port", -1 ); if( port < 0 ) throw new ConfigurationException(); String ip = conf.getString( "Address", null ); if( ip == null ) throw new ConfigurationException(); … } … } 

この問題をどのように解決しましたか? いくつかの単純なコンポーネントがありますが、この例では最大限に単純化されています。 (Apache Commons Configurationに出会っていない人のために:ConfigurationオブジェクトはPropertiesに似ていますが、型付きメソッドgetInt()、getLong()などがまだあります。これらは小さなステロイドのjuPropertiesであると想定できます。)コンポーネントに2つのパラメーターが必要であるとします:例えば、TCPアドレスとTCPポート。 それらを引き出して確認します。 ここの4つの共通部分は何ですか?



これは、パラメーターの名前、タイプ、デフォルト値(ここでは簡単です。nullと-1、時には正常な値があります)およびいくつかの検証です。 ここのポートは、あまりにも単純に、不完全に検証されます。通過するポートを指定できますが、有効なネットワークポートではありません。 したがって、私もこの瞬間を改善したいと思います。 しかし、まず第一に、これら4つのことを1つのものに変えたいと思います。 たとえば、これ:

 IProperty<Integer> PORT_PROPERTY = intProperty( "Port" ) .withDefaultValue( -1 ) .matchedWith( validNetworkPort() ); IProperty<String> ADDRESS_PROPERTY = stringProperty( "Address" ) .withDefaultValue( null ) .matchedWith( validIPAddress() ); 

そのような複合オブジェクトは、その名前、デフォルト値、および検証を実行できることを知っているプロパティの説明です(ここでは、ハムクレストマッチャーを再び使用します)。 そして、このオブジェクトには次のようなインターフェースがあります:

 interface IProperty<T> { /* (name, defaultValue, matcher…) */ /** lookup (or use default), * convert type, * validate value against matcher */ FetchedValue<T> fetch( final Configuration config ) } class FetchedValue<T> { public final String propertyName; public final T propertyValue; … } 

つまり、特定の実装に固有のオブジェクトを作成した後、構成から表すパラメーターを抽出するように彼に依頼できます。 そして、彼はこのパラメーターを引き出し、プロセスをチェックインします。パラメーターがない場合は、デフォルト値を指定し、目的のタイプに導き、すぐに名前とともに返します。

つまり、パラメーターの名前と、この構成から要求した場合にサービスが確認する実際の値です。 これにより、複数のコード行を1つのエンティティにラップできます。これは、最初に必要な簡略化です。

問題を解決するために必要な2番目の単純化は、構成にいくつかのプロパティを必要とするコンポーネントを導入することでした。 コンポーネント構成モデル:



これらの2つのプロパティを使用するコンポーネントがあり、その構成用のモデルがあります-このクラスが実装するIConfigurationModelインターフェイスです。 IConfigurationModelは、コンポーネントが行うすべてを実行しますが、構成に関連する部分のみを実行します。 コンポーネントが特定のデフォルト値で特定の順序でパラメーターを必要とする場合-IConfigurationModelはこの情報をそれ自体で結合し、カプセル化します。 コンポーネントの他のすべてのアクションは、彼にとって重要ではありません。 これは、構成アクセスに関するコンポーネントモデルです。



このビューの秘Theは、モデルが結合可能であることです。 他のコンポーネントを使用するコンポーネントがあり、それらがそこで結合される場合、同様に、この複雑なコンポーネントのモデルは、2つのサブコンポーネントの呼び出しの結果をマージできます。

つまり、構成モデルの階層を、コンポーネント自体の階層と並行して構築できます。 上位モデルで、fetch()を呼び出します。これは、名前から構成から取り出したパラメーターからシートを返します。これは、対応するコンポーネントがリアルタイムで必要とするパラメーターとまったく同じです。 もちろん、すべてのモデルを正しく記述した場合。

つまり、タスクは、構成にアクセスできるアプリケーション内の各コンポーネントに対してこのようなモデルを作成することです。 私のアプリケーションには、そのようなコンポーネントがかなりありました。アプリケーション自体はかなり緑豊かですが、コードを積極的に再利用するため、70のメインクラスのみが構成されています。 彼らのために、私は70のモデルを書かなければなりませんでした。

費用:


自分自身を構成するコンポーネントのコードで画面を開くだけで、次の画面で対応するConfigurationModelのコードを作成しました。 それらのほとんどは、示されている例のように簡単です。 場合によっては、分岐と条件付き遷移があります-コードは分岐しやすくなりますが、すべてが解決されます。 私はこの問題を1.5週間から2週間で解決し、70個のコンポーネントすべてについてモデルを説明しました。

その結果、すべてをまとめると、次のコードが得られます。



各サービス/環境/などについて 構成モデル、つまりこのツリーの最上位ノードを取得し、構成からすべてを取得するように要求します。 この時点で、すべての検証が内部を通過し、各プロパティは、設定から自身を引き出したときに、その値が正しいかどうかをチェックします。 少なくとも1つがパスしない場合、例外が発生します。 すべてのコードは、すべての値が単独で有効であることを確認することによって取得されます。

サービスの相互依存性


サービスの相互依存関係を確認する方法については、まだ質問がありました。 これはもう少し複雑です。相互依存関係の種類を調べる必要があります。 相互依存関係は、サービスがネットワークエンドポイントで「満たす」必要があるという事実に要約されることがわかりました。 サービスAは、サービスBがパケットを送信するアドレスを正確にリッスンする必要があり、その逆も同様です。 私の例では、異なるサービスの構成間のすべての依存関係がこれになりました。 この問題を非常に簡単に解決することができました。異なるサービスからポートとアドレスを取得してチェックします。 多くのテストがあるでしょう、それらはかさばるでしょう。 私は怠け者で、これは欲しくありませんでした。 したがって、そうしなかった。

まず、このネットワークエンドポイント自体をなんとか抽象化したかった。 たとえば、TCP接続の場合、必要なパラメーターはアドレスとポートの2つだけです。 マルチキャスト接続の場合、4つのパラメーター。 それを何らかのオブジェクトに変えたいと思います。 エンドポイントオブジェクトでこれを行いましたが、内部には必要なものがすべて隠されています。 このスライドは、アウトバウンドTCPネットワーク接続であるOutcomingTCPEndpointの例を示しています。

 IProperty<IEndpoint> TCP_REQUEST = outcomingTCP( // (+matchers, +default values) “TCP.Request.Address”, “TCP.Request.Port» ); class OutcomingTCPEndpoint implements IEndpoint { //(localInterface, localAddress, multicastGroup, port) @Override boolean matches( IEndpoint other); } 

Endpoint matches(), Endpoint, , .

« »? , : , , - , — . , , / . , , .

, , ---, , Endpoint. ConfigurationModels — , . ? :

 ValueWithContext[] allEndpoints = flattenedConfigurationValues(environment) .filter( valueIsEndpoint() ) .toArray(); ValueWithContext[] unpairedEndpoints = Arrays.stream( allEndpoints ) .filter( e -> !hasMatchedEndpoint(e, allEndpoints) ) .toArray(); assertThat( unpairedEndpoints, emptyArray() ); 

environment' endpoint', , , , . . ÂŤ Âť O(n^2), , endpoint' , .

Endpoint , , . , , - .

, , , «» — , . . , , . , .

. , , . , , , c, , .

ConfigurationModel :


, . , , , — . : , . , , , , .

. , ConfigurationModels, . , UDP- , , .

, endpoints , .dot. . — .

. 結論:


Heisenbug 2018 Piter , : 6-7 Heisenbug . . 1 — .

Source: https://habr.com/ru/post/J427487/


All Articles