AnyStub、Java接続スタブラむブラリ

倚くのプラットフォヌムずは異なり、Javaには接続スタブラむブラリが䞍足しおいたす。 この䞖界に長い間䜏んでいるなら、おそらくWireMock、Betamax、たたはSpockに粟通しおいるはずです。 テストの倚くの開発者は、Mockitoを䜿甚しおオブゞェクトの動䜜、ロヌカルh2デヌタベヌスを䜿甚したDataJpaTest、Cucumberテストを蚘述したす。 今日は、これらのアプロヌチを䜿甚しお発生する可胜性のあるさたざたな問題に察凊するのに圹立぀軜量の代替手段に䌚いたす。 特に、anyStubは次の問題を解決しようずしたす。



anyStubずは䜕ですか


AnyStubは関数呌び出しをラップし、すでに蚘録されおいる䞀臎する呌び出しを芋぀けようずしたす。 これにより、次の2぀のこずが起こりたす。



すぐに䜿甚できるanyStubは、httpリク゚ストのスタブずjavax.sqlからのいく぀かのむンタヌフェヌスを䜜成するためのApache HttpClientからhttpクラむアントのラッパヌを提䟛したす。 他の接続甚のスタブを䜜成するためのAPIも提䟛されたす。


AnyStubは単玔なクラスラむブラリであり、環境の特別な構成は必芁ありたせん。 このラむブラリは、スプリングブヌトアプリケヌションを䜿甚するこずを目的ずしおおり、このパスに埓うこずで最倧限のメリットが埗られたす。 Springの倖郚、プレヌンなJavaアプリケヌションで䜿甚できたすが、間違いなく远加の䜜業が必芁になりたす。 以䞋の説明は、スプリングブヌトアプリケヌションのテストに焊点を圓おおいたす。


統合テストを芋おみたしょう。 これは、システムをテストする最も゚キサむティングで包括的な方法です。 実際、魔法の泚釈を曞くずき、spring-bootずJUnitはほずんどすべおを行いたす。


@RunWith(SpringRunner.class) @SpringBootTest 

珟圚、統合テストは過小評䟡されおおり、限られた範囲で䜿甚されおおり、䞀郚の開発者はそれらを避けおいたす。 これは䞻に、テストの準備ずメンテナンスに時間がかかるため、たたはビルドサヌバヌで環境を特別に構成する必芁があるためです。


anyStubを䜿甚するず、スプリングコンテキストを無効にする必芁はありたせん。 代わりに、コンテキストを本番構成に近づけるこずは簡単で簡単です。


この䟋では、Pivo​​talのマニュアルからanyStubをConsuming a RESTful Web Serviceに接続する方法を説明したす。


pom.xmlを介しおラむブラリを接続する


  <dependency> <groupId>org.anystub</groupId> <artifactId>anystub</artifactId> <version>0.2.27</version> <scope>test</scope> </dependency> 

次のステップは、スプリングコンテキストを倉曎するこずです。


 package hello; import org.anystub.http.StubHttpClient; import org.apache.http.client.HttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.client.RestTemplateCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; @Configuration public class TestConfiguration { @Bean public RestTemplateBuilder builder() { RestTemplateCustomizer restTemplateCustomizer = new RestTemplateCustomizer() { @Override public void customize(RestTemplate restTemplate) { HttpClient real = HttpClientBuilder.create().build(); StubHttpClient stubHttpClient = new StubHttpClient(real); HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); requestFactory.setHttpClient(stubHttpClient); restTemplate.setRequestFactory(requestFactory); } }; return new RestTemplateBuilder(restTemplateCustomizer); } } 

この倉曎により、アプリケヌション内のコンポヌネントの関係は倉曎されたせんが、単䞀のむンタヌフェヌスの実装のみが眮き換えられたす。 これにより、 Barbara Lisk Substitution Principleに送られたす。 アプリケヌションの蚭蚈がそれを䟵害しない堎合、この眮換は機胜を䟵害したせん。


すべお準備完了です。 このプロゞェクトにはすでにテストが含たれおいたす。


 @RunWith(SpringRunner.class) @SpringBootTest public class ApplicationTest { @Autowired private RestTemplate restTemplate; @Test public void contextLoads() { assertThat(restTemplate).isNotNull(); } } 

このテストは空ですが、すでにアプリケヌションコンテキストを実行しおいたす。 ここから楜しみが始たりたす 。 䞊で述べたように、テストのアプリケヌションコンテキストは、倖郚システムぞのhttpリク゚ストが実行されるCommandLineRunnerが䜜成される䜜業コンテキストず䞀臎したす。


 @SpringBootApplication public class Application { private static final Logger log = LoggerFactory.getLogger(Application.class); public static void main(String args[]) { SpringApplication.run(Application.class); } @Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder.build(); } @Bean public CommandLineRunner run(RestTemplate restTemplate) throws Exception { return args -> { Quote quote = restTemplate.getForObject( "https://gturnquist-quoters.cfapps.io/api/random", Quote.class); log.info(quote.toString()); }; } } 

これは、ラむブラリの動䜜を瀺すのに十分です。 初めおテストを開始するず、新しいcomplete/src/test/resources/anystub/stub.yml 。


 request0: exception: [] keys: [GET, HTTP/1.1, 'https://gturnquist-quoters.cfapps.io/api/random'] values: [HTTP/1.1, '200', OK, 'Content-Type: application/json;charset=UTF-8', 'Date: Thu, 25 Apr 2019 23:04:49 GMT', 'X-Vcap-Request-Id: 5ffce9f3-d972-4e95-6b5c-f88f9b0ae29b', 'Content-Length: 177', 'Connection: keep-alive', '{"type":"success","value":{"id":3,"quote":"Spring has come quite a ways in addressing developer enjoyment and ease of use since the last time I built an application using it."}}'] 

どうした spring-bootは、テスト構成からアプリケヌションにRestTemplateBuilderを構築したした。 これにより、httpクラむアントのスタブ実装を介しおアプリケヌションが実行されたした。 StubHttpClientはリク゚ストをむンタヌセプトし、スタブファむルを芋぀けられず、リク゚ストを実行し、結果をファむルに保存し、ファむルから回埩した結果を返したした。


これ以降、むンタヌネットに接続せずにこのテストを実行でき、このリク゚ストは成功したす。 restTemplate.getForObject()は同じ結果を返したす。 将来のテストでこの事実に頌るこずができたす。


説明されおいるすべおの倉曎はGitHubで芋぀けるこずができたす。


実際、ただ単䞀のテストを䜜成しおいたせん。 テストを䜜成する前に、デヌタベヌスでどのように機胜するかを芋おみたしょう。


この䟋では、Pivo​​talマニュアルのSpringずJDBCを䜿甚したリレヌショナルデヌタぞのアクセスに統合テストを远加したす 。


この堎合のテスト構成は次のようになりたす。


 package hello; import org.anystub.jdbc.StubDataSource; import org.h2.jdbcx.JdbcDataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; @Configuration public class TestConfiguration { @Bean public DataSource dataSource() { JdbcDataSource ds = new JdbcDataSource(); ds.setURL("jdbc:h2:./test"); return new StubDataSource(ds); } } 

ここでは、倖郚デヌタベヌスに察しお通垞のデヌタ゜ヌスが䜜成され、スタブ実装-StubDataSourceクラスでラップされたす。 Spring-bootはそれをコンテキストに埋め蟌みたす。 たた、テストでスプリングコンテキストを実行するには、少なくずも1぀のテストを䜜成する必芁がありたす。


 package hello; import org.anystub.AnyStubId; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import static org.junit.Assert.*; @RunWith(SpringRunner.class) @SpringBootTest public class ApplicationTest { @Test @AnyStubId public void test() { } } 

これも空のテストです-その唯䞀のタスクはアプリケヌションコンテキストを実行するこずです。 ここでは、非垞に重芁なアノテヌション@AnystubIdが衚瀺されたすが、ただ含たれおいたせん。


最初の実行埌、すべおのデヌタベヌス呌び出しを含む新しいsrc/test/resources/anystub/stub.ymlがsrc/test/resources/anystub/stub.yml 。 デヌタベヌスを䜿甚するず、春が舞台裏でどのように機胜するかに驚くでしょう。 テストを新たに実行しおも、デヌタベヌスぞの実際のアクセスは行われないこずに泚意しおください。 test.mv.dbを削陀するず、テストを繰り返し実行しおも衚瀺されたせん。 倉曎の完党なセットはGitHubで衚瀺できたす。


たずめるず。 anyStubの堎合



おそらく疑問がありたす。デヌタベヌスがただ存圚しない堎合、どのようにこれをカバヌするのか、吊定的なテストず䟋倖凊理をどうするのか。 これに戻りたすが、最初に、簡単なテストの䜜成に取り組みたす。


珟圚、RESTful Webサヌビスの䜿甚を実隓しおいたす 。 このプロゞェクトには、テスト可胜なコンポヌネントは含たれおいたせん。 アヌキテクチャ蚭蚈の2぀の局を衚す2぀のクラスが䞋に䜜成されたす。


 package hello; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; @Component public class DataProvider { private final RestTemplate restTemplate; public DataProvider(RestTemplate restTemplate) { this.restTemplate = restTemplate; } Quote provideData() { return restTemplate.getForObject( "https://gturnquist-quoters.cfapps.io/api/random", Quote.class); } } 

DataProviderは、デヌタぞのアクセスを提䟛したす 揮発性 倖郚システム。


 package hello; import org.springframework.stereotype.Component; @Component public class DataProcessor { private final DataProvider dataProvider; public DataProcessor(DataProvider dataProvider) { this.dataProvider = dataProvider; } int processData() { return dataProvider.provideData().getValue().getQuote().length(); } } 

DataProcessorは、倖郚システムからのデヌタを凊理したす。


DataProcessorをテストするDataProcessorです。 凊理アルゎリズムの正確性をテストし、将来の倉曎による劣化からシステムを保護する必芁がありたす。


これらの目暙を達成するには、デヌタセットを䜿甚しおDataProviderモックオブゞェクトを䜜成し、テストでDataProcessorコンストラクタヌに枡すこずを怜蚎したす。 別の方法ずしお、DataProcessorを分解しおQuoteクラスの凊理を匷調衚瀺するこずもできたす。 次に、このようなクラスは、単䜓テストを䜿甚しお簡単にテストできたすこれは、クリヌンコヌドに関する著曞で掚奚される方法です。 コヌドの倉曎ずテストデヌタの発明を避け、テストを䜜成しおみたしょう。


 @RunWith(SpringRunner.class) @SpringBootTest public class DataProcessorTest { @Autowired private DataProcessor dataProcessor; @Test @AnyStubId(filename = "stub") public void processDataTest() { assertEquals(131, dataProcessor.processData()); } } 

@AnystubIdアノテヌションに぀いお説明したす。 この泚釈は、テストでスタブファむルを管理および制埡するのに圹立ちたす。 テストクラスたたはそのメ゜ッドで䜿甚できたす。 この泚釈は、察応する領域に個別のスタブファむルを蚭定したす。 いずれかの領域がクラスおよびメ゜ッドレベルのアノテヌションで同時にカバヌされおいる堎合、メ゜ッドアノテヌションが優先されたす。 この泚釈には、スタブファむルの名前を定矩するfilenameパラメヌタヌがありたす。 省略した堎合、拡匵子「.yml」が自動的に远加されたす。 このテストを実行しおも、新しいファむルは芋぀かりたせん。 src/test/resources/anystub/stub.ymlはすでに以前に䜜成されおおり、このテストはそれを再利甚したす。 ク゚リの結果を分析するこずにより、このスタブから131番を取埗したした。


  @Test @AnyStubId public void processDataTest2() { assertEquals(131, dataProcessor.processData()); Base base = getStub(); assertEquals(1, base.times("GET")); assertTrue(base.history().findFirst().get().matchEx_to(null, null, ".*gturnquist-quoters.cfapps.io.*")); } 

このテストでは、@ AnyStubIdアノテヌションがfilenameパラメヌタヌなしで衚瀺されたす。 この堎合、 src/test/resources/anystubprocessDataTest2.ymlたす。 ファむル名は、関数の名前クラス+ ".yml"から䜜成されたす。 anyStubがこのテスト甚の新しいファむルを䜜成したら、実際のシステムコヌルを行う必芁がありたす。 そしお、新しい芋積もりの​​長さが同じであるこずは幞運です。 最埌の2぀のチェックは、アプリケヌションの動䜜をテストする方法を瀺しおいたす。 パラメヌタたたはパラメヌタの䞀郚によっおク゚リを遞択し、ク゚リの数をカりントしたす。 ドキュメントには、時間ず䞀臎関数のバリ゚ヌションがいく぀かありたす 。


  @Test @AnyStubId(requestMode = RequestMode.rmTrack) public void processDataTest3() { assertEquals(79, dataProcessor.processData()); assertEquals(79, dataProcessor.processData()); assertEquals(168, dataProcessor.processData()); assertEquals(79, dataProcessor.processData()); Base base = getStub(); assertEquals(4, base.times("GET")); } 

このテストでは、新しいrequestModeパラメヌタヌずずもに@AnyStubIdが衚瀺されたす。 スタブファむルの暩限を管理できたす。 制埡する2぀の偎面がありたすファむルによる怜玢ず倖郚システムを呌び出す蚱可。


RequestMode.rmTrackは、次のルヌルを蚭定したすファむルが䜜成されたばかりの堎合、すべおの芁求は倖郚システムに送信され、ファむルに同䞀の芁求があるかどうかに関係なく、回答ずずもにファむルに曞き蟌たれたすファむル内の重耇は蚱可されたす。 テストを実行する前にスタブファむルが存圚する堎合、倖郚システムぞのリク゚ストは犁止されおいたす。 呌び出しはたったく同じ順序で行われたす。 次の芁求がファむル内の芁求ず䞀臎しない堎合、䟋倖がスロヌされたす。


RequestMode.rmNewこのモヌドはデフォルトでアクティブになっおいたす。 各芁求はスタブファむルで怜玢されたす。 䞀臎する芁求が芋぀かった堎合-察応する結果がファむルから埩元され、倖郚システムぞの芁求は延期されたす。 芁求が芋぀からない堎合、倖郚システムが芁求され、結果がファむルに保存されたす。 ファむル内の重耇したリク゚スト-発生したせん。


RequestMode.rmNone各リク゚ストはスタブファむルで怜玢されたす。 䞀臎するク゚リが芋぀かった堎合、その結果はファむルから埩元されたす。 テストがファむルにない芁求を生成するず、䟋倖がスロヌされたす。


RequestMode.rmAllは、最初の芁求の前に、スタブファむルが消去されたす。 すべおの芁求はファむルに曞き蟌たれたすファむル内の重耇は蚱可されたす。 接続の動䜜を監芖する堎合は、このモヌドを䜿甚できたす。


RequestMode.rmPassThroughすべおの芁求は、実装スタブをバむパスしお、倖郚システムに盎接送信されたす。


これらの倉曎はGitHubで入手できたす。


他に䜕


anyStubが応答を保存する方法を芋たした。 倖郚システムぞのアクセス時に䟋倖がスロヌされた堎合、anyStubはそれを保存し、埌続のリク゚ストでそれを再珟したす。


倚くの堎合、䟋倖はトップレベルのクラスによっおスロヌされたすが、接続クラスは有効な応答を取埗したすおそらく゚ラヌコヌドで。 この堎合、anyStubぱラヌコヌドを䜿甚しお答えを再珟し、䞊䜍クラスもテストの䟋倖をスロヌしたす。


リポゞトリにスタブファむルを远加したす。


スタブファむルを削陀しお䞊曞きするこずを恐れないでください。


スタブファむルを賢く管理したす。 1぀のファむルを耇数のテストで再利甚するか、各テストに個別のファむルを提䟛できたす。 必芁に応じおこの機䌚を利甚しおください。 ただし、通垞、異なるアクセスモヌドで単䞀のファむルを䜿甚するこずはお勧めできたせん。


これらはすべおanyStubの䞻な機胜です。



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


All Articles