JavaでHTTPアクセスを記録/再生してテスト時に使える、Betamaxを試してみた
id:ikikko さんのこちらの記事を参考にJavaでHTTPアクセスを記録/再生してテスト時に使える、Betamaxを試してみました。
このBetamaxというのはその名の通り、テープに記録/再生するものなわけですが、その対象が映像ではなくHTTPアクセスだと言うことです。
Rubyでvcrという同様のツールがあるのですが、こちらのクローンとなります。Betamxを使ったテストのメリットとして
- ネットに繋げる必要がない
- 状態を固定できる(検索結果などアクセス毎に変わるものに有効)
- レアケースのテストに対応できる
- モックを用意する必要がない
というのがあげられます。モックを用意しなくて良いので、テスト全体もシンプルになりますし、WebAPIを始め外部のリソースを叩くときにはかなり汎用的に活用できそうです。
基本的な仕組みはJettyベースのProxyを立ち上げて、Javaの標準Proxy設定を変更する作りのようです。
HttpUnitなど独自のProxyを利用するコードには明示的にProxyを指定してやる必要があります。
Groovyで記述されたライブラリですが、ピュアJavaからでも問題なく利用出来ました。おそらくScalaとかからの利用も可能でしょう。
では、使い方について下記に書いていきます。
pom.xmlの変更
<dependency> <groupId>co.freeside</groupId> <artifactId>betamax</artifactId> <version>1.1.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-all</artifactId> <version>2.0.4</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.2.1</version> <scope>test</scope> </dependency>
ここで重要なのはgroovy-allとhttpclientを追加するのを忘れないことです。
このあたりが無いと下記のようなエラーがでるので要注意です。
httpclirntが無いとき:
groovy.lang.GroovyRuntimeException: Could not find matching constructor for: org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager() at groovy.lang.MetaClassImpl.invokeConstructor(MetaClassImpl.java:1477) at groovy.lang.MetaClassImpl.invokeConstructor(MetaClassImpl.java:1393) at org.codehaus.groovy.runtime.callsite.MetaClassConstructorSite.callConstructor(MetaClassConstructorSite.java:46) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:57) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:182) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:186) at co.freeside.betamax.proxy.jetty.ProxyServer.newHttpClient(ProxyServer.groovy:70) at co.freeside.betamax.proxy.jetty.ProxyServer.this$4$newHttpClient(ProxyServer.groovy) at co.freeside.betamax.proxy.jetty.ProxyServer$this$4$newHttpClient.callCurrent(Unknown Source) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:49) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:141) at co.freeside.betamax.proxy.jetty.ProxyServer.start(ProxyServer.groovy:52) at co.freeside.betamax.HttpInterceptor$start.call(Unknown Source) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:112)
groovy-allが無いとき:
java.lang.NoClassDefFoundError: groovy/lang/GroovyObject at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:634) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) at java.net.URLClassLoader.defineClass(URLClassLoader.java:277) at java.net.URLClassLoader.access$000(URLClassLoader.java:73)
Javaだけ使ってると特にgroovy-allなんて入ってないと思うので、そこでハマるかもです。
テスト以外には不要なのでscopeはtestにしておきます。
テストの記述
Betamaxを使うときのポイントは3点です。
- @RuleにRecorderを設定する
- 各テストケースに@Betamaxを指定する
- 必要に応じてresources/betamax.propertiesを編集する
まずは下記の通り@RuleにRecorderのフィールドを宣言します。この値はpublicでなければいけません。
@Rule public Recorder recorder = new Recorder();
次に各テストに対して@Betamaxアノテーションを追記します。
tapeで記載された値がHTTPリクエストの記録名となります。
@Betamax(tape = "my tape") @Test public void testSimpleHttp1() throws Exception { // URL接続 HttpURLConnection con = (HttpURLConnection) new URL("http://www.google.com/").openConnection(); con.setRequestMethod("GET"); BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream())); // HTMLソースの表示 String str; while *1 != null) { System.out.println(str); } // URL切断 br.close(); con.disconnect(); }
この状態で実行すると、まず初回アクセスは通常通りネットにアクセスを行います。
その時のネットワークの記録が
src/test/resources/betamax/tapes/記録名.yml
として出力されます。これ以降は実行の度にこのファイルを参照して、ネットワーク通信無しのダミーアクセスとなります。
なお、ファイルの中身は以下のようなフォーマットです。
!tape name: my tape interactions: - recorded: 2012-11-25T23:29:55.459Z request: method: GET uri: http://www.google.com/ headers: Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Host: www.google.com Proxy-Connection: keep-alive User-Agent: Java/1.6.0_24 response: status: 200 headers: Cache-Control: private, max-age=0 Content-Type: text/html; charset=Shift_JIS Date: Sun, 25 Nov 2012 23:29:55 GMT Expires: '-1' P3P: CP="This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info." Server: gws Set-Cookie: PREF=ID=fa5743e33aa1e0a8:FF=0:TM=1353886195:LM=1353886195:S=my_Hw0OYJp3nuD-z; expires=Tue, 25-Nov-2014 23:29:55 GMT; path=/; domain=.google.co.jp, NID=66=IijFeNtgPs0FQFBqIU7fwPxJB7ZFoUeRf5hdMExgaARPsU9M_VAzI1EwJyjr44MYEJzVZbZQ1G1Vyx6d_WjqlYJ6TImA1uxuTLz8CgKztwizzjjChom9d-_9Qr-LfMag; expires=Mon, 27-May-2013 23:29:55 GMT; path=/; domain=.google.co.jp; HttpOnly X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block body: |- <html>Hello World.</html>
こちら、ファイルフォーマットを直接変更することで任意の内容に変えることができるので、再現が難しい内容に関しては、直接テープを用意してしまうのが良いと思います。
またデフォルトポートが5555なのですが、こちらが既に利用されていてる場合は当然ですが、下記のような例外が出ます。
java.net.BindException: Address already in use
その場合はsrc/test/resources/betamax.propertiesを作成して設定内容を変更するのが有効です。私の環境の場合5555はぶつかってしまったので、以下の設定ファイルを作成して6555に変更してあります。
betamax.tapeRoot=src/test/resources/betamax/tapes betamax.useProxy=true betamax.proxyPort=6555 betamax.proxyTimeout=5000 betamax.defaultMode=READ_WRITE betamax.ignoreHosts=localhost,127.0.0.1 betamax.ignoreLocalhost=false betamax.sslSupport=true
こちらを読み込むことで無事、テストが動作します。
また、HTTPSアクセスを有効にするためには
betamax.sslSupport=true
を設定する必要があります。デフォルト設定ではfalseなので、下記のようなエラーが発生します。
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
HTTPSを利用する場合は忘れずに変更するようにしましょう。
まとめ
さて、これで基本的なBetamaxの使い方は説明完了です。
まだ、自分も使い始めたばかりですが、開発中のAndroidアプリがちょうど外部アクセスをするのでテストに組み込んで見ました。ファイルはこちら。まだ、1ケースしか書いてないのであまり意味無いですがw
仕事でAPI叩いてることも良くあるので、その辺にも随時適用していきたいと思います。いまだと、ネットワーク不通でテスト通らないとか結構あるので...
参考:
- Betamax - Record & replay HTTP traffic
- HTTPアクセスを記録/再生してテスト時に使える、Betamaxを試してみたよ - @ikikko のはてなブログ
- 入門Geb+Betamax
- Betamax as presentet on GR8Conf
*1:str = br.readLine(