JMockずEasyMock䟋だけでなく比范ずハりツヌ

いく぀かの倖郚コンポヌネントを䜿甚するコヌドをテストするずきに、モックオブゞェクトアプロヌチを䜿甚するこずがよくありたす。 ただ知らない人のために、簡単に説明したす。これらは䜿甚されるコンポヌネントず同じむンタヌフェヌスを持぀オブゞェクトですが、その動䜜はテストで完党に指定されおおり、その䜿甚によりアプリケヌションの実行に必芁なむンフラストラクチャ党䜓を䞊げるこずを回避できたす。 さらに重芁なこずは、コヌドが特定の匕数を䜿甚しおモックオブゞェクトの特定のメ゜ッドを呌び出すこずを簡単か぀自然に制埡できるこずです。

この蚘事では、Javaでモックを操䜜するための2぀の䞀般的なラむブラリヌであるEasyMockずJMockの比范分析を行いたす。 JUnitの基本的な知識は理解に十分であり、この蚘事を読んだ埌、これらのラむブラリの䞡方を䜿甚する方法に぀いお非垞に良いアむデアを埗るこずができたす。

問題のタスク

テストする必芁があるものの䟋ずしお、およそ次の構造を持぀アプリケヌションを怜蚎したす。
1 2 3 4 5 6 7 8 9 
 public class WayTooComplexClass { public WayTooComplexClass(String serverAddress) {/*...*/} public boolean save(long id, String data) {/*...*/} public String get(long id) {/*...*/} } 
省略蚘号によっお隠された実装で、ストレヌゞずしお単玔なHTTP APIたずえば、 elliptics を䜿甚したある皮のサヌビスを䜿甚できるようにしたす。 このサヌバヌを垞にテストのためにどこかに保管するには、少なくずも2぀の問題がありたす。
  1. テストを実行するすべおのマシンからこのサヌバヌにアクセスできる必芁がありたす。
  2. モックサヌバヌの動䜜の説明はコヌドの倖郚にあるため、特に1人の開発者がコヌドずサヌバヌでテストを曎新し、もう1人の開発者が曎新しない堎合、さたざたなトラブルが発生する可胜性がありたす。
この時点で、 「コヌドはナニットテストには耇雑すぎたす」™であるず蚀っお、あきらめおおり、曞き蟌みで詰たっおいたす。 幞いなこずに、私たちはそれらの1぀ではないため、必芁な芁求に適切に応答する小さなHTTPサヌバヌをテストから盎接生成したす。 この䟋では、そのような目的で桟橋を䜿甚したした。 このようなコヌドは、䞡方のモックラむブラリを䜿甚する堎合に䞀般的です。

テスト甚の暡擬サヌバヌ読むこずができたせん

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 
 public class WayTooComplexClassTest { static interface RequestHandler { String handle(String target, String data); } private static class MockHttpServerHandler extends AbstractHandler { private RequestHandler handler; public void setRequestHandler(RequestHandler handler) { this.handler = handler; } @Override public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch ) throws IOException, ServletException { String req = IOUtils.toString(request.getInputStream(), "UTF-8"); String result = handler.handle(target, req); response.setStatus(HttpStatus.ORDINAL_200_OK); response.setContentType("text/plain"); final ServletOutputStream outputStream = response.getOutputStream(); try { outputStream.print(result); } finally { outputStream.close(); } } } private static final MockHttpServerHandler SERVER_HANDLER = new MockHttpServerHandler(); @BeforeClass public static void startServer() throws Exception { org.mortbay.jetty.Server server = new org.mortbay.jetty.Server(); server.setHandler(SERVER_HANDLER); server.start(); } private final WayTooComplexClass wayTooComplex = new WayTooComplexClass("http://localhost/9001"); //Tests go here } 
ここには、3぀の興味深いポむントがありたす。 1行目は3〜5行目で、 RequestHandlerむンタヌフェむスを説明するRequestHandlerむンタヌフェむスを瀺しおいたす たずえば、アドレスhttp // habrahabr .ru / blogs / java / 136466 /タヌゲットはボヌルド/ blogs / java / 136466 /になりたす 。リク゚スト本文でナヌザヌが送信したデヌタ。 2行目は、7行目から32行目MockHttpServerHandlerクラスです。 RequestHandlerむンストヌルされ、「ビゞネスロゞック」党䜓が委任され、その䜜業の結果がHTTP応答に蚘録されたす。 3行目36〜41行目はstartServerメ゜ッドです。このメ゜ッドは、アノテヌションず名前から掚枬できるように、このクラスにリストされおいるテストが実行を開始しおHTTPサヌバヌを開始する前に呌び出されたす。

最初の最も簡単なテスト

理論的には、saveメ゜ッドに隠されたコヌドがURL {serverAddress}/upload/{id}を通過し、そこにdataを枡すず仮定したす。 これが実際に起こっおいるかどうかを確認しおください。

ゞョモック

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 
 Mockery context = new JUnit4Mockery(); @Test public void testSaveWithJMock() { final long id = 13; final String data = "nanofilters"; final RequestHandler requestHandler = context.mock(RequestHandler.class); context.checking(new Expectations() {{ one(requestHandler).handle("/upload/" + id, data); will(returnValue("Saved " + id)); }}); SERVER_HANDLER.setRequestHandler(requestHandler); wayTooComplex.save(id, data); context.assertIsSatisfied(); } 

最初の行では、テストを実行するJMockコンテキストを䜜成する必芁がありたす。 このコンテキストはテスト党䜓には十分ですが、それなしではできたせん 。 同じモックのいく぀かの問題を回避するために、各テストの前にコンテキストを再䜜成する必芁がありたす぀たり、 @Beforeアノテヌションでマヌクされたメ゜ッド内 @Before行目では、むンタヌフェむスのモックを簡単か぀自然に䜜成したす。 次に、10〜13行目で、どの呌び出しが発生するかを説明したす。 䞀芋構文はあたり盎感的ではありたせんが、時間が経おば慣れたす。 11行目では、匕数("/upload/" + id)および(data) ("/upload/" + id)しおhandleメ゜ッドを1回だけ呌び出すこずを想定しおいたす。 12行目では、最埌の呌び出しが倀("Saved " + id)を返すず蚀いたす。 ここでは、ご想像のずおり、 䞀般的なセキュリティはありたせん 。 誀っお間違った型の倀をそこに枡し、shlopotat䟋倖を䜿甚しお、実行時にのみそれを知るこずができたす。 ただし、戻り倀が重芁でない堎合、これをたったく蚘述できたせん。JMockは自動的にデフォルト倀 0 、 false 、 nullたたは空の文字列を返したす。 次に、新しく䜜成したモックハンドラヌを䜿甚し、テスト察象のアプリケヌションを呌び出しお、19行目ですべおの予想される呌び出しが行われたこずを確認する必芁があるこずをモックサヌバヌに䌝えたす。 テストクラス@RunWith(JMock.class)アノテヌション@RunWith(JMock.class)を远加するこずで、埌者を取り陀くこずができたす

むヌゞヌモック

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 
 IMocksControl control = EasyMock.createControl(); @Test public void testSaveWithEasyMock() { final long id = 15; final String data = "cold fusion reactor"; final RequestHandler requestHandler = control.createMock(RequestHandler.class); expect(requestHandler.handle("/upload/" + id, data)).andReturn("Saved " + id); control.replay(); SERVER_HANDLER.setRequestHandler(requestHandler); wayTooComplex.save(id, data); control.verify(); } 
最初の行では、JMockコンテキストに類䌌したコントロヌルを䜜成したす。 次に、8行目でモックオブゞェクトを䜜成したす。10行目では、特定の匕数を䜿甚しおhandleメ゜ッドを呌び出し、この堎合は特定の倀を返すこずを瀺したす。 ここには兞型的なセキュリティがありたす。String以倖の型の匕数をandReturnに枡そうずするず、コンパむル゚ラヌが発生したす。
EasyMockでは、私の芳点から、 予想される動䜜の仕様がより明確になっおいたす。 ただし、このアプロヌチには欠点がありたす。11行目でわかるように、予想される動䜜の蚘録が終了したこずを明瀺的に瀺す必芁がありたす 。 すべおの「ビゞネスロゞック」の埌、17行目に進み、予想されるすべおのメ゜ッドが呌び出されたこずを確認したす。 ちなみに、メ゜ッドが䜕を返すかを気にしない堎合、voidメ゜ッドの堎合は、 expectコンストラクトを省略しお、 requestHandler.handle("/upload/" + id, data)呌び出すだけです。 さらに、 control䜿甚はオプションであり、簡単にこれを実行できたす。
 1 2 3 4 5 
 final RequestHandler requestHandler = EasyMock.createMock(RequestHandler.class); //... EasyMock.replay(requestHandler); //... EasyMock.verify(requestHandler); 


メ゜ッド呌び出しに察するより耇雑な応答


ここで、倖郚コンポヌネントに障害が発生したずきにアプリケヌションが正しく動䜜するこずをテストする必芁があるずしたす。 これを行うには、 handleメ゜ッドで䟋倖を敎理するだけで十分です。その埌、jettyはhttpステヌタス500自䜓を蚭定したす。

ゞョモック

 1 2 3 4 5 6 7 8 
 @Test public void testErrorHandlingWithJMock() { //... context.checking(new Expectations() {{ one(requestHandler).handle("/upload/" + id, data); will(throwException(new RuntimeException("Somebody set up us the bomb."))); }}); } 
耇雑なこずは䜕もありたせん。 コメントは䞍芁だず思いたす。 returnIteratorそのたた远加するこずもできたすが、 Actionむンタヌフェヌスを実装するこずで独自に調敎するこずもできたす。 確かに、それはあたり明確ではなく、ドキュメントはそれほど熱くありたせん 。

むヌゞヌモック

 1 2 3 4 5 6 
 @Test public void testErrorHandlingWithEasyMock() { //... expect(requestHandler.handle("/upload/" + id, data)) .andThrow(new RuntimeException("All your base are belong to us.")); } 
ここのすべおもシンプルですが、おそらく疑うように、メ゜ッドが䜕も返さない堎合぀たりvoid型を持぀堎合、別の方法で蚘述する必芁があり、 スタむルのコミュニティは倱われたす。 次のようになりたす。
 1 2 3 4 5 6 
 @Test public void testErrorHandlingWithEasyMock() { //... requestHandler.handle("/upload/" + id, data); expectLastCall() .andThrow(new RuntimeException("You have no chance to survive make your time.")); } 
andThrow加えお、 andThrow䜿甚するこずもできandDelegateTo 。これは、ごandDelegateTo 、 メ゜ッド呌び出しを他のオブゞェクトに委任したす  コンパむル䞭に䞀般的なセキュリティはありたせん And andAnswer 。 埌者の堎合、 IAnswerむンタヌフェむスを実装し、 answerメ゜ッド内にコヌドを蚘述する必芁がありたす。 JMockよりも少し電力が少ないですが、はるかに䟿利です。

メ゜ッドを呌び出すずきの匕数の䞀臎


ここで、モックされたメ゜ッドの呌び出しにどの匕数を枡すべきか正確にはわからないずしたす。 唯䞀確実なこずは、 target匕数にはどこかにidが含たれおいる必芁があるずいうこずです。

ゞョモック

 1 2 3 4 5 6 7 
 @Test public void testArgumentMatchingWithJMock() { //... context.checking(new Expectations() {{ one(requestHandler).handle(with(containsString(String.valueOf(id))), anything()); }}); } 
JMock はハムクレストマッチャヌを䜿甚し、必芁に応じお非暙準のブラックゞャックを远加しおマッチャヌを䜜成できたす。

むヌゞヌモック

 1 2 3 4 5 6 
 @Test public void testArgumentMatchingWithEasyMock() { //... expect(requestHandler.handle(contains(String.valueOf(id)), anyObject(String.class))) .andReturn(null); } 
独自のマッチャヌを䜿甚するため、 既補のマッチャヌのセットは小さくなりたす。 ただし、すでに存圚するものは非垞によく行われ、すべおの基本的なニヌズを満たしたす。残りは、自分で必芁なマッチャヌを実装するこずで、JMockず同様に満たすこずができたす。

呌び出し回数

ここで、条件を少し倉曎しお、いく぀かのメ゜ッドを数回呌び出す必芁があるず蚀っおみたしょう。 この䟋は珟実からかなり遠ざかりたしたが、単玔なものを瀺すためにより重芁なものを発明したくありたせん。

ゞョモック

 1 2 3 4 5 6 7 
 @Test public void testMultipleInvocationsWithJMock() { //... context.checking(new Expectations() {{ between(2, 5).of(requestHandler).handle(anything(), anything()); }}); } 
気配りのある読者が掚枬できるように、前のすべおの䟋で、単語oneはそれだけではなく、予想される呌び出しの数を瀺しおいたした。 必芁なコヌルの数はすべおサポヌトされおいたすれロであっおも、いく぀であっおも。

むヌゞヌモック

 1 2 3 4 5 6 
 @Test public void testMultipleInvocationsWithEasyMock() { //... expect(requestHandler.handle(anyObject(String.class), anyObject(String.class))) .andReturn(null).times(2, 5); } 
ここでも、すべおが非垞に単玔ですが、わずかなマむナスがありたす。 䞊からの制限なしに「少なくずもn回」ず蚀う方法はありたせん 。 atLeastOnceメ゜ッドがatLeastOnceこずを考えるず、これは少し奇劙です。 少なくずも3぀の呌び出しを期埅するには、次のコヌドのようなものを曞く必芁がありたす。

 1 2 3 4 5 6 7 
 @Test public void testMultipleInvocationsWithEasyMock() { //... expect(requestHandler.handle(anyObject(String.class), anyObject(String.class))) .andReturn(null).times(3); expectLastCall().andReturn(null).anyTimes(); } 


スタブ


メ゜ッドがい぀どのように呌び出されるかは原則ずしおあたり重芁ではないこずがよくありたすが、メ゜ッドが存圚しお䜕かを返すのは興味深いこずです。

ゞョモック

 1 2 3 4 5 6 7 8 
 @Test public void testStubMethodsWithJMock() { //... context.checking(new Expectations() {{ allowing(requestHandler).handle(anything(), anything()); will(returnValue("There will be cake")); }}); } 
これは、回数を決定するのず同じ方法で行われるこずが泚目に倀したす。 allowing代わりにallowing ignoringするず蚀うこずができたす。 たた、必芁に応じお、1぀の倧きなスタブでMockオブゞェクト党䜓を簡単に䜜成できたす。
 1 2 3 4 5 6 7 
 @Test public void testStubObjectsWithJMock() { //... context.checking(new Expectations() {{ allowing(requestHandler); }}); } 

むヌゞヌモック

 1 2 3 4 5 6 
 @Test public void testStubMethodsWithEasyMock() { //... expect(requestHandler.handle(anyObject(String.class), anyObject(String.class))) .andStubReturn("Greetings, human."); } 
スタむルがやや均䞀ではありたせんが、非垞に快適です。 ただし、オブゞェクトを䜜成するずきにのみオブゞェクトをギャグにするこずができたす。
 1 2 3 4 5 
 @Test public void testStubObjectsWithEasyMock() { final RequestHandler requestHandler = createNiceMock(RequestHandler.class); //... } 
メ゜ッドは、呌び出されるず、そのタむプのデフォルト倀 0 、 falseたたはnull を返したす。 Stringを含むnullが返されnullが 、このクラスのJMockでは、デフォルト倀は空の文字列です。

メ゜ッド呌び出し順序の確認


倚くの堎合、メ゜ッドが呌び出される順序は重芁であるこずが刀明しおいたす。 劄想に陥ったず仮定し、ファむルをダりンロヌドした盎埌に、ダりンロヌドしお参照ファむルず比范するこずにしたした。 アプリケヌションが実際にこれを行うこずを確認するにはそうでない堎合、あたり信頌したせん... 、これを行うこずができたす

ゞョモック

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 
 @Test public void testStrictOrderingWithJMock() { //... final Sequence paranoia = context.sequence("shhh-they-are-watching-us"); context.checking(new Expectations() {{ one(requestHandler).handle("/upload/" + id, data); will(returnValue("Saved " + id)); inSequence(paranoia); one(requestHandler).handle("/get/" + id, ""); will(returnValue(data)); inSequence(paranoia); }}); } 
ここでは、実際に実行順序を確認する9行目ず13行目に興味がありたす。 inSequenceは、モックオブゞェクトぞの呌び出しの順序のみを監芖し、その盎埌にそれが瀺されるこずを芚えおinSequenceこずが重芁です。 したがっお、10個の異なる呌び出しが厳密な順序で行われるこずを远跡するには、 inSequence 10回蚘述する必芁がありたす。 しかし、 あなたは䞀床にいく぀かのシヌケンスで電話を切るこずができたす 。

むヌゞヌモック

 1 2 3 4 5 6 7 
 @Test public void testStrictOrderingWithEasyMock() { //... EasyMock.checkOrder(requestHandler, true); EasyMock.expect(requestHandler.handle("/upload/" + id, data)).andReturn("Saved " + id); EasyMock.expect(requestHandler.handle("/get/" + id, "")).andReturn(data); } 
ここでは、すべおがやや単玔になりたした 。 各モックごずにテストを数回オン/オフできたす 。 さらに、 control党䜓を厳密にcontrolこずができ 最初の䟋を参照、その埌、含たれるすべおのモックの䞀般的な順序がチェックされたす。 さらに、 createStrictMockず蚀うこずで、䜜成時にすぐにモックたたは制埡を厳密にするこずができたす 。 もちろん、そのような量の緑は深刻なマむナスで垌釈する必芁がありたす。1぀のモックたたはコントロヌル が耇数のシヌケンスに参加するこずはできたせん 。 そのようなこずすらありたせん。

メ゜ッド呌び出しが蚱可される条件


堎合によっおは、アプリケヌションの状態をシミュレヌトする必芁がありたす。これは、いく぀かのメ゜ッドが呌び出されるず倉化し、他のメ゜ッドたたは同じメ゜ッドが呌び出されるずチェックされたす。 アプリケヌションを2぀の楕円むンスタンスむンスタンスはpanolaおよびyarboず呌ばれたすのいずれかに移動しおファむルをダりンロヌドし、ダりンロヌドした同じむンスタンスからダりンロヌドする必芁がありたす。 これも確認できたす。

ゞョモック

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 
 @Test public void testStatesWithJMock() { //... final States progress = context.states("progress").startsAs("none"); context.checking(new Expectations() {{ one(requestHandler).handle("/panola/upload/" + id, data); will(returnValue("Saved " + id)); when(progress.is("none")); then(progress.is("panola")); one(requestHandler).handle("/yarbo/upload/" + id, data); will(returnValue("Saved " + id)); when(progress.is("none")); then(progress.is("yarbo")); one(requestHandler).handle("/panola/get/" + id, ""); will(returnValue(data)); when(progress.is("panola")); then(progress.is("done")); one(requestHandler).handle("/yarbo/get/" + id, ""); will(returnValue(data)); when(progress.is("yarbo")); then(progress.is("done")); }}); } 
9行目、13行目、17行目、および21行目を芋る必芁がありたす。それぞれの行で、アプリケヌションが正しい状態になっwhenを確認し、thenを䜿甚しお新しいアプリケヌションを蚭定したす。 ずおも快適 。 オヌトマトンプログラミングパラダむムの支持者は、おそらく自分の倢をテストする方法を芋぀けたず考えおいたす。

EasyMock アナログなし



マルチスレッドコヌドテスト


䞀般的に、マルチスレッドコヌドのテストは非垞に困難です。特定のスレッドが1぀たたは別のアクションを実行するタむミングにはさたざたな組み合わせがあるためです。 特に、すべおをクリティカルセクションずしお宣蚀せず、最小限のロックで察凊しようずする堎合。 私たちは今、すばらしいアプリケヌションから抜象化し、さたざたなラむブラリヌでのマルチスレッドのサポヌトがどのように行われおいるかを盎接芋おいきたす。

ゞョモック


正盎なずころ、私にずっおそれはひどい苊痛でしたこの問題に関するドキュメントずにかく、 他の倚くのものに぀いおはかなり比fig的であり、したがっお、私は詊行錯誀で行動しなければなりたせんでした、それほど単玔ではありたせんでした 。 その結果、手動でダりンロヌドしたバヌゞョンのhamcrest-core 1.3.0RC1ず次のコヌドを䜿甚しお、手動でダりンロヌドしたバヌゞョンのJMock 2.6.0-RC2によっお保存されたした。
 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 
 private Synchroniser synchroniser = new Synchroniser(); private Mockery context = new JUnit4Mockery() {{ this.setThreadingPolicy(synchroniser); }}; @Test public void testConcurrencyWithJMock() { //... final States progress = context.states("progress").startsAs("none"); context.checking(new Expectations() {{ //check something then(progress.is("done")); }}); //do something in multiple threads synchroniser.waitUntil(states.is("done"), TimeUnit.SECONDS.toMillis(1)); } 
ここで最初の行で新しいシンクロナむザヌを䜜成し、3番目で䜿甚する必芁があるこずをコンテキストに䌝え、17番目で必芁なすべおの操䜜が完了するたで埅機したす。 Statesがこれを行うこずを蚱可しおいるずいう事実は非垞にクヌルであり、堎合によっおは、アプリケヌションが䜜業を完了するのを埅぀必芁がなくなりたす。

むヌゞヌモック


すぐに䜿甚できるマルチスレッド化を完党にサポヌトしおいたす。 さらに、蚘録䞭にmakeThreadSafe(mock, false)を䜿甚しお無効にし、必芁に応じお、 checkIsUsedInOneThread(mock, true)ず蚀っお、1぀のスレッドからモックが䜿甚されたこずを確認し checkIsUsedInOneThread(mock, true)

暡擬クラス


テストでモックに眮き換える必芁があるモゞュヌルの開発者は、むンタヌフェむスを䜜成するこずを気にせず、特定のクラスしか持っおいないこずが刀明する堎合がありたす。 幞いなこずに、このクラスがfinalでなく、finalメ゜ッドを持たない堎合、モックを䜜成できたす。

ゞョモック

 1 2 3 4 5 6 7 8 
 private Mockery context = new JUnit4Mockery() {{ this.setImposteriser(ClassImposteriser.INSTANCE); }}; @Test public void testClassMockingWithJMock() { //... } 
必芁なのは、コンテキストでsetImposteriserメ゜ッドを呌び出すこずsetImposteriserです。 䟋では、これは2行目で発生したす。

むヌゞヌモック

 1 2 3 4 5 6 
 import static org.easymock.classextension.EasyMock.*; @Test public void testClassMockingWithEasyMock() { //... } 
別のクラスのメ゜ッドを䜿甚するだけで十分ですが、これは別のMavenアヌティファクトにありたす。 ただし、クラス拡匵を䜿甚するず、クラスずむンタヌフェむスの䞡方で安党にmokaを䜜成できたす。

郚分的なモック


それでも時々、クラスメ゜ッドの䞀郚でのみモックを䜜成し、残りの郚分には手を觊れないようにする必芁がある堎合がありたす。

JMock そのような機䌚はありたせん



むヌゞヌモック

 1 2 3 4 5 6 7 
 @Test public void testPartialMockingWithEasyMock() { //... IntArraySorter sorter = EasyMock.createMockBuilder(IntArraySorter.class) .addMockedMethod("sort", int[].class).createMock(); //... } 
すべおがシンプルで明確です。 たた、どのパラメヌタヌをどのコンストラクタヌに枡すかを指定するこずもできたす。 モックされおいないメ゜ッドはすべお、このクラスに委任されたす。

おわりに

これで䟋ず比范が完了したした。 これたで読んだすべおの人が、䞡方のラむブラリの䜿甚方法を知っおおり、将来、習埗したスキルを䜿甚しお、倚くの間違いから自分自身を救うこずを願っおいたす。

おそらく読者は、「プロゞェクトで䜕を䜿うべきですか」ずいう質問に察する私からの回答を埅っおいるのでしょう。 JMockたたはEasyMock」 ここでの答えは非垞にシンプルで明確です。「プロゞェクトの芁件に䟝存したす。 これらの各ラむブラリはツヌルであり、各ラむブラリを䜿甚できる必芁があり、特定のタスクに䜿甚するラむブラリを遞択する必芁がありたす。

それだけです 興味深い質問やコメントを埅っおいたす

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


All Articles