社内se × プログラマ × ビッグデータ

プログラミングなどITに興味があります。

Mockito のチュートリアル記事を読んだ感想

これです。
www.discoversdk.com

実際のところ、Mockito って Mavenリポジトリを見ても、12/31/2014以降は新しいバージョンが出ていないくらい完成された?フレームワークなのかもしれない。
ただ、この記事は 5/25/2017 に更新されたのもので、個人的にそんなに詳しくないので、読んでみました。

Mockito とは?

Javaオープンソースフレームワークで、モックを簡単に作れるよ。
モックというのは、ユニットテストで使えるダミーの機能(実装はされていない)みたいなもの。
※この記事には、最新バージョンが Mockito release is version 1.9.5 と紹介されているけど、1.10.19 までは出ているはず

Mavenを使用してのクラスパスの追加

記事内では mockito と junit と assertj を追加していました。
assertj は使ったことがないです。

Mockito と JUnit

簡単に試せれば何でも良いので、以下の2つのクラスを定義しました。
1) 1 - 100 内の数字をランダムに生成するクラス。NumberGenerator.class
2) 2 つの数字の平均を求めるクラス。AverageCalculator.class

正直、プログラミングの練習のために、適当なクラスを考えるのは苦手です。

Mockito を使うためには

1. テストクラスに @RunWith のアノテーションをつける。

2. テストフィールドに @Mock もしくは @Spy のアノテーションをつける。
要するに、モックをつくりたいオブジェクトはここで定義する。

3. モックを注入するクラスに、@InjectMocks のアノテーションをつける。
要するに、テスト対象のクラスということになると思います。

アノテーションをつけることはわかるのですが、それによって内部的にどういう動きをするようになるのかが知りたいところです。
プログラミングしていてよく感じることですが、なぜこの一行を加えるだけで、全く違う動きになるのか?というところは、ソースコードが公開されているライブラリであれば、調べようと思えば調べられることなんですけどね。
知らなくても何とかなる部分でも、興味があればトコトン調べれば良いだろうし、そこまで無いのであれば、もっと興味のある方に時間をかけた方が良い部分だとは思います。

テストコード

以下のようになりました。

@RunWith(MockitoJUnitRunner.class)
public class AverageCalculatorTest {
	
@Mock
NumberGenerator numberGeneratorMock;
	
@InjectMocks
AverageCalculator averageCalculatorMock;

@Test
public void test() {
    // given
    given(numberGeneratorMock.generateNumber()).willReturn(50);
		
    // when
    Integer average = averageCalculatorMock.getAverage();
		
    // then
    then(average).isEqualTo(50);
}
}

普通に JUnit を実行させると、テスト結果が返ってきます。
おおまかな流れは以下のようになっていると思います。

1. NumberGenerator のモックが作られる
2. AverageCalculator のモックに NumberGenerator のモックが注入される
3. generateNumber() の戻り値が 50 で固定される
4. よって getAverage() の結果は 50 となる

用語

Dummy: ビジネスロジックに全く関わりのないコードのためのオブジェクト。
例えば、パラメーターを関数に渡すだけとか。
> ダミーと呼ぶのは、聞いたことがないですね。

Fake: 本番環境では使用しないオブジェクト。
例えば、メモリ上で動作する H2 データベースとか。
> これも意識して、フェイクと呼んだりしていない。

Stub: テスト中において、実行結果を予め定義してあるオブジェクト。
> スタブはよく耳にすると思いますが、モックとの違いがよく議論されていますね。

Mock: テスト中において、実行結果を予め定義してあるオブジェクトで、各実行の期待値が記録(保持)されている。
> スタブとの違いは、期待値を持っているかどうか?だけ。

Spy: スタブと似ているが、更にどのように実行されたか?が記録されている。
> スパイとか呼んだことありません。

普段は、スタブとモックを使い分けることなんかしていなくて、「テスト時に使うダミー的なコードはすべてモック」(上に書いたやつ全部まとめて)と呼んでいる気がします。

Mockito と TestNG

TestNG は、単体テスト、機能テスト、総合テストなどすべてのカテゴリを網羅するテストフレームワーク(らしい)。
最初、TestNG って、「テスト失敗」のことだと思ってました。
NG って、NextGeneration から取っているみたいですが、英語圏の人からするとややこしくないのだろうか。
JUnitに似ているけど、拡張版ではないらしい。
ただ、JUnit4 の方が新しいフレームワークなので、今や NextGeneration とは呼びにくいでしょうけど。

まとめ

Mockito 初心者によって、分かりやすい内容だったと思います。
本当に単純なコードを一つ書いただけですが、最初のチュートリアルはこのくらいが良いのかもしれません。

用語については、あまり考えて使い分けていないことを改めて感じさせられました。
この辺は開発現場にかなり依存してしまっている気がします。

TestNG は今度試してみたいですが、コスト的に JUnit4 から移行するかどうかは分からないです。
歴史のあるフレームワークなのに、まだまだ知らないこと多いです。(耳にした覚えがなかったです)
特に海外では評価が高いフレームワークなので、海外の記事を呼んでいるとこういった情報が入ってくるかもしれませんね。
あと、assertj は面白そうです。

VirtualBox ゲストOS のネットワークが頻繁に Up/Down するが原因は分かっていない話

本件、今のところ結局解決していないので、解決策には言及していません。
(追記)解消しました。
VirtualBox ゲストOS のネットワークが頻繁に Up/Down していたが NATネットワークに変えたら解消 - 社内se × プログラマ × ビッグデータ


VirtualBox のゲストOSとして、lubuntu(ubuntu)を乗せているのだが、頻繁にネットワークが切断されてしまう。
ログを見てみると、1~2分に1回の頻度で Link の Up/Downが繰り返されている。
Down -> Up までは 5秒程度だが、ダウンしている間はネットワークに接続できず、Webページ一つ開くにも、再度クリックしなければならないことが頻繁にある。

$ less /var/log/kern.log | grep -e "NIC Link is Down" -e "NIC Link is Up" | head -20
Jul 30 21:34:49 xxx kernel: [   38.396584] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX
Jul 30 21:35:54 xxx kernel: [  102.912681] e1000: enp0s3 NIC Link is Down
Jul 30 21:36:00 xxx kernel: [  108.962889] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX
Jul 30 21:36:58 xxx kernel: [  167.424237] e1000: enp0s3 NIC Link is Down
Jul 30 21:37:02 xxx kernel: [  171.456655] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX
Jul 30 21:38:07 xxx kernel: [  235.969067] e1000: enp0s3 NIC Link is Down
Jul 30 21:38:11 xxx kernel: [  240.004110] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX
Jul 30 21:39:15 xxx kernel: [  304.512446] e1000: enp0s3 NIC Link is Down
Jul 30 21:39:22 xxx kernel: [  310.656653] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX
Jul 30 21:40:24 xxx kernel: [  373.152291] e1000: enp0s3 NIC Link is Down
Jul 30 21:40:30 xxx kernel: [  379.200501] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX
Jul 30 21:41:43 xxx kernel: [  451.776211] e1000: enp0s3 NIC Link is Down
Jul 30 21:41:49 xxx kernel: [  457.888537] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX
Jul 30 21:42:57 xxx kernel: [  526.432210] e1000: enp0s3 NIC Link is Down
Jul 30 21:43:03 xxx kernel: [  532.480859] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX
Jul 30 21:44:02 xxx kernel: [  590.944245] e1000: enp0s3 NIC Link is Down
Jul 30 21:44:08 xxx kernel: [  596.992489] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX
Jul 30 21:45:04 xxx kernel: [  653.440290] e1000: enp0s3 NIC Link is Down
Jul 30 21:45:10 xxx kernel: [  659.488486] e1000: enp0s3 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: RX
Jul 30 21:46:15 xxx kernel: [  724.000182] e1000: enp0s3 NIC Link is Down

アダプタの状態を見てみても、Speed は 1000Mb/s 設定されているし、Duplex も Full (Halfになっていない)。
特に不安定になっているようには見えない。

$ ethtool enp0s3
Settings for enp0s3:
	Supported ports: [ TP ]
	Supported link modes:   10baseT/Half 10baseT/Full 
	                        100baseT/Half 100baseT/Full 
	                        1000baseT/Full 
	Supported pause frame use: No
	Supports auto-negotiation: Yes
	Advertised link modes:  10baseT/Half 10baseT/Full 
	                        100baseT/Half 100baseT/Full 
	                        1000baseT/Full 
	Advertised pause frame use: No
	Advertised auto-negotiation: Yes
	Speed: 1000Mb/s
	Duplex: Full
	Port: Twisted Pair
	PHYAD: 0
	Transceiver: internal
	Auto-negotiation: on
	MDI-X: off (auto)
Cannot get wake-on-lan settings: Operation not permitted
	Current message level: 0x00000007 (7)
			       drv probe link
	Link detected: yes

ホストOSは Windows7 でネットワークにはUSB接続の無線LANアダプタを使用している。
※ゲストOSとのアダプタは NAT。
ハードウェア側に問題があるとすれば、このアダプタに問題があるか、あるいはルーターも古いので怪しいところはある。

ネットで検索してみると、似たような状況になっている記事や質問がいくつか見かけられたが、ハードウェアを疑っているケースも多かった。
すぐには試せないが、有線ケーブルで接続してみれば、案外それで安定はするのかもしれない。

Eclipse 例外ブレークポイントで効率アップ?

Eclipseデバッグ機能で Exception Breakpoint というものがあるそうで、何をするものか分からなかったので使ってみました。
デバッグ画面で Add Java Exception Breakpoint と表示されるボタンをクリックすると、例外が登録できる。
今回は、"NullPointerException" を登録してみました。
ソースコード上では、 90 % 以上の確率で、"NullPointerException"が発生するようにしています。

それでデバッグ機能を動かしてみると、登録した例外が発生したところで、ブレークしてくれました。
f:id:blueskyarea:20170722002327g:plain

なるほど、例外はふつう、意図しないものなので、”どこで発生しているか分からない”。
でもこの機能を使えば、例外の発生箇所をすぐに見つけることができそうですね。

うーん。
ただ、 JUnit でも例外の発生箇所はわりとすぐに見つかったりするので、本当にかつやくする機会があるかは、まだ分からないのが正直なところです。^^;

テキストファイルを1行ずつ処理するには

input.txt がよみこむテキストファイルの場合

こんな感じです。

while IFS='' read -r line && [[ -n "$line" ]]; do echo $line; done < input.txt
なぜ、IFS='' するのですか?

IFSってのはbash環境変数で、デフォルトでは$' ¥t¥n'(スペース・タブ・改行)が入っています。
そのままだと、半角スペースとタブがかってに消されてしまうため、空文字を設定しています。

read -r のオプションは何のためですか?

バック・スラッシュをエスケープ文字とみなさないで、そのまま取得するため。

awk でパターンにマッチする行のみを抽出する

こんな感じでスラッシュで挟んで指定すれば良いらしい。

$ xwininfo | awk '/Width/'
  Width: 1360

ただ、単に抽出するだけなら、 grep で十分かも。

$ xwininfo | grep 'Width'
  Width: 1360

抽出した結果を変数に入れて処理するには便利そう。

$ xwininfo | awk '/Width/ {width = $2} END { print "width="width }'
width=1360

Java challenge cloneable

Java challenge 既出のコードです。
出力結果を考えてみます。

ソースコード
public class CloneableChallenge {
    public static void main(String[] args) throws CloneNotSupportedException {
        Human human17 = new Human("cells");
	Human human18 = (Human) human17.clone();
		
	System.out.println(human17.equals(human18));
    }
	
    static class Human implements Cloneable {
	Cell cell;
		
	public Human(String cellName) {
	    this.cell = new Cell(cellName);
	}
		
	@Override
	public Object clone() throws CloneNotSupportedException {
	    return super.clone();
	}
    }
	
    static class Cell {
	String name;
	public Cell(String cellName) {
	    name = cellName;
        }
    }
}
予想

"false" が出力される。
正直、Cloneable をちゃんと使ったことがないので、clone 自体のコードに問題があるか分からないです。
単純に Human クラスのインスタンスが正常に clone 出来たと仮定したら、 それぞれ別のメモリ上に存在するはず。

結果

"false" が出力された。
うーん。ただ、上記の理解で正しいのかが分からない。
実際にこの challenge をした人のコメントを見てみると、human17.cell.equals(human18.cell) では true が返ってくるらしい(手元の環境でも true になった)。

Human class は別々のオブジェクトだが、クラス変数である Cell は同じオブジェクトを参照しているということか。

以下のサイトも参考になりました。
promamo.com

試したソースコード
github.com

Java private フィールドをモックする

private フィールドをモックする junit 書きたいなと思って調べたら、すぐに出てきたのが mockito の whitebox.
以前使ったことがあった気がするけど、忘れていました。

とりあえず簡単なサンプルで
public class WhiteBoxExample {
	
	private String message = "This is example.";
	
	public WhiteBoxExample() {
	}
	
	public String getMessage() {
		return message;
	}
}

これの message 変数に格納されている内容を書き換えるということです。

junit の方は
public class WhiteBoxExampleTest {

        // 1つ目は何もせずに、そのまま出力させるテスト
	@Test
	public void testWithoutWhiteBox() {
		WhiteBoxExample wb = new WhiteBoxExample();
		assertTrue(wb.getMessage().equals("This is example."));
	}

        // 2つ目は whitebox を使って、値を書き換えてます
	@Test
	public void testWithWhiteBox() {
		WhiteBoxExample wb = new WhiteBoxExample();
		Whitebox.setInternalState(wb, "message", "overwritten the message.");
		
		assertTrue(wb.getMessage().equals("overwritten the message."));
	}
}

Whitebox.setInternalState(対象クラスのインスタンス, "モックするフィールド名", 置き換える値);
たったこれだけで private フィールドが置き換わってしまいました。
非常に強力なライブラリだと思いますが、何でも置き換えてしまうと、本来の仕様に沿ったテストから遠ざかってしまう気がします。
テストのコア部分を置き換えるのではなくて、そこに到達するまでのあくまで補助的に使った方が良いですね。
そもそも頼らないでテスト出来るように設計出来れば良いのかもしれませんが。