技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

Java:Mockitoのチュートリアル

【はじめに】
Mockitoとは、Javaユニットテストをサポートするためのライブラリです。
 
テスト対象のクラスから呼び出されるクラス(オブジェクト)の代わりとなるオブジェクトを生成する機能を持ちます。
代わりとなるオブジェクトは、「モックオブジェクト」と呼ばれます。
 
図で説明すると以下の通りです。

 
テスト対象のクラスから呼び出されるクラスをそのまま用いるとユニットテストが困難になる場合にMockitoが役立ちます。
例えば、そのクラスが開発中の場合に、開発完了前にユニットテストを行うことができるようになります。
また、そのクラスの出力が不定の場合(データベースや外部システムの状態に依存する、ランダム性がある、等)にも役立ちます。
 
実際に動かすと、Mockitoがどのようなライブラリなのか理解しやすいです。
この記事では、Eclipse上でのJavaプロジェクトの構築、JUnitを用いたテスト環境構築、Mockitoの導入、Mockitoを用いたテストの実施、という順を追って、Mockitoを実際に動かしてみます。
Javaのバージョンは11です)
 
なお、JUnitを用いたテスト環境を構築する所までは、株式会社サイゼントが発行している「絶対にJavaプログラマーになりたい人へ(https://www.amazon.co.jp/%E7%B5%B6%E5%AF%BE%E3%81%ABJava%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC%E3%81%AB%E3%81%AA%E3%82%8A%E3%81%9F%E3%81%84%E4%BA%BA%E3%81%B8%E3%80%82-%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E9%96%8B%E7%99%BA%E4%BC%9A%E7%A4%BE%E3%81%AE%E6%9C%AA%E7%B5%8C%E9%A8%93%E5%90%91%E3%81%91%E7%A4%BE%E5%86%85%E7%A0%94%E4%BF%AE%E3%82%92%E5%85%AC%E9%96%8B%E3%80%82%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%82%B9%E3%82%AF%E3%83%BC%E3%83%AB%E3%82%92%E9%81%8B%E5%96%B6%E4%B8%AD-%E6%A0%AA%E5%BC%8F%E4%BC%9A%E7%A4%BE%E3%82%B5%E3%82%A4%E3%82%BC%E3%83%B3%E3%83%88-ebook/dp/B0D2VJ3GF2)」の第一章~第三章に詳しく書かせていただいています。
環境構築に躓いてしまう方は、こちらも是非ご覧になってください!
 
Eclipse上でのJavaプロジェクトの構築】
Eclipseを起動したら、画面上部のメニューバーから、「ファイル > 新規 > Java プロジェクト」を選択します。

 
その後、プロジェクト名に任意のプロジェクト名を入力し、JREのバージョンにJava 11を選択したら、「完了」を押下します。

 
この手順で新規のJavaプロジェクトが作成されます。
 
その後、以下のクラスを作成します。
(クラスの作成は、新規で作成されたJavaプロジェクトを画面左部のプロジェクト・エクスプローラー上で「右クリック > 新規 > クラス」を選択することで作成できます)
 
・MyLogic.java
package mylogic;

public class MyLogic {

    /* 商品コードから在庫量を取得 */
    public int getStockAmount(String productCode) {
        
        int stockAmount;
        
        // 本当はDBアクセスを含む複雑な処理を行うが、今回はシンプルな処理とする
        try {
            stockAmount = Integer.parseInt(productCode);
        } catch (Exception e) {
            stockAmount = 0;
        }
        if (stockAmount < 0) {
            stockAmount = 0;
        }
        
        return stockAmount;
    }
}
 
・MyMain.java
package mymain;

import mylogic.MyLogic;

public class MyMain {

    private static MyLogic myLogic = new MyLogic();
    
    public static void main(String args) {
        
        System.out.println("商品コードから在庫量を取得。");
        String productCode = "0020";
        System.out.println("商品コード:" + productCode);
        System.out.println("在庫量:" + myLogic.getStockAmount(productCode));
        
    }

}
 
その後、確認のため、プロジェクト・エクスプローラー上でMyMain.javaを「右クリック > 実行 > Java アプリケーション」で実行します。

 
以下のように出力されれば、ここまでの環境構築はOKです。
 
商品コードから在庫量を取得。
商品コード:0020
在庫量:20
 
【JUnitを用いたテスト環境構築】
次に、MyLogicクラスをテストするためのテストコードを作成します。
テストコードの内容は以下の通りです。
 
・MyJUnit.java
package mytest;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

import mylogic.MyLogic;

class MyJUnit {

    private MyLogic myLogic;
    
    @Test
    void test() {
        myLogic = new MyLogic();
        String productCode = "0020";
        assertEquals(20, myLogic.getStockAmount(productCode));

    }

}
 
ここまでで、以下のようなフォルダ構成になっているはずです。

 
その後、確認のため、プロジェクト・エクスプローラー上でMyJUnit.javaを「右クリック > 実行 > JUnit テスト」で実行します。

 
テストが成功すれば(緑色のバーが表示されれば)、ここまでの環境構築はOKです。
 
【より実践的なコードへの修正】
ここで、Mockitoの使用に適した実践的なソースコードに修正します。
ここでは、「MyLogic.java」を、商品コードに対応した在庫量を取得する「MyLogicSub.java」と、その結果を修正して返す「MyLogic.java」に分割します。
また、在庫量の取得ロジックに乱数を用いるようにします。
(実際はデータベース等の外部のリソースにアクセスすることで取得するのが一般的ですが、ここでは、「外部の状態に依存するために結果が不定になる」というのを表現するために乱数を用います)
 
MyLogicSub.javaを新たに作成します。
 
・MyLogicSub.java
package mylogic.sub;
import java.util.Random;

public class MyLogicSub {

    /* 商品コードから在庫量を取得 */
    public int searchStockAmount(String productCode) {
    
        int stockAmount = 0;
        
        // 本当はDBアクセスを行うが、今回はシンプルな処理とする
        try {
            stockAmount = Integer.parseInt(productCode);
            // 取得結果が不定(外部に依存する)というのを乱数で表現
            Random random = new Random();
            stockAmount = stockAmount + random.nextInt(11) - 5;
        } catch (Exception e) {
            stockAmount = 0;
        }
        
        return stockAmount;
        
    }
    
}
 
また、これまで作成してきたクラスも、クラス分割の方針に従って修正します。
 
・MyLogic.java

package mylogic;

import mylogic.sub.MyLogicSub;

public class MyLogic {

    /* 商品コードから在庫量を取得 */
    public int getStockAmount(String productCode, MyLogicSub myLogicSub) {
        
        int stockAmount;
        
        // DBアクセスにより在庫量を取得
        stockAmount = myLogicSub.searchStockAmount(productCode);
                
        // DBアクセスして得た結果を補正。今回は単純な処理とする。
        if (stockAmount < 0) {
            stockAmount = 0;
        }
        
        return stockAmount;
    }
}

 
・MyMain.java
package mymain;

import mylogic.MyLogic;
import mylogic.sub.MyLogicSub;

public class MyMain {

    private static MyLogic myLogic = new MyLogic();
    
    public static void main(String args) {
        
        System.out.println("商品コードから在庫量を取得。");
        String productCode = "0020";
        System.out.println("商品コード:" + productCode);
        MyLogicSub myLogicSub = new MyLogicSub();
        System.out.println("在庫量:" + myLogic.getStockAmount(productCode, myLogicSub));
        
    }

}
 
・MyJUnit.java
package mytest;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

import mylogic.MyLogic;
import mylogic.sub.MyLogicSub;

class MyJUnit {

    private MyLogic myLogic = new MyLogic();
    
    @Test
    void test() {
        String productCode = "0020";
        MyLogicSub myLogicSub = new MyLogicSub();
        assertEquals(20, myLogic.getStockAmount(productCode, myLogicSub));

    }

}
 
ここまでで、以下のようなフォルダ構成になっているはずです。

 
この状態でテストを実行すると、10回に9回以上の確率で失敗するように(赤色のバーが表示されるように)なります。
これは、在庫量の取得に乱数を用いるようにしたことで、期待結果の在庫量と一致しないケースが出てくるようになったためです。
 
このままではMyLogicクラスのユニットテストが難しくなるので、今後の手順でMockitoを用いることで問題を解決していきます。
 
【Mavenプロジェクトへの変換】
ライブラリを取り込む上では、ビルドツールを用いると便利です。
ここでは、ビルドツールにMavenを使用します。
 
まず、プロジェクト・エクスプローラー上で「プロジェクト名を右クリック > 構成 > Maven プロジェクトへ変換」を選択します。

 
その後のポップアップでは完了を押下します。

 
すると、JavaプロジェクトがMavenプロジェクトに変換され、Mavenを使用したライブラリ取り込みが可能になります。
以下のようにpom.xmlが出力されていれば成功です。

 
【Mockitoの導入】
pom.xmlを修正し、Mockitoを取り込むdependencyタグの設定を記述します。
 
・pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>MochitoTest</groupId>
  <artifactId>MochitoTest</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-core</artifactId>
      <version>4.0.0</version>
    </dependency>
  </dependencies>
  <build>
    <sourceDirectory>src</sourceDirectory>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <release>11</release>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
 
pom.xmlを保存すると、Mavenにより、Mockitoが自動で取り込まれます。
以下のように依存関係にMockitoが表示されれば成功です。

 
【Mockitoを用いたテストの実施】
いよいよ、テストクラスであるMyJUnitクラスでMockitoを使用します。
 
モックオブジェクトに置き換えたいオブジェクトは、MyLogicSubクラスのオブジェクトです。
それを示すために、MyLogicSubクラスを定義する箇所で@Mockアノテーションを定義します。
また、それを使用するMyLogicクラスを定義する箇所では@InjectMocksアノテーションを定義します。
 
テストメソッドを実行する前に実行されるメソッドとして、initMocksメソッドを定義します。
ここでは、MockitoAnnotationsクラスのopenMocksメソッドを、テストクラス(this)を引数にして実行します。
これにより、前述のアノテーションが使用可能になります。
 
そして、モックオブジェクトの処理内容は、テストメソッド中に記述します。
今回は以下のような記述を行っています。

String productCode = "0020";
Mockito.doReturn(20).when(myLogicSub).searchStockAmount(productCode);

これは、「myLogicSubクラスのsearchStockAmountメソッドが呼ばれた時、引数に"0020"(変数「productCode」の値)が与えられていれば、戻り値として20を返す」という意味です。
つまり、myLogicSubクラスを以下のような処理内容に置き換えたのと同じ意味です。

public class MyLogicSub {

    /* 商品コードから在庫量を取得 */
    public int searchStockAmount(String productCode) {
    
        int stockAmount;
        
        if (productCode = "0020") {
            stockAmount = 20;
        }
        
        return stockAmount;
        
    }
    
}
 
このmyLogicSubクラスのモックオブジェクトを利用することで、処理結果が意図した一定の値になるようになり、テストが成功するようになります。
 
ここまで書いた内容をソースコードとしてまとめると以下の通りです。
 
・MyJUnit.java
package mytest;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import mylogic.MyLogic;
import mylogic.sub.MyLogicSub;

class MyJUnit {

    @InjectMocks
    private MyLogic myLogic;
    
    @Mock
    private MyLogicSub myLogicSub;

    @BeforeEach
    public void initMocks() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    void test() {
        String productCode = "0020";
        Mockito.doReturn(20).when(myLogicSub).searchStockAmount(productCode);
        assertEquals(20, myLogic.getStockAmount(productCode, myLogicSub));

    }

}
 
テストが必ず成功するようになれば、Mockitoの利用に成功しています。
 
このように、Mockitoを用いれば、テスト対象のクラスから呼び出されるクラスの処理を意図通りの仮の値とすることができるようになります。
テスト対象のクラスから呼び出されるクラスの処理が意図しない値を返すことによるテスト失敗がなくなるため、テスト対象のクラスの処理内容のテストに集中することができるようになります。

乱数の解析行為とその対策

私がプレイしているオンラインゲームで話題になったので、乱数の解析行為とその対策について、簡単にまとめます。
 
【コンピューターにおける乱数とは】
「乱数」とは、その名の通り、ランダムな数値のことを指します。
しかし、コンピューター上で乱数を生成する場合、特定のアルゴリズムにより疑似的に生成しており、正確に言うと完全にランダムではないため、専門的には「疑似乱数」と呼ばれます。
 
例えば、平易なアルゴリズムの例として「線形合同法」が挙げられます。
このアルゴリズムでは、「初期値(シード)」と呼ばれるXn(n=0)の値を与え、Xn(n=1)の値をXn(n=0)を元に算出し、Xn(n=2)の値をXn(n=1)を元に算出し…といった形で、前回の乱数の生成値から今回の乱数の生成値を求めています。


 
他にも疑似乱数を生成するアルゴリズムの種類はいくつか存在しますが、前回の乱数の生成値から今回の乱数の生成値を求めている所は変わりありません。
 
【疑似乱数の解析行為】
疑似乱数では、アルゴリズムを知っている状態で実際に出力される生成値を観察することで、初期値を解析できる場合があります。
そして、この解析に成功した場合、初期値から順番に計算を行うことで、Xnの値を知ることができます。
 
例えば、先ほどの「線形合同法」がアルゴリズムとして適用されている場合において、Xn(n=1)から順番に
2, 5, 1, 4, 0, 3...
といった値が観察される場合、初期値は6であると解析することができます。
(M = 7, A = 1, B = 3)
 
通常、実際に使用される乱数のアルゴリズムはより複雑なものですが、同じ要領で、複数の生成値から初期値を解析することができる場合があります。
 
【解析行為への対策】
疑似乱数は、暗号鍵の生成のようなセキュリティ分野や、オンラインカジノのようなギャンブル等の分野でも用いられます。
これらの分野で疑似乱数を解析され、乱数の生成値が推測された場合、何かしらの犯罪行為が行われて不利益を受ける可能性あるため、そのような分野で疑似乱数を用いる場合は解析対策を行う必要があります。
 
対策を行う上では、「暗号論的疑似乱数」と呼ばれる、学術的に解析が困難なアルゴリズムを用いて乱数を生成することが有効です。 
(ただし、「暗号論的疑似乱数」は計算量が多いため、シミュレーションや一人向けゲームのような解析対策を行う必要がない分野においては、通常の疑似乱数の方が適しています)
 
【疑似乱数の実装方法】
各言語で、疑似乱数や暗号論的疑似乱数を生成するためのライブラリが用意されています。
例えば、Javaの場合は以下のようにライブラリを使用して実装することができます。
 
・疑似乱数
public class Main {
    public static void main(String[] args) throws Exception {
        double randomNumber = Math.random();
        System.out.println(randomNumber);
    }
}
 
・暗号論的疑似乱数
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;

public class Main {
    public static void main(String[] args) throws Exception {
        try {
            SecureRandom randomNumber = SecureRandom.getInstance("SHA1PRNG");
            System.out.println(randomNumber.nextDouble());
        } catch (NoSuchAlgorithmException e) { 
            // 例外処理
        }
    }
}

私が所属する会社からSpring Frameworkの入門書が発行されました!

以前に、私が所属する会社からJava入門者向け書籍「絶対にJavaプログラマーになりたい人へ」を発行しましたが、その別冊をこの度発行しました!
今回も、私が執筆の多くの部分に関わっています。
 
解説の範囲をSpring Frameworkに限定することで、より容易に理解できるようにきめ細かく解説しています。
 

Kindleで販売しており、購入はこちらからできます。
ご興味がある方は、年末年始の時間がある時に手にとってみられてはいかがでしょうか?

 

X(Twitter)始めました!

既に2か月ほど経ってますが、X(Twitter)始めました!
このブログとは異なり、こちらは雑談メインです。
 
よろしければこちらもよろしくお願いします🙇
 

https://x.com/red_slime_cyz

結合条件で件数が膨らむと性能が悪化する

SQLでのクエリの性能改善というと、「インデックスを設定する」「本当にインデックスが使われているか実行計画を見て確認する」といった、RDBMSの物理的な機能の活用に注目しがち。
 
しかし、それ以前の問題として、「クエリの最中に件数が増加しないようにSQLを書く」という、論理的な観点での工夫により性能改善が図ることができるケースも多いです。特に、性能を考慮せずに業務的な手続きをそのままSQLに書き起こしているような場合は、論理的な観点での性能改善の余地が残っていることが多いです。
 
具体的に言えば、テーブルをJOIN句で結合する際に、結合順や処理順を工夫することで、クエリの最中に件数が増加することを防ぐことができ、性能改善を図ることができます。
そのことを、この記事では説明していきます。
 
----
 
まずは、テーブル結合により件数が膨らむ現象について説明します。
 
これは、件数が膨らまない場合です。


結合元の商品マスタは3件、結合先の商品ページ閲覧履歴は3件、共に商品コードで一意にレコードを特定できます。
以下のようなクエリを発行し、商品名ごとの閲覧回数を取得する場合も、商品マスタの件数と同じ件数の3件が取得されます。
a.商品名 AS 商品名,
b.閲覧回数 AS 閲覧回数
FROM 商品マスタ AS a
JOIN 商品ページ閲覧履歴 AS b
ON a.商品コード = b.商品コード
;
 
次に、結合先のテーブルの件数に引きずられて件数が膨らむ場合です。

結合元の商品マスタは3件、結合先の購入履歴は5件です。
商品マスタは商品コードで一意にレコードを特定できますが、購入履歴は顧客コードと商品コードの組み合わせでないと一意にレコードを特定できません。
以下のようなクエリを発行し、商品名ごとに購入日を取得する場合、結合元の件数である3件を超え、結合先の件数に引きずられ、5件が取得されます。
a.商品名 AS 商品名,
b.購入日 AS 購入日
FROM 商品マスタ AS a
JOIN 購入履歴 AS b
ON a.商品コード = b.商品コード
;
 
そして、直積結合のような形で、結合元や結合先のテーブルの件数を超えて件数が膨らむ場合です。

結合元の商品マスタは3件、結合先のメーカーマスタも3件です。
共にメーカーコードだけでは一意にレコードを特定できず、商品マスタは商品コード、メーカーマスタはメーカーコードと廃止日で一意にレコードを特定します。
以下のようなクエリを発行し、商品名ごとにメーカー名を取得する場合、結合元や結合先の件数である3件を超えて、5件のレコードが取得されます。
a.商品名 AS 商品名,
b.メーカー名 AS メーカー名,
b.廃止日 AS 廃止日
FROM 商品マスタ AS a
JOIN メーカーマスタ AS b
ON a.メーカーコード = b.メーカーコード
;
 
----
 
ここまでの例を見てお気づきかもしれませんが、テーブルのレコードを一意に特定できないカラムを結合条件として指定すると、件数が膨らんでしまいます。
 
テーブルのレコードを一意に特定するには、主キーや一意キーを結合条件として指定するのが間違いありませんが、それ以外のカラムを指定する場合も、設計上もしくは運用上、一意に特定できることもあります。
実際の設計や運用を考慮し、言い換えると実際に入っているデータに着目して、テーブルのレコードを一意に特定できているかどうか調べる方法としては、以下の2つのクエリを投げて結果が一致するかどうかを見る方法があります。一致すればレコードを一意に特定できていますし、一致しなければレコードを一意に特定できていません。乖離が大きければ大きいほど、件数が大きく膨らむ可能性が高まります。
・SELECT COUNT(*) FROM 対象テーブル;
・SELECT COUNT(*) FROM (SELECT DISTINCT 結合条件 FROM 対象テーブル);
 
----
 
このように、結合条件によっては、テーブル結合により件数が膨らむことがあります。
テーブル結合を何度も行うような長いクエリの場合、中間結果の件数が膨らむと、性能が大きく悪化することがあります。
 
それを防ぐために一番良い方法は、結合条件を見直し、件数が膨らまないようなテーブル結合を行うことです。
しかし、業務要件上、それは難しい場合が多いです。
 
結合条件の見直しが難しい場合、以下のような対策により、中間結果の件数の膨らみを最小限にすることが有効になります。
・WHERE句や集計(集合関数やDISTINCT)による件数の絞り込みはなるべく早い段階で行う
・その上で、件数が膨らむテーブル結合はなるべく遅らせる
 
なお、内部結合(JOIN・INNER JOIN)に関しては、単に件数を絞るために記述することもあるので、その場合はExistsやサブクエリに書き換えることで性能が改善する可能性があります。
ただし、これはRDBMSやテーブル定義による所が多く、逆に性能が悪化する可能性も否定できないので、一般化できるものではなく、この方法を使うとしても実行計画を調べてから使うのが望ましいです。

私が所属する会社から入門書が発行されました!スクールも同時開校です!

表題の通り、Javaプログラミング入門書「絶対にJavaプログラマーになりたい人へ」が発行されました!
Amazon kindleで読むことができます。
 
私も執筆の多くの部分に関わっています。
 
ご興味がある方は是非とも手に取ってみてください!


 
同時に、プログラミングスクール「サイゼントアカデミー」が開校されました!
サポートやフィードバックが欲しくなりましたらこちらも是非!
 
詳しい情報は、会社のブログにて書かせていただきました。

cyzennt.co.jp

ITシステムの安定稼働は神頼み?

GWということで、ITシステムのリリースを控えている会社は少なくないと思います。
リリースするシステムは是が非でも安定稼働させたいものです。
 
そこで、神様の力を借りて、システムの安定稼働を祈願するのはいかがでしょうか?
嘘のような話かもしれませんが、神社に出向いたり、祭壇を作ったり、お札を貼ったりする現場は実在しますし、筆者の経験上、その効果は無視できないものであると思っています。
 
----
 
東京都内で働く人にとって最も有名で手が出しやすいのは、神田明神だと思います。
神田明神の公式ページはこちらです。

www.kandamyoujin.or.jp


 
公式ページの「アクセス」の通りですが、御茶ノ水駅から徒歩5分、新御茶ノ水駅から徒歩5分、末広町駅から徒歩5分、秋葉原駅から徒歩7分と、都内で働いている人にとってはアクセスしやすい場所にあります。
また、「FAQ」の通り、この神社はITの安全を祈願する場所でもあります。「IT情報安全守護」というお札も販売されています。
 
----
 
神頼みには科学的な根拠はないかもしれませんが、筆者の経験上、関係者の精神面、特に「神頼み」という発想に至るまでの過程において、何かしらの効果はあると思っています。
 
どれだけ準備を怠らなかったとしても、システムが安定稼働するかどうかは運次第な所が要素が残ります。
ハードウェアが故障するかもしれませんし、連携している他社のシステムの障害に巻き込まれるかもしれません。
そして、人間がすることなので、ミスを完全にゼロにすることもできません。
 
運だと思えることまで徹底して準備を行い、それでも何かできることがないかと考えた末に辿り着くのが「神頼み」という発想です。
目の前の準備に追われ、余裕がないプロジェクトにおいては、神頼みしようと思うような精神状態ではなくなります。
「神頼み」という発想に至った時点で、品質は高い傾向にあり、実際に安定稼働もする、というのが、筆者の見解です。
 
筆者も、「神頼み」という発想に至るまで準備を怠らないようにしたいものです。