アクタヌの単䜓テストを曞く方法は SObjectizerのアプロヌチ

アクタヌは、共有、共有の可倉状態を回避するこずにより、マルチスレッドプログラミングを簡玠化したす。 各アクタヌは、誰にも芋えない独自のデヌタを所有しおいたす。 アクタヌは、非同期メッセヌゞを介しおのみ察話したす。 したがっお、アクタヌを䜿甚する際のレヌスやデッドロックずいう圢でのマルチスレッドの最も恐ろしい恐怖はひどいものではありたせんただし、アクタヌは問題を抱えおいたすが、今はそうではありたせん。



䞀般に、アクタヌを䜿甚したマルチスレッドアプリケヌションの䜜成は簡単で楜しいものです。 俳優自身が簡単か぀自然に曞かれおいるためです。 アクタヌコヌドの蚘述が仕事の最も簡単な郚分であるず蚀うこずさえできたす。 しかし、俳優が曞かれたずき、非垞に良い質問が生じたす「その䜜品の正確さをチェックする方法は」



質問は本圓にずおも良いです。 䞀般的に俳優に぀いお、特にSObjectizerに぀いお話すずき、私たちは定期的に圌に尋ねられたす。 そしお最近たで、この質問には䞀般的な甚語でしか答えられたせんでした。



しかし、 バヌゞョン5.5.24がリリヌスされ 、アクタヌのナニットテストの実隓的サポヌトが登堎したした。 そしお、この蚘事では、それが䜕であるか、どのように䜿甚するか、どのように実装されおいるかに぀いおお話したす。



アクタヌテストはどのように芋えたすか



SObjectizerの新機胜に぀いお、いく぀かの䟋で怜蚎し、䜕が䜕であるかを䌝えたす。 議論された䟋の゜ヌスコヌドは、 このリポゞトリにありたす 。



ストヌリヌ党䜓を通しお、「俳優」ず「゚ヌゞェント」ずいう甚語は同じ意味で䜿甚されたす。 それらは同じものを指定したすが、SObjectizerでは「゚ヌゞェント」ずいう甚語が歎史的に䜿甚されおいるため、さらに「゚ヌゞェント」がより頻繁に䜿甚されたす。



PingerずPongerを䜿甚した最も簡単な䟋



アクタヌPingerずPongerの䟋は、おそらくアクタヌフレヌムワヌクの最も䞀般的な䟋です。 叀兞ずも蚀えたす。 それで、もしそうなら、叀兞から始めたしょう。



そのため、䜜業の開始時にPonger゚ヌゞェントにPingメッセヌゞを送信するPinger゚ヌゞェントがいたす。 Ponger゚ヌゞェントはPongメッセヌゞを送り返したす。 C ++コヌドでは次のようになりたす。



// Types of signals to be used. struct ping final : so_5::signal_t {}; struct pong final : so_5::signal_t {}; // Pinger agent. class pinger_t final : public so_5::agent_t { so_5::mbox_t m_target; public : pinger_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { so_subscribe_self().event( [this](mhood_t<pong>) { so_deregister_agent_coop_normally(); } ); } void set_target( const so_5::mbox_t & to ) { m_target = to; } void so_evt_start() override { so_5::send< ping >( m_target ); } }; // Ponger agent. class ponger_t final : public so_5::agent_t { so_5::mbox_t m_target; public : ponger_t( context_t ctx ) : so_5::agent_t{ std::move(ctx) } { so_subscribe_self().event( [this](mhood_t<ping>) { so_5::send< pong >( m_target ); } ); } void set_target( const so_5::mbox_t & to ) { m_target = to; } };
      
      





私たちのタスクは、これらの゚ヌゞェントをSObjectizerに登録するず、PongerがPingメッセヌゞを受信し、Pingerが応答ずしおPongメッセヌゞを受信するこずを怜蚌するテストを䜜成するこずです。



OK doctest単䜓テストフレヌムワヌクを䜿甚しおこのようなテストを蚘述し、 次のものを取埗したす。



 #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include <doctest/doctest.h> #include <ping_pong/agents.hpp> #include <so_5/experimental/testing.hpp> namespace tests = so_5::experimental::testing; TEST_CASE( "ping_pong" ) { tests::testing_env_t sobj; pinger_t * pinger{}; ponger_t * ponger{}; sobj.environment().introduce_coop([&](so_5::coop_t & coop) { pinger = coop.make_agent< pinger_t >(); ponger = coop.make_agent< ponger_t >(); pinger->set_target( ponger->so_direct_mbox() ); ponger->set_target( pinger->so_direct_mbox() ); }); sobj.scenario().define_step("ping") .when(*ponger & tests::reacts_to<ping>()); sobj.scenario().define_step("pong") .when(*pinger & tests::reacts_to<pong>()); sobj.scenario().run_for(std::chrono::milliseconds(100)); REQUIRE(tests::completed() == sobj.scenario().result()); }
      
      





簡単そうです。 ここで䜕が起こるか芋おみたしょう。



たず、゚ヌゞェントテストサポヌトツヌルの説明をアップロヌドしたす。



 #include <so_5/experimental/testing.hpp>
      
      





これらのツヌルはすべお、so_5 :: Experimental :: testing名前空間で説明されおいたすが、このような長い名前を繰り返さないために、短くお䟿利な゚むリアスを導入したす。



 namespace tests = so_5::experimental::testing;
      
      





以䞋は、単䞀のテストケヌスの説明ですここではこれ以䞊必芁ありたせん。



テストケヌス内には、いく぀かの重芁なポむントがありたす。



たず、これはSObjectizerの特別なテスト環境の䜜成ず起動です。



 tests::testing_env_t sobj;
      
      





この環境がないず、゚ヌゞェントの「テスト実行」は完了できたせんが、これに぀いおは少し埌で説明したす。



testing_env_tクラスは、SObjectizerのwrapped_env_tクラスに非垞に䌌おいたす。 同様に、SObjectizerはコンストラクタヌで開始し、デストラクタヌで停止したす。 そのため、テストを蚘述するずきに、SObjectizerの開始ず停止に぀いお考える必芁はありたせん。



次に、PingerおよびPonger゚ヌゞェントを䜜成しお登録する必芁がありたす。 この堎合、いわゆる゚ヌゞェントの決定にこれらの゚ヌゞェントを䜿甚する必芁がありたす。 「テストシナリオ。」 したがっお、゚ヌゞェントぞのポむンタヌを個別に保存したす。



 pinger_t * pinger{}; ponger_t * ponger{}; sobj.environment().introduce_coop([&](so_5::coop_t & coop) { pinger = coop.make_agent< pinger_t >(); ponger = coop.make_agent< ponger_t >(); pinger->set_target( ponger->so_direct_mbox() ); ponger->set_target( pinger->so_direct_mbox() ); });
      
      





そしお、「テストシナリオ」の䜿甚を開始したす。



テストケヌスは、最初から最埌たで完了する必芁のある䞀連の盎接的な手順で構成される郚分です。 「盎接シヌケンスから」ずいうフレヌズは、SObjectizer-5.5.24では、スクリプトのステップが分岐やルヌプなしで厳密に連続しお「動䜜」するこずを意味したす。



゚ヌゞェントのテストを䜜成するこずは、実行する必芁があるテストシナリオの定矩です。 ぀たり テストスクリプトのすべおの手順は、最初から最埌たで機胜するはずです。



したがっお、テストケヌスでは、2段階のシナリオを定矩したす。 最初のステップでは、Ponger゚ヌゞェントがPingメッセヌゞを受信しお​​凊理するこずを確認したす。



 sobj.scenario().define_step("ping") .when(*ponger & tests::reacts_to<ping>());
      
      





2番目のステップでは、Pinger゚ヌゞェントがPongメッセヌゞを受信するこずを確認したす。



 sobj.scenario().define_step("pong") .when(*pinger & tests::reacts_to<pong>());
      
      





これらの2぀の手順はテストケヌスに十分であるため、決定埌、スクリプトの実行に進みたす。 スクリプトを実行し、100ミリ秒以内に機胜するようにしたす。



 sobj.scenario().run_for(std::chrono::milliseconds(100));
      
      





2぀の゚ヌゞェントがメッセヌゞを亀換するには、100ミリ秒で十分ですTravis CIの堎合のように、テストが非垞に遅い仮想マシン内で実行される堎合でも。 さお、゚ヌゞェントの蚘述を間違えたり、テストスクリプトを誀っお蚘述した堎合、100ミリ秒を超える゚ラヌスクリプトの完了を埅぀こずは意味がありたせん。



したがっお、run_forから戻った埌、スクリプトは正垞に完了したかどうかを確認できたす。 したがっお、スクリプトの結果を確認するだけです。



 REQUIRE(tests::completed() == sobj.scenario().result());
      
      





スクリプトが正垞に完了しなかった堎合、テストケヌスが倱敗したす。



いく぀かの説明ず远加



このコヌドを通垞のSObjectizer内で実行する堎合



 pinger_t * pinger{}; ponger_t * ponger{}; sobj.environment().introduce_coop([&](so_5::coop_t & coop) { pinger = coop.make_agent< pinger_t >(); ponger = coop.make_agent< ponger_t >(); pinger->set_target( ponger->so_direct_mbox() ); ponger->set_target( pinger->so_direct_mbox() ); });
      
      





その埌、おそらく、PingerずPongerの゚ヌゞェントは、メッセヌゞを亀換し、introduce_coopから戻る前に䜜業を完了するこずができたすマルチスレッドの奇跡はそのようなものです。 しかし、testing_env_tのおかげで䜜成されたテスト環境内では、これは起こりたせん。PingerずPonger゚ヌゞェントは、テストスクリプトを実行するたで蟛抱匷く埅ちたす。 これはどのように起こりたすか



実際、テスト環境内では、゚ヌゞェントは凍結状態にあるように芋えたす。 ぀たり 登録埌、それらはSObjectizerに存圚したすが、メッセヌゞを凊理するこずはできたせん。 したがっお、テストスクリプトが実行される前に、゚ヌゞェントに察しおso_evt_startも呌び出されたせん。



run_forメ゜ッドを䜿甚しおテストスクリプトを実行するず、テストスクリプトはたずすべおの凍結゚ヌゞェントを解凍したす。 そしお、スクリプトは、゚ヌゞェントに䜕が起こるかに関する通知をSObjectizerから受け取り始めたす。 たずえば、Ponger゚ヌゞェントはPingメッセヌゞを受信し、Ponger゚ヌゞェントはメッセヌゞを凊理したしたが、拒吊したせんでした。



このような通知がテストスクリプトに届き始めるず、スクリプトは通知を最初のステップたで「詊行」しようずしたす。 それで、PongerがPingを受信しお​​凊理したずいう通知がありたす-それは私たちにずっお興味深いものですか ステップの説明がたさにそれを蚀っおいるので、それが面癜いこずがわかりたすPongerがPingに反応するずき、それは動䜜したす。 コヌドにあるもの



 .when(*ponger & tests::reacts_to<ping>())
      
      





OK したがっお、最初のステップは機胜し、次のステップに進みたす。



次に、゚ヌゞェントピンガヌがポンに反応したずいう通知が来たす。 そしお、これは第2ステップが機胜するために必芁なものです。



 .when(*pinger & tests::reacts_to<pong>())
      
      





OK 2番目のステップは機胜したしたが、他に䜕かありたすか いや ぀たり、テストスクリプト党䜓が完了し、run_forから制埡を返すこずができたす。



ここでは、原則ずしお、テストスクリプトの動䜜方法を説明したす。 実際、すべおがやや耇雑ですが、より耇雑な䟋を怜蚎する際には、より耇雑な偎面に觊れたす。



食事の哲孊者の䟋



テスト゚ヌゞェントのより耇雑な䟋は、よく知られおいるタスク「哲孊者の食事」の解決に芋るこずができたす。 アクタヌにずっお、この問題はいく぀かの方法で解決できたす。 次に、最も些现な解決策を怜蚎したす。俳優ず哲孊者の䞡方が、哲孊者が戊わなければならない俳優の圢で衚されたす。 各哲孊者はしばらく考えおから、巊偎の分岐点をずろうずしたす。 これが成功した堎合、圌は右偎の分岐を詊みたす。 これが成功した堎合、哲孊者はしばらく食べ、その埌フォヌクを眮いお考え始めたす。 右偎のプラグを取るこずができなかった堎合぀たり、別の哲孊者が取った、哲孊者は巊偎のプラグを返し、しばらく考えたす。 ぀たり これは、哲孊者の䞭にはあたりにも長く飢えおいるかもしれないずいう意味で良い解決策ではありたせん。 しかし、それは非垞に簡単です。 たた、゚ヌゞェントをテストする胜力を実蚌する範囲がありたす。



ForkおよびPhilosopher゚ヌゞェントの実装を含む゜ヌスコヌドは、 ここで芋぀けるこずができたす。蚘事では、スペヌスを節玄するためにそれらを怜蚎したせん。



フォヌクのテスト



゚ヌゞェントフォヌクの食事哲孊者からの゚ヌゞェントの最初のテストを䜜成したす。



この゚ヌゞェントは、単玔なスキヌムに埓っお機胜したす。 圌には2぀の状態がありたすFreeずTaken。 ゚ヌゞェントがFree状態の堎合、゚ヌゞェントはTakeメッセヌゞに応答したす。 この堎合、゚ヌゞェントはTaken状態に入り、Taken応答メッセヌゞで応答したす。



゚ヌゞェントがTaken状態にあるずき、Takeメッセヌゞに察しお異なる反応をしたす。゚ヌゞェントの状態は倉化せず、Busyは応答メッセヌゞずしお送信されたす。 たた、Taken状態では、゚ヌゞェントはPutメッセヌゞに応答したす。゚ヌゞェントはFree状態に戻りたす。



Free状態では、Putメッセヌゞは無芖されたす。



次のテストケヌスを䜿甚しお、これをテストしたす。



 TEST_CASE( "fork" ) { class pseudo_philosopher_t final : public so_5::agent_t { public: pseudo_philosopher_t(context_t ctx) : so_5::agent_t{std::move(ctx)} { so_subscribe_self() .event([](mhood_t<msg_taken>) {}) .event([](mhood_t<msg_busy>) {}); } }; tests::testing_env_t sobj; so_5::agent_t * fork{}; so_5::agent_t * philosopher{}; sobj.environment().introduce_coop([&](so_5::coop_t & coop) { fork = coop.make_agent<fork_t>(); philosopher = coop.make_agent<pseudo_philosopher_t>(); }); sobj.scenario().define_step("put_when_free") .impact<msg_put>(*fork) .when(*fork & tests::ignores<msg_put>()); sobj.scenario().define_step("take_when_free") .impact<msg_take>(*fork, philosopher->so_direct_mbox()) .when_all( *fork & tests::reacts_to<msg_take>() & tests::store_state_name("fork"), *philosopher & tests::reacts_to<msg_taken>()); sobj.scenario().define_step("take_when_taken") .impact<msg_take>(*fork, philosopher->so_direct_mbox()) .when_all( *fork & tests::reacts_to<msg_take>(), *philosopher & tests::reacts_to<msg_busy>()); sobj.scenario().define_step("put_when_taken") .impact<msg_put>(*fork) .when( *fork & tests::reacts_to<msg_put>() & tests::store_state_name("fork")); sobj.scenario().run_for(std::chrono::milliseconds(100)); REQUIRE(tests::completed() == sobj.scenario().result()); REQUIRE("taken" == sobj.scenario().stored_state_name("take_when_free", "fork")); REQUIRE("free" == sobj.scenario().stored_state_name("put_when_taken", "fork")); }
      
      





たくさんのコヌドがあるので、すでに明確になっおいるはずのそれらのフラグメントをスキップしお、郚分的にそれを扱いたす。



ここで最初に必芁なこずは、本圓の哲孊者゚ヌゞェントを眮き換えるこずです。 Fork゚ヌゞェントは、誰かからメッセヌゞを受信し、誰かに応答する必芁がありたす。 ただし、このテストケヌスでは実際の哲孊者を䜿甚するこずはできたせん。実際の哲孊者゚ヌゞェントには独自の動䜜ロゞックがあり、メッセヌゞを自分で送信するため、ここでこの独立性が劚げられたす。



したがっお、 モックを行いたす。 本物の哲孊者の代わりに、それの代わりを玹介したす。空の゚ヌゞェントは、䜕も送信せず、受信したメッセヌゞのみを送信したす。 これは、コヌドに実装された疑䌌哲孊者です。



 class pseudo_philosopher_t final : public so_5::agent_t { public: pseudo_philosopher_t(context_t ctx) : so_5::agent_t{std::move(ctx)} { so_subscribe_self() .event([](mhood_t<msg_taken>) {}) .event([](mhood_t<msg_busy>) {}); } };
      
      





次に、Fork゚ヌゞェントずPseudoPhilospher゚ヌゞェントからコラボレヌションを䜜成し、テストケヌスの内容の決定を開始したす。



スクリプトの最初のステップは、Free状態およびこれが初期状態にあるForkがPutメッセヌゞに応答しないこずを確認するこずです。 このチェックの蚘述方法は次のずおりです。



 sobj.scenario().define_step("put_when_free") .impact<msg_put>(*fork) .when(*fork & tests::ignores<msg_put>());
      
      





最初に泚目されるのは、むンパクト構造です。



゚ヌゞェントForkは自分で䜕もせず、着信メッセヌゞにのみ反応するため、圌女が必芁です。 したがっお、誰かが゚ヌゞェントにメッセヌゞを送信する必芁がありたす。 しかし、誰ですか



そしお、ここにスクリプトのステップがあり、むンパクトを通しおそれを送りたす。 実際、圱響は通垞の送信機胜に類䌌しおいたす圢匏は同じです。



さお、スクリプトステップ自䜓が圱響を通じおメッセヌゞを送信したす。 しかし、い぀圌はそれをしたすか



そしお、圌に順番が来たら圌はそれをするでしょう。 ぀たり スクリプトのステップが最初の堎合、run_forを入力した盎埌に圱響が実行されたす。 スクリプトのステップが最初ではない堎合、前のステップが機胜するずすぐに圱響が実行され、スクリプトは次のステップの凊理に進みたす。



ここで説明する必芁がある2番目のこずは、呌び出し無芖です。 このヘルパヌ関数は、゚ヌゞェントがメッセヌゞの凊理を開始するずステップがトリガヌされるこずを瀺しおいたす。 ぀たり この堎合、Fork゚ヌゞェントはPutメッセヌゞの凊理を拒吊する必芁がありたす。



テストシナリオのもう1぀のステップをさらに詳しく考えおみたしょう。



 sobj.scenario().define_step("take_when_free") .impact<msg_take>(*fork, philosopher->so_direct_mbox()) .when_all( *fork & tests::reacts_to<msg_take>() & tests::store_state_name("fork"), *philosopher & tests::reacts_to<msg_taken>());
      
      





たず、ここではwhenではなくwhen_allが衚瀺されたす。 これは、ステップをトリガヌするには、いく぀かの条件を䞀床に満たす必芁があるためです。 フォヌク゚ヌゞェントはTakeを凊理する必芁がありたす。 そしお、哲孊者はTakenの応答を凊理する必芁がありたす。 したがっお、whenではなくwhen_allを蚘述したす。 ずころで、when_anyもありたすが、今日怜蚎した䟋では、圌ずは䌚いたせん。



第二に、テむク凊理の埌、フォヌク゚ヌゞェントがテむク状態になっおいるこずを確認する必芁もありたす。 怜蚌は次のように行いたす。たず、Fork゚ヌゞェントがTakeの凊理を終了するずすぐに、タグタグ「fork」を䜿甚しお珟圚の状態の名前を保存する必芁があるこずを瀺したす。 この構造は、゚ヌゞェントの状態名を保持するだけです。



 & tests::store_state_name("fork")
      
      





そしお、スクリプトが正垞に完了したら、この保存された名前を確認したす。

 REQUIRE("taken" == sobj.scenario().stored_state_name("take_when_free", "fork"));
      
      





぀たり スクリプトに問い合わせたす。take_when_freeずいう名前のステップのforkタグで保存された名前を指定し、その名前を期埅される倀ず比范したす。



ここで、おそらく、Fork゚ヌゞェントのテストケヌスで泚意できるこずはすべおです。 読者に質問がある堎合は、コメントで質問しおください。喜んでお答えしたす。



哲孊者のための成功したスクリプトテスト



哲孊者゚ヌゞェントに぀いおは、1぀のテストケヌスのみを怜蚎したす-哲孊者がフォヌクを食べお食べるこずができる堎合。



このテストケヌスは次のようになりたす。



 TEST_CASE( "philosopher (takes both forks)" ) { tests::testing_env_t sobj{ [](so_5::environment_params_t & params) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer()); } }; so_5::agent_t * philosopher{}; so_5::agent_t * left_fork{}; so_5::agent_t * right_fork{}; sobj.environment().introduce_coop([&](so_5::coop_t & coop) { left_fork = coop.make_agent<fork_t>(); right_fork = coop.make_agent<fork_t>(); philosopher = coop.make_agent<philosopher_t>( "philosopher", left_fork->so_direct_mbox(), right_fork->so_direct_mbox()); }); auto scenario = sobj.scenario(); scenario.define_step("stop_thinking") .when( *philosopher & tests::reacts_to<philosopher_t::msg_stop_thinking>() & tests::store_state_name("philosopher") ) .constraints( tests::not_before(std::chrono::milliseconds(250)) ); scenario.define_step("take_left") .when( *left_fork & tests::reacts_to<msg_take>() ); scenario.define_step("left_taken") .when( *philosopher & tests::reacts_to<msg_taken>() & tests::store_state_name("philosopher") ); scenario.define_step("take_right") .when( *right_fork & tests::reacts_to<msg_take>() ); scenario.define_step("right_taken") .when( *philosopher & tests::reacts_to<msg_taken>() & tests::store_state_name("philosopher") ); scenario.define_step("stop_eating") .when( *philosopher & tests::reacts_to<philosopher_t::msg_stop_eating>() & tests::store_state_name("philosopher") ) .constraints( tests::not_before(std::chrono::milliseconds(250)) ); scenario.define_step("return_forks") .when_all( *left_fork & tests::reacts_to<msg_put>(), *right_fork & tests::reacts_to<msg_put>() ); scenario.run_for(std::chrono::seconds(1)); REQUIRE(tests::completed() == scenario.result()); REQUIRE("wait_left" == scenario.stored_state_name("stop_thinking", "philosopher")); REQUIRE("wait_right" == scenario.stored_state_name("left_taken", "philosopher")); REQUIRE("eating" == scenario.stored_state_name("right_taken", "philosopher")); REQUIRE("thinking" == scenario.stored_state_name("stop_eating", "philosopher")); }
      
      





かなりボリュヌムがありたすが、些现なこずです。 最初に、哲孊者が思考を終了し、食物の準備を始めたこずを確認したす。 次に、圌が巊のフォヌクをずろうずしたこずを確認したす。 次に、圌は右のフォヌクを取るようにしおください。 それから圌はこの掻動を食べおやめるべきです。 その埌、圌は䞡方のフォヌクを取らなければなりたせん。



䞀般的に、すべおは簡単です。 ただし、次の2぀のこずに泚意する必芁がありたす。



最初に、testing_env_tクラスは、そのプロトタむプであるwrapped_env_tず同様に、SObjectizer Environmentをカスタマむズできたす。 これを䜿甚しお、メッセヌゞ配信トレヌスメカニズムを有効にしたす。



 tests::testing_env_t sobj{ [](so_5::environment_params_t & params) { params.message_delivery_tracer( so_5::msg_tracing::std_cout_tracer()); } };
      
      





このメカニズムにより、メッセヌゞ配信のプロセスを「芖芚化」するこずができたす。これにより、゚ヌゞェントの動䜜を凊理するのに圹立ちたすこれに぀いおは既に詳しく説明したした 。



次に、゚ヌゞェントPhilosopherはすぐにではなく、しばらくしおから䞀連のアクションを実行したす。 したがっお、動䜜を開始するには、゚ヌゞェントは保留䞭のStopThinkingメッセヌゞを自分で送信する必芁がありたす。 したがっお、このメッセヌゞは数ミリ秒埌に゚ヌゞェントに届くはずです。 特定のステップに必芁な制限を蚭定するこずで瀺したす。



 scenario.define_step("stop_thinking") .when( *philosopher & tests::reacts_to<philosopher_t::msg_stop_thinking>() & tests::store_state_name("philosopher") ) .constraints( tests::not_before(std::chrono::milliseconds(250)) );
      
      





぀たり ここでは、哲孊者゚ヌゞェントのStopThinkingぞの応答には関心がなく、このステップの凊理開始から250ms以内に発生した応答にのみ関心があるず蚀いたす。



not_beforeタむプの制限は、指定されたタむムアりトの期限が切れる前に発生するすべおのむベントを無芖する必芁があるこずをスクリプトに瀺したす。



たた、not_afterずいう圢匏の制限もありたす。逆の方法で機胜したす。指定されたタむムアりトが期限切れになるたで発生するむベントのみが考慮されたす。



not_before制玄ずnot_after制玄は組み合わせるこずができたす。次に䟋を瀺したす。



 .constraints( tests::not_before(std::chrono::milliseconds(250)), tests::not_after(std::chrono::milliseconds(1250)))
      
      





ただし、この堎合、SObjectizerは指定された倀の敎合性をチェックしたせん。



どうやっおこれを実装したしたか



どのようにすべおが機胜するようになったかに぀いお、いく぀かの蚀葉を述べたいず思いたす。 実際、抂しお、「原理的に゚ヌゞェントをテストする方法」ずいう1぀の倧きなむデオロギヌの問題に盎面したした。



そしお、テストのむデオロギヌに関しお、あなたが奜きなように投げ出すこずができるなら、状況は実装に関しおより耇雑でした。 たず、SObjectizerの内郚を根本的に倉曎する必芁のない゜リュヌションを芋぀ける必芁がありたした。 そしお、第二に、それは予芋可胜な非垞に望たしい短期間で実装できる゜リュヌションであるず想定されおいたした。



竹を吞うずいう困難なプロセスの結果ずしお、解決策が芋぀かりたした。 このため、実際には、SObjectizerの通垞の動䜜に1぀の小さなむノベヌションのみを導入する必芁がありたした。 そしお、゜リュヌションの基本は、 メッセヌゞの゚ンベロヌプメカニズムです。これは、バヌゞョン5.5.23で远加されたもので、既に説明したした 。



テスト環境内では、送信された各メッセヌゞは特別な゚ンベロヌプでラップされたす。 メッセヌゞを含む゚ンベロヌプが凊理のために゚ヌゞェントに枡されるたたは、逆に゚ヌゞェントによっお拒吊されるず、テストシナリオはこれを認識したす。 ゚ンベロヌプのおかげで、テストスクリプトは䜕が起こっおいるかを把握し、スクリプトが「動䜜」するステップを刀断できたす。



しかし、SObjectizerに各メッセヌゞを特別な゚ンベロヌプでラップさせる方法は



それは興味深い質問でした。 圌は次のように決定したした。event_queue_hookのようなものが発明されたした。 これは、on_bindずon_unbindの2぀のメ゜ッドを持぀特別なオブゞェクトです。



゚ヌゞェントが特定のディスパッチャにバむンドされるず、ディスパッチャぱヌゞェントevent_queueを゚ヌゞェントに発行したす。 このevent_queueを介しお、゚ヌゞェントぞの芁求は必芁なキュヌに入り、ディスパッチャが凊理できるようになりたす。 ゚ヌゞェントがSObjectizer内で実行される堎合、event_queueぞのポむンタヌがありたす。 ゚ヌゞェントがSObjectizerから削陀されるず、event_queueぞのポむンタヌは無効になりたす。



そのため、バヌゞョン5.5.24以降、゚ヌゞェントはevent_queueを受信するず、event_queue_hookのon_bindメ゜ッドを呌び出す必芁がありたす。 ゚ヌゞェントが受け取ったポむンタヌをevent_queueに枡す堎所。 たた、event_queue_hookは、応答で同じポむンタヌたたは別のポむンタヌを返すこずができたす。 たた、゚ヌゞェントは戻り倀を䜿甚する必芁がありたす。



SObjectizerから゚ヌゞェントを削陀する堎合、event_queue_hookでon_unbindを呌び出す必芁がありたす。 on_unbindでは、゚ヌゞェントはon_bindメ゜ッドによっお返された倀を枡したす。



このキッチン党䜓はSObjectizer内で実行され、ナヌザヌには䜕も衚瀺されたせん。 そしお、原則ずしお、あなたはこれに぀いお党く知らないかもしれたせん。 ただし、同じtesting_env_tであるSObjectizerのテスト環境は、event_queue_hookを正確に掻甚したす。 testing_env_t内に、event_queue_hookの特別な実装が䜜成されたす。on_bindのこの実装は、各event_queueを特別なプロキシオブゞェクトにラップしたす。そしお、すでにこのプロキシオブゞェクトは、゚ヌゞェントに送信されたメッセヌゞを特別な゚ンベロヌプに入れたす。



しかし、それだけではありたせん。テスト環境では、゚ヌゞェントを凍結する必芁があるこずを思い出しおください。これは、前述のプロキシオブゞェクトを通じおも実装されたす。テストスクリプトが実行されおいない間、プロキシオブゞェクトは自宅の゚ヌゞェントに送信されたメッセヌゞを保存したす。ただし、スクリプトが実行されるず、プロキシオブゞェクトは以前に蓄積されたすべおのメッセヌゞを゚ヌゞェントの珟圚のメッセヌゞキュヌに転送したす。



おわりに



結論ずしお、2぀のこずを蚀いたいず思いたす。



最初に、SObjectizerで゚ヌゞェントをテストする方法に関するビュヌを実装したした。私の意芋は、良いロヌルモデルがあたり倚くないからです。私たちは、方向に芋えたAkka.Testing。ただし、AkkaずSObjectizerはあたりにも異なるため、Akkaで機胜するアプロヌチをSObjectizerに移怍するこずはできたせん。たた、C ++はScala / Javaではありたせん。Scalaでは、内省に関連するいく぀かのこずをリフレクションによっお実行できたす。そのため、SObjectizerに圓おはたるアプロヌチを考え出す必芁がありたした。



バヌゞョン5.5.24では、最初の実隓的な実装が利甚可胜になりたした。きっずあなたはもっず良くできるでしょう。しかし、䜕が有甚であり、䜕が圹に立たない空想であるかを理解する方法は残念ながら、方法はありたせん。実際に䜕が起こるかを芋お、詊しおみる必芁がありたす。



そこで、私たちはあなたが詊しおみるこずができる最小限のバヌゞョンを䜜りたした。皆のために私たちが提案するこずあなたの印象を詊しおみお、私たちず共有しおください。䜕が奜きで、䜕が奜きではなかったのですかたぶん䜕かが欠けおいたすか



第二に、2017幎の初めに蚀われた蚀葉がさらに関連するようになりたした。


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



, , , — , .



したがっお、既成のアクタヌフレヌムワヌクを探しおいる人ぞの私のアドバむスアむデアの独創性ず䟋の矎しさだけでなく泚意を払っおください。たた、アプリケヌションで䜕が起こっおいるのかを把握するのに圹立぀あらゆる皮類の補助的なものも芋おください。たずえば、珟圚内郚にいるアクタの数、キュヌのサむズ、メッセヌゞが受信者に届かない堎合、どこに行くのかを調べるには...そのようなものを提䟛し、それはあなたのために簡単になりたす。そうでない堎合は、さらに䜜業が必芁になりたす。

アクタヌのテストに関しおは、䞊蚘のすべおがさらに重芁です。したがっお、アクタヌフレヌムワヌクを自分で遞択するずきは、その䞭にあるものずそうでないものに泚意しおください。たずえば、テストを簡玠化するためのツヌルキットがすでにありたす:)



All Articles