Mockito でモックオブジェクトを初期化する
環境
- Junit 4
- Mockito 2.8.9
内容
Mockito の Mock アノテーションを使ってモックを使ったテストを書きたい場合、 モックオブジェクトを初期化する処理を入れる必要がある。方法は調べた限り3つある様子。
MockitoAnnotations.initMocks(this) method has to be called to initialize annotated objects. In above example, initMocks() is called in @Before (JUnit4) method of test's base class. For JUnit3 initMocks() can go to setup() method of a base class. Instead you can also put initMocks() in your JUnit runner (@RunWith) or use the built-in MockitoJUnitRunner.
方法1 Before アノテーションを使って initMocks() を呼ぶ。
下記は javadoc から。(テストケースで継承するという実装の是非はここでは問わない。)
このコードでは Before アノテーションで各テストケース毎に初期化する MockitoAnnotations.initMocks(this)
を呼び出す基底クラスを作り、継承先でモックオブジェクトを定義している。
public class ArticleManagerTest extends SampleBaseTestCase { @Mock private ArticleCalculator calculator; @Mock(name = "database") private ArticleDatabase dbMock; @Mock(answer = RETURNS_MOCKS) private UserProvider userProvider; @Mock(extraInterfaces = {Queue.class, Observer.class}) private articleMonitor; private ArticleManager manager; @Before public void setup() { manager = new ArticleManager(userProvider, database, calculator, articleMonitor); } } public class SampleBaseTestCase { @Before public void initMocks() { MockitoAnnotations.initMocks(this); } }
方法2 RunWith アノテーションに MockeitoJUnitRunner を指定する。
下記コードは javadoc から。
MockitoJUnitRunner.class で指定するとデフォルトでは MockitoJUnitRunner.Strict が使われるものと同等となる。javadoc を見る限りだと 下記コードのように StrictStubs を使うことが推奨されている。
ちなみに MockitoJUnitRunner を使う場合は Test アノテーションがないと動かない。( JUnit 側の Runner のバリデーションの問題)
@RunWith(MockitoJUnitRunner.StrictStubs.class) public class ExampleTest { @Mock private List list; @Test public void shouldDoSomething() { list.add(100); } }
方法3 JUnit の MethodRule を実装した MockitoRule を使う
SpringRunner や Theories などの Runner を使いたいため MokcitJUnitRunner が指定できない場合などは Mockito のほうを Rule で動かしてやると同じような挙動をする。使用する際には MocketoJUnit から取得するらしい。
public class ExampleTest { //Creating new rule with recommended Strictness setting @Rule public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); @Mock private List list; @Test public void shouldDoSomething() { list.add(100); } }
ドキュメント
spring で logback を使う際に application.yml の値を使う
The <springProperty> tag allows you to surface properties from the Spring Environment for use within Logback. This can be useful if you want to access values from your application.properties file in your logback configuration.
どうやら springProperty を使うと良いらしい。多分こういうこと。
application.yml
my: log: prop: DEBUG
... <springProperty name="myLogProp" source="my.log.prop" /> <logger name="com.example.log"> <appender-ref ref="${myLogProp}" /> </logger> ...
Gradle で SNAPSHOT バージョンのライブラリを使う場合は --refresh-dependencies をつける
開発バージョンとして -SNAPSHOT を付けたものを gradle プロジェクトで利用している場合、なぜかいつまでたっても新しくアップロードした SNAPSHOT のバージョンを使ってくれない問題があった。
SNAPSHOT バージョンは下記のように maven で開発中のバージョンとして用いられるものである。
https://maven.apache.org/guides/getting-started/index.html#What_is_a_SNAPSHOT_version
https://docs.gradle.org/current/userguide/dependency_management.html#sub:cache_refresh
Alternatively, sometimes the module you request can change over time, even for the same version. An example of this type of changing module is a Maven SNAPSHOT module, which always points at the latest artifact published. In other words, a standard Maven snapshot is a module that never stands still so to speak, it is a “changing module”.
SNAPSHOT がつくバージョンは changing module と呼ばれるものらしい。
“gradle SNAPSHOT” などでググると下記のような記事が出て来る。
どうやら下記のように changing modules のキャッシュ時間(デフォルト 24 時間)を調整せよとのことらしい。
https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.ResolutionStrategy.html
// don't cache changing modules at all cacheChangingModulesFor 0, 'seconds'
自分の環境( というか Intellij IDEA 上の gradle ?)がおかしいのか効果がなかったので おとなしく –refresh-dependencies つけることにしたことで解決した。実行に時間はかかるようになるが SNAPSHOT バージョンを使っていても新規にリポジトリに反映したバージョンを使ってくれるようになった。
Jackson で OffsetDateTime を ISO8601 形式にシリアライズする
JavaTimeModule を使って シリアライズする場合に OffsetDateTime などが変な数字になる場合がある。
@Data class MyObject { @JsonProperty("datetime") private OffsetDateTime dateTime; } ObjectMapper mapper = new ObjectMapper(); objectMapper.registerModule(new JavaTimeModule()); MyObject paramObject = new MyObject(); paramObject.setDateTime(OffsetDateTime.now()); Map<String, String> map = objectMapper.convertValue(paramObject, new TypeReference<Map<String, String>>() {}); map.get("datetime"); // ここがタイムスタンプになる
下記のようにシリアライズされる際に WRITE_DATES_AS_TIMESTAMPS かどうかを確認している箇所がある。
ObjectMapper がデフォルトで WRITE_DATES_AS_TIMESTAMPS が有効になっているのでこれを解除する。
@Data class MyObject { @JsonProperty("datetime") private OffsetDateTime dateTime; } ObjectMapper mapper = new ObjectMapper(); objectMapper.registerModule(new JavaTimeModule()); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); MyObject paramObject = new MyObject(); paramObject.setDateTime(OffsetDateTime.now()); Map<String, String> map = objectMapper.convertValue(paramObject, new TypeReference<Map<String, String>>() {}); map.get("datetime"); // ISO8601 形式の文字列となる
apache HttpClient の backoff について
この記事の目的
コードリーディング後の理解用メモ
対象
概要
apache http client には 動的にコネクション数を調整できる機能がある。用途としてはリクエスト先のサーバが高負荷になった場合などにコネクション数を減らして負荷を一時的に減らせるようにするような目的で使えそう。
正確な記述などはすっとばして理解の範囲で記載
関連クラスやメンバ
- org.apache.http.impl.client.HttpClientBuilder
- backoffManager
- connectionBackoffStrategy
- org.apache.http.impl.execchain.BackoffStrategyExec
- org.apache.http.client.ConnectionBackoffStrategy
- org.apache.http.client.BackoffManager
- org.apache.http.impl.client.AIMDBackoffManager
- org.apache.http.impl.client.DefaultBackoffStrategy
簡単な使い方
もっとも簡単なコード
PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(); CloseableHttpClient client = HttpClientBuilder.create() .setBackoffManager(new AIMDBackoffManager(manager)) .setConnectionBackoffStrategy(new DefaultBackoffStrategy()) .build()
コネクション数制御。 HttpClientBuilder で設定したコネクション数設定は初期値として使われる。もちろん BackoffManager で調整されていくので運用中の数字とはことなる場合がある。
PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(); AIMDBackoffManager backoffManager = new AIMDBackoffManager(manager); backoffManager.setPerHostConnectionCap(10); CloseableHttpClient client = HttpClientBuilder.create() .setBackoffManager(backoffManager) .setConnectionBackoffStrategy(new DefaultBackoffStrategy()) .build();
メモ
- HttpClientBuilder で BackoffStrategyExec を作る
- BackoffStrategyExec が ConnectionBackoffStrategy や BackoffManager を利用する
- コネクション数の増え方や減らし方は BackoffManager で実装する
- Backoff する条件は ConnectionBackoffStrategy で実装する
- Exec は Strategy の shouldBackoff() を見てどうするかきめる
- true -> Manager の backoff() を実行
- false -> Manager の probe() を実行
HttpClient 内の実装について
AIMDBackoffManager について
- AIMD = Additional increase, multiplicative decrease (加算増加乗算減少)
- デフォルトでは最大2コネクション
- 初期設定でプールの方の設定を大きくしていても次の更新でこの数字になる
- 正確にはホストごとの最大コネクション数
- 増加や減少を抑える時間(クールダウン)はデフォルト 5 秒
- 最後の増加と減少から 5 秒ごとにプールの最大接続数が 1 ふえる
- 最後の減少から 5 秒ごとにプールの最大接続数が半分になる
- BackoffStrategyExec が shouldBackoff() の true/false で呼び出し変えてるだけなので最大で消費してなくても最大値は増えてく
DefaultBackoffStrategy について
- SocketTimeoutException や ConnectException のような例外が発生した場合に backoff すべきと返す
- http status が 503 の場合に backoff すべきと返す
- 注意としては ServiceUnavailableRetryExec などを利用したときに backoff せずにリトライが優先される場合がある
- HttpClientBuilder#build() の実装でそういう順番でチェインしている 参考
- 注意としては ServiceUnavailableRetryExec などを利用したときに backoff せずにリトライが優先される場合がある
参考文献
apache HttpClient の eviction について
この記事の目的
apache HttpClinent の eviction について コードリーティングした内容のメモ。 コネクションの有効期限(expire?)についても書いているがプールから取り除く処理(eviction?) について調べるときに必要だったので書いている。混同してたらごめんなさい。動作確認してたりしてなかったりするので鵜呑みにしないように。
対象
以下 http components を hc と略していたりする。文言が統一されてないのは殴り書きなので。
内容
概念
抽象クラスや正確な記述などはすっとばして理解の範囲で記載
関連クラスやメンバ
- org.apache.http.impl.client.HttpClientBuilder
- connManagerShared
- evictExpiredConnections
- evictIdleConnections
- maxIdleTime, maxIdleTimeUnit
- connTimeToLive, connTimeToLiveTimeUnit
- org.apache.http.impl.client.IdleConnectionEvictor
- org.apache.http.impl.conn.CPool
- org.apache.http.impl.conn.CPoolEntry
- org.apache.http.pool.PoolEntry
- validityDeadline
- expiry
- org.apache.http.impl.conn.PoolingHttpClientConnectionManager
- timeToLive
メモ内容
概念としては以下の二つ
- プールされたコネクションの有効期間 -> PoolingHttpConnectionManager (あるいは HttpClientBuilder)に設定
- 作成されてからの有効期間 -> 設定可能
- 最後に使用されてからの有効期間 -> 基本的に HTTP/1.1 の keepalive
- プールされたコネクションが生存期間内かどうかチェックする間隔 -> HttpClientBuilder に設定
IdleConnectionEvictor のコンストラクタでスレッドを立てて、一定時間ごとに失効したコネクションあるいはアイドル時間が一定時間を超過したコネクションをクローズする。
- 設定する利点は?
- 設定しない場合有効期限が切れたコネクションが Pool に残ったままになるので、定期的に掃除できれば取得時に余計な探索をするコストを減らせる。
失効の判定設定は PoolingHttpConnectionManager の timeToLive を設定する。 設定場所は HttpClientBuilder の使い方によって以下の二つのどちらになる。
- HttpClientBuilder で各種設定をする
- HttpClientBuilder#setConnectionManager() に PoolingHttpClientConnectionManager を設定する。
- こちらを使う場合はこちらが優先される
自分で timeToLive を指定せずに生成した場合は -1 となる。これは CPool で無限(正確には Long.MAX_VALUE)として扱われる。
PoolingHttpClientConnectionManager のコンストラクタに timeToLive を指定する。この中で Pool インタフェースの実装である CPool が生成され、CPoolEntry が生成されるたびに生成時刻から expire を計算する。 そのため HttpClientBuilder に自身で生成したものを設定した時、このあたりの挙動が変化する可能性があるので注意。
失効の判定設定は HttpClientBuilder の evictExpiredConnections() または evictIdleConnections() でおこなう。 チェック間隔は evictExpiredConnections() の場合は 10 秒、間隔を自分で設定したい場合は evictIdleConnections() を使う。
スレッドは基本は HttpClient 毎に生成される。正確には HttpClientBuilder#build() されるたびに生成される。 ただし ConnectionManager を共有する場合は行われない。
- ConnectionManager を共有するユースケースは?
- わからん。 Pooling を共有する場合はアウトゴーイングなコネクションで消費するローカルポートを hc 全体で管理したい場合に使えば良いのかも。この場合コネクションのシャットダウンもちゃんと管理しないとダメそう。 管理しないとっていうのは ConnectionManager が shared になってる場合は CloseableHttpClient を close してもプールされたコネクションはクローズされない。(そりゃ他の CloseableHttpClient で使ってる可能性があるのに勝手にクローズされちゃこまるよねと)なので自分で ConnectionManager の close を使う必要がありそう。
HttpClient では Closeable のリストを管理しており、各 HttpClient で close() 時動作を追加することが出来る。この設計を使って IdleConnetionEvictor で生成されたスレッドを終了させる。
コード(動作確認など)
基本的な使い方
// 特に制限なしなので繋いだら Connection ヘッダに従う。 Keep-Alive の場合は繋ぎっぱなし。 CloseableHttpClient client = HttpClientBuilder.create().build(); // 10 分後に使用不能になる。 // keepalive があれば min(10分, 生存期間 + keepalive 時間) に使用不能になる。 // 取得時に Pool からの取得時に使えないものとして取得し直す CloseableHttpClient client = HttpClientBuilder.create() .setConnectionTimeToLive(10, TimeUnit.MINUTES) .build(); // 条件は前項と一緒。 // 10 秒ごとにチェックしてプールから取り除く。取得時のコネクション取得コストを軽減できる可能性がある。 CloseableHttpClient client = HttpClientBuilder.create() .setConnectionTimeToLive(10, TimeUnit.MINUTES) .evictExpiredConnections() .build(); // 10 分ごとにチェックする // 基本的にリクエスト先で keep alive で時間が設定されている場合に意味がある CloseableHttpClient client = HttpClientBuilder.create() .evictIdleConnections(10, TimeUnit.MINUTES) .build(); // 10 分ごとにチェックする(evictExpiredConnections() は意味ない) CloseableHttpClient client = HttpClientBuilder.create() .evictExpiredConnections() .evictIdleConnections(10, TimeUnit.MINUTES) .build();
以下動作確認
手っ取り早く Spring Boot で Bean 登録して確認する。
確認:本当にスレッドは生成されるのか?
確認方法: evictExpiredConnections() を呼ぶ
@Bean public HttpClient httpClient() { return HttpClientBuilder.create().evictExpiredConnections().build(); }
jconsole で起動したアプリを選択してスレッドを見て「Connection Evictor」でフィルタする。
確認:Connection Evictor スレッドがリークする可能性がある?
動作確認: HttpClient を close() せずに何度も build() してみる。
@Bean public HttpClient httpClient() { HttpClientBuilder.create().evictExpiredConnections().build(); HttpClientBuilder.create().evictExpiredConnections().build(); HttpClientBuilder.create().evictExpiredConnections().build(); return null; }
スレッド起動したままだった。
ちゃんと close() するとスレッドはなくなっている。
@Bean public HttpClient httpClient() throws IOException { HttpClientBuilder.create().evictExpiredConnections().build().close(); HttpClientBuilder.create().evictExpiredConnections().build().close(); HttpClientBuilder.create().evictExpiredConnections().build().close(); return null; }
ClientExecChain あたりの挙動メモ
実際に処理する順番
- MainExcec
- decorateMainExec()
- ProtocolExec
- decorateProtocolExec()
- RetryExec
- RedirectExec
- ServiceUnavailableRetryExec
- BackoffStrategyExec
MainExec
たぶん実際にリクエストを行なう部分
decorateMainExec()
HttpClientBuilder を継承したクラスで実装する部分っぽい。
実装例としては httpcomponent-cache の CachingHttpClientBuilder がある。
ProtocolExec
正直よくわからん
decorateProtocolExec()
HttpClientBuilder を継承したクラスで実装する部分っぽい。 が、ProtocolExec がわからんので正直よくわからん。
RetryExec
何らかの IOException レベルの http protocol が返ってこないような例外を検知した後 再度リクエストをし直す。
リトライ条件は HttpRequestRetryHandler を介して設定する。
DefaultHttpRequestRetryHandler では下記の例外の場合はリトライしない。
- InterruptedIOException.class
- UnknownHostException.class
- ConnectException.class
- SSLException.class
RedirectExec
リクエスト内でリダイレクトが発生する場合にリダイレクト先に変更してまたリクエストし直す部分っぽい。
ServiceUnavailableRetryExec
HTTP ステータスが返ってきたあとにリトライする場合の処理を記述するらしい。
BackoffStrategyExec
コネクション数を動的に制御する場合にコネクション数を減らさなければならない条件を表す部分っぽい。
コネクション数を動的に制御する部分は BackoffManager が行っている。 HttpClient がデフォルトで実装してあるのは AIDM (Additional increase, multiplicative decrease) というアルゴリズムであり、コネクションは加算増加乗算減少を行なうらしい。
decrease 条件は ConnectionBackoffStrategy で記述して 設定する。 デフォルトの DefaultBackoffStrategy では 503 Servie Unavailable になった場合にコネクション数が減少する挙動をする。