Junitで単体テストをする機会に恵まれたので、備忘録も兼ねて記載。
JUnit暦2週間なのでGreaterな方法もあるはずです。
JUnitでユニットテストをする際にはモックを使う場合がある。
モックを作るツールは多々あるが、今回はMockitoとPowerMockitoを利用する。
クラス名は適当にCarクラスとしておく。
今回紹介するためのクラスなので、面倒くさそうなものを。
public class Car {
private static final int FUEL_MAX = 1000;
private static Runnable sRunnable;
private int mFuel;
public Car(int fuel){
mFuel = fuel;
}
//Constクラスなどによくあるprivateコンストラクタ
private Car(){}
//publicなメソッド
public int getFuel(){
return mFuel;
}
//publicでstaticなメソッド
public static void setRunnable(Runnable runnable){
sRunnable = runnable;
}
//privateなメソッド+匿名クラス
private void setAnnonymous(){
setRunnable(new Runnable() {
@Override
public void run() {
//なにかしらの処理
}
});
}
//privateな内部クラス
private class SubCar {
//privateな内部クラスのコンストラクタ
private SubCar(){
}
//内部クラスのprivateメソッド
private String getCarName(){
return "さぶかー";
}
}
まずはコンストラクタから。
//コンストラクタ
public Car(int fuel){
mFuel = fuel;
}
モックは必要ないかと。
Car car = new Car(20);
assertTrue(car.getFuel() == 20)
//publicなメソッド
public int getFuel(){
return mFuel;
}
これもモック不要
Car car = new Car(20);
int fuel = getFuel();
assertTrue(car.getFuel() == 20)
//Constクラスなどによくあるprivateコンストラクタ
private Car(){}
privateなのでPowerMockitoを利用する。
使うのはWhiteboxだけど。
Constructor constructor = Whitebox.getConstructor(Car.class);
Car privateCar = (Car) constructor.newInstance();
assertNotNull(privateCar)
privateコンストラクタは大抵中身が無いのでインスタンス作ってnullチェックレベル。
//publicでstaticなメソッド
public static void setRunnable(Runnable runnable){
sRunnable = runnable;
}
Carクラスでのテストなら普通にsetRunnableを呼べは良いだけ。
これを他クラスで使う場合にこのメソッドをモック化したい場合はPowerMockitoを利用する
//spyで一部をモック化
PowerMockito.spy(Car.class);
//なんとなくモック化。普通にnew Runnableでも良い
Runnable mockRun = Mockito.mock(Runnable.class);
//Car.setRunnable(mockRun)を実行した場合は何もしない(doNothing)
PowerMockito.doNothing().when(Car.class,"setRunnable",mockRun);
//Exceptionを投げたければ
PowerMockito.doThrow(new RuntimeException()).when(Car.class,"setRunnable",mockRun);
//privateなメソッド+匿名クラス
private void setAnnonymous(){
setRunnable(new Runnable() {
@Override
public void run() {
//なにかしらの処理
}
});
}
Mockitoでは原則匿名クラスは取得できない。
PowerMockitoにそれっぽいAPIはあるが、どうにも動いている気がしない。
実施方法は二つ。
1.匿名クラス一つの内部クラスに分離する。
2.匿名クラスをセットしたフィールドを強引に取得する。
可能なら1。ただし、テストのために実装を変える本末転倒甚だしい事態。
1は内部クラスの説明でするので、とりあえず2。
ここでは匿名クラスの格納先がsRunnableなので、これを取得する。
ただしsetAnnonymousをするまではnullなのでメソッドを実行してから。
Car car = new Car(100);
//リフレクションでsetAnnonymousメソッド取得
Method method = Whitebox.getMethod(Car.class,"setAnnonymous");
//メソッド実行。
method.invoke(car);
//フィールド取得
Field field = Whitebox.getField(Car.class,"sRunnable");
//getの引数nullはstaticフィールドのため。
Runnable runnable = (Runnable) field.get(null);
//テスト対象メソッド実行
runnable.run();
最後に内部クラスのprivateメソッド。
//クラス取得
Class subCarClass = Whitebox.getInnerClassType(Car.class,"SubCar");
//メソッド取得
Method method = Whitebox.getMethod(subCarClass,"getCarName");
//コンストラクタからインスタンスを生成
Constructor constructor = Whitebox.getConstructor(subCarClass);
Object subCarInstance = constructor.newInstance();
//getCarNameメソッドの実行
method.invoke(subCarInstance);
結構力技。でもこれくらいしか思いつかなかった。
基本(力)技があればいくらでも応用は利くので、この辺りまで。
最後に注意点まとめ。
メソッドをモック化するためにdo-when-thenReturnなどを利用する。
例
PowerMockito.doNothing().when(Car.class,"setRunnable",mockRun);
これをしたとき、CarクラスのsetRunnableが実行されてしまう。
このときsetRunnableでExceptionなんて起こそうものなら途端にテストが止まる。
モック化するメソッドの中身を見て、もしそれがExceptionを起こしそうなものがあればそれをモック化する必要がある。
それをモック化するときにExceptionが出たら...悩みは尽きない。
Whiteboxはリフレクションを簡易にする機能。
Whiteboxで検索するとprivateでもstaticでもfinalでも全てOKと書いてあるが、実際にはfinalはできるものとできないものがある。
遅延初期化しているものはできる可能性あり。定数のようにハードコーディングしているものは無理っぽい。
PowerMockitoも時折finalなものはモック化できなかったりするので、柔軟に対応していこう。
以上。