おおくまねこ

職業プログラマーです。興味のある話題を書いています。

mockito でコンストラクターの mock を使ったテストをしたい

はじめに

今回は「mockito を使ったコンストラクターを mock 化する方法」について記載します。

Javaユニットテストを記述する際に利用するモックライブラリ mockito の使い方のひとつの説明です。

 

以前、以下で「mockito を使った static メソッドを mock 化する方法」について書きましたが、

その派生、コンストラクターを mock 化する場合の内容になります。

keyno63.hatenablog.com

 

コンストラクタを mock 化する際のモチベーションについて

この機能を使うモチベーション、つまり使いどころとしては、

メソッド内でクラスインスタンスを生成しているものがあり、

それをJUnit でテストする場合です。*1

 

具体的な例をコードで記述すると、以下のようなメソッドを想定しています。

public Object method(String value) {
var o = new SomeClass(value);
return o.doSomething();
}

 

mockito によるコンストラクタの mock 化

コンストラクタを mock 化する方法として powermock を使う方法があると思います。

前述した前回の記事でも触れましたが、powermock は JUnit5 対応や対応の検討もされていないため、

mockito を使った方法があるのでそれを使います。

 

mockito の mockConstruction というメソッドを使うと、

そのスコープ中は該当するクラスのインスタンスは自動的に mock に指し変わった状態となるため、

自由にふるまいを定義してテストに使うことができます。

 

Mockito 3.5.0 以降であれば、mockito によるコンストラクターの mock 化が可能になっています。

(出展として、草案のようなPR機能マージされたPRはありましたが、Github のリリースノートなどに機能追加されたことの説明などをみつけらませんでした。)

 

使い方の確認

実際の実装・動作確認した方法について触れていきます。

開発環境

  • OS: Windows 10 Pro
  • IDE: Intellij IDEA 2021.2 (Ultimate Edition)
  • JDK: openjdk-16.0.2
  • ビルドツール: gradle(7.0)

JDKバージョン、gradle のバージョンは古すぎなければほかのバージョンでも問題ないと思います。

私の環境では(たまたま)このバージョンを使っていました。

 

実装方法

mockito モジュールをビルドツールの設定に追加

gradle を使っている場合、build.gradle の依存関係に以下を追記します。

dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.0'
testImplementation 'org.mockito:mockito-inline:3.12.4'
testImplementation 'org.assertj:assertj-core:3.20.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.0'
}

test {
useJUnitPlatform()
}

モジュールのバージョンは記載時点(2021/09)の最新版を使用しています。

 

実装例

コンストラクターを使っているメソッドのテストの実装例を記載します。

以下のようなクラスをテストするとします*2

public class UseConstructorSample {

public String useMethod(String arg1, String arg2) {
ConstructorSample sample = new ConstructorSample(arg1);
return sample.method(arg2);
}

public static class ConstructorSample {

private final String value;

public ConstructorSample(String value) {
this.value = value;
}

public String method(String arg) {
return String.format("field=[%s], method=[%s]", value, arg);
}
}
}

ConstructorSample を mock 化し、UseConstructorSample の useMethod メソッドをテストすることを考えます。

 

実際にテストするコードは以下のように記述できます。

class UseConstructorSampleTest {

@Test
final void test() {
final String expected = "mockedValue";
try(MockedConstruction<ConstructorSample> mocked = mockConstruction(ConstructorSample.class,
(mock, ctx) -> doReturn(expected).when(mock).method(anyString()))
) {
UseConstructorSample target = new UseConstructorSample();
String actual = target.useMethod("value1", "value2");
assertThat(actual).isEqualTo(expected);
}
}
}

 

mockConstruction() メソッドの第一引数で mock 化したい対象のクラスを指定し、

第二引数で mock の振る舞いをラムダ式で定義しています。

第二引数で使っている mock が mock 化したい対象のクラス(今回だと ConstructorSample)になっているのに注意してください。

mockConstruction() が返す MockConstruction<T> のインスタンスは mock の制御をするためのインスタンスで、mock 自体ではないようです。

 

定義した mock の振る舞いは、try 文で囲われた領域で定義した通りの振る舞いを行います。

(mock, ctx) -> doReturn(expected).when(mock).method(anyString())

※doReturn、when を使って mock の振る舞いを定義。今回は「すべての文字列を受け取った時に expected を返す」振る舞いをする。

 

try 文で囲っているのは、MockConstruction を自動的に Close するためです。

インスタンスを Close されるまでが mockConstruction() で定義した mock の有効期間になります。

try 文を使わない場合は、最後に close() を呼ぶ必要があります。

MockedConstruction<ConstructorSample> mocked = mockConstruction(ConstructorSample.class);
...
mocked.close();

 

最後に

コンストラクターを mock 化する方法は、あまり使うケースが多くないとは思うのですが、

調べてみると mockito を使った方法についての情報になかなかたどりつかなかったり、

自分な必要な情報単位で巡り合えなかったので、このようにまとめるようにしました。

 

*1:可能であれば DI を使ったほうがテストのしやすさやインスタンスの一意性とかがあるのでメリットがあるのですが、事情によりこういうコードが紛れ込んでいる場合がありますよね

*2:ファイル分けすると視認性が悪くなるので内部クラスにしています

Slack のチャンネルにメッセージを送信するために Incoming Webhook を使って送信する

はじめに

概要

Slack にメッセージを送信するため、コマンドラインcurl やプログラムから

HTTPリクエストをよるAPIを用いた投稿をする方法がありますが、

そのやり方をいつも忘れてしまうので履歴として残しています。

 

Slack API にリクエストするアプリケーションを作成するのは

1年に1回とかが私にはいいところなので、

作るたびにやり方を忘れてしまっていたりUIがかわったりしているので

備忘録としても記載することにしました。

 

Slack API でポストする方法は3つありますが、

その中で Incoming Webhook を選んだ強い理由はありません。

token の管理をアプリケーション側でしなくてよいことくらいかなと思うのですが、

他のでもよかったのかもしれません。

ひとまずの動作確認をしたかったので以上のものを選びました。

 

2021年8月時点の使い方、UIの操作方法になります。

ユースケース

どういうケースで Slack API からの投稿を行いたいかというと、

機械的に実行した結果を Slack に投稿したい場合や、

システムにイベントが発生した時に、その内容や通知を Slack に飛ばしたい場合かと思います。

 

なお、今回の内容では触れることはないんですが、

私の場合は Github API で取得したPRの情報などを形成して

内容を Slack に通知したいというユースケースを実現するためのプログラムを作成しようとしました。

(頻度しては1か月に1回とかくらいですが)

 

参考にさせていただいた内容

以下のブログの「Incoming Webhooks を使う」を参考にしました。

説明は丁寧ですし、「Bot token を使う場合」、「User Tokenを使う場合」についても詳細に書かれているので、とても参考になる内容でした。

christina04.hatenablog.com

 

内容

Slack の workspace は事前に作成済みを想定しています*1

 

実際にやることの内容は以下になります。

  • 投稿する App を作成します(作成済みのものを使う場合は不要)
  • 「Incoming Webhooks」を有効にする

投稿するApp の作成

ワークスペースにログインした状態で、以下にアクセスして投稿用の App を作成します。

Slack API: Applications | Slack

 

「Create New App」をクリックして、Appを作成することができます。

 

f:id:keyno63:20210810003405p:plain

クリックすると、ダイアログが表示されるので、「From scratch」を選択します。

さらにダイアログが表示され、Appの名称・使うワークスペースの選択をすればAppを作成されます。

f:id:keyno63:20210810003856p:plain

今回は「test_app」という名前で作成しました。

隠している部分は、自分の workspace を選択しています。

 

Incoming Webhooks の有効化

 App を作成すると「Basic Information」という画面に遷移していると思います。

f:id:keyno63:20210810004917p:plain

遷移できていない場合や、既存の app から有効化する場合は、

先ほどの「Your Apps」画面から使いたい App を選択することで遷移することができます。

 

「Basic Information」にある「Add features and functionality」から、

「Incoming Webhooks」を選択します。

f:id:keyno63:20210810005035p:plain

 

遷移先の画面で、右上にある Incoming Webhook の有効を行い、

画面の下のほうにあるボタンをクリックして投稿先のチャンネルを選択するようにします。

f:id:keyno63:20210810005617p:plain

投稿先のチャンネルを選択する画面は以下のようになっています。

今回は「general」に投稿するようにします。

f:id:keyno63:20210810005838p:plain

「許可する」 をクリックすると、Webhook URL が作成されます。

f:id:keyno63:20210810010043p:plain

Webhookを作る作業としては以上で完了です。

 

動作確認

実際にURLにリクエストして確認します。

curl などで動作確認も可能です。

TypeScript でアプリケーションを作っていたので、以下のようなサンプルコードを使って確認します*2

const slackBotClient = axios.create({
baseURL: "https://hooks.slack.com/services",
responseType: "json",
headers: {
"Content-Type": "application/json"
}
});

const botData = {
text: "bot message"
};

slackBotClient.post(webHookPath, botData)
.then(
v => {
console.log("slack bot")
console.log(v.data)
}
)

実行すると、以下のような結果が標準出力されます。

// > npx tsc && node dist/index.js
//
// slack bot
// ok

Slack のチャンネルを確認すると、メッセージが投稿されているのが確認できます。

f:id:keyno63:20210810011020p:plain

 

最後に

これで Slack のチャンネルにプログラムからチャットを投稿できるようになりました。

*1:未作成の場合は以下を参考にしてください。

Slack ワークスペースを作成する | Slack

*2:「axios」という HTTP Client を使って確認しています。「webHookPath」という変数に今回作った webhook URL を設定しています。。

docker-compose と docker サブコマンドの compose って結局どっち使えばいいのかという話

始めに

書いていること

Docker 管理する方法に Docker Compose を使う方法があります。

その実行する時のコマンドが複数あり、「結局どっちを使うといいのか?」、「何が違うのか?」というのが意外と情報としてないらしいので、ブログにまとめてみようと思いました。

以前から使ってる人からすると悩むところではないとは思うのですが、

Docker をこれから始める、Docker Composeは今まで使ったことない人からすると、この部分の情報があまりなかったそうです。

 

 

Docker Compose とは

Docker Compose とは、複数の Docker を使って構成されるアプリケーションを管理・実行を比較的簡単に行うことのできる機能のことです。

docker-compose.yml を設定ファイルとし、そこにそれぞれのコンテナの情報やネットワーク接続状態を記載して、特定のコマンドから起動するとサービスに必要なコンテナ郡が起動・管理が可能になる機能です。

 

実行コマンド

実際に Docker Compose をコマンドラインから実行する際に、(2021年7月)現在では以下の2パターンがあります

  • docker-compose コマンドを使う
  • docker コマンドのサブコマンドである compose を使う

 

この二つについて記載します。

 

想定してる環境

筆者の経験として開発環境や動作確認の環境構築では docker (および Docker Compose)を使っていることがしばしばなので、

そういうユースケースを想定しています。

本番環境としては若干毛色が異なるかもしれません。

(本番では kubernetes を使ってるよという人も多いかもしれません)

 

なお、個人のおすすめなので、強制力があるわけではありません。

本内容に違和感があったり、チームの方針と合わないなどあれば、そちらを優先していただければと思います。

 

本題

docker-compose と、docker compose (以下、 compose サブコマンド)の違いや、

どちらを使ったほうがいいのかについて記載します。

結論:結局どっちを使うといいのか

早速結論から言います。

compose サブコマンドを使うほうがおすすめです。

ただ、現時点では compose サブコマンドも docker-compose コマンドも互換性を維持しているので、どちらでも構わないと思います。

以前から docker-compose を使っていて、作業手順の実績があるからとかで使い続けたいとかであれば docker-compose を使っていても問題はないと思います。

 

そもそも docker-compose と compose サブコマンドはどっちが先に作られたのか

docker-compose の方が以前から存在して、使われていました。

以前までは docker-compose コマンドは、docker コマンドや docker-engine とは別になっていて、使う場合は別々にインストール必要がありました。

後追いの形で docker コマンドのサブコマンドとして、compose サブコマンドが追加されるようになっています。

 

どう違うのか

後発の compose サブコマンドは docker-compose コマンドの将来的な置き換えを目指して開発された機能になるそうです。

docker-compose は python で書かれたものですが、compose サブコマンドは compose-cli というレポジトリーで Go で再実装されています。

互換性を維持した状態で開発されています。ここでいう互換性というのは、docker-compose 側で使えるオプションコマンドや実行方法などが同じような状態で使えるようにされています。

例えば、docker compose の起動の際、docker-compose コマンドで起動する場合は以下のようでした。

docker-compose -f docker-compose-sampl.yaml up -d

 

compose サブコマンドでは以下のように実行すれば同じことが可能になります。

docker compose -f docker-compose-sampl.yaml up -d

 

互換性をどのくらい意識してくれているのか気になる方は以下のissue を参照すると良さそうです。

github.com

いつから compose サブコマンドがあるのか

Mac/Windows ともに Docker Desktop の v3.0.0 からになります。

以下抜粋

> First version of docker compose (as an alternative to the existing docker-compose). 

docs.docker.com

docs.docker.com

なので 2020 年12月くらいから compose サブコマンドが使えるようになっていることになります。

 

LinuxUbuntu)の場合、Docker 19.03 以降でインストールスクリプトを実行することでインストールできるようです。

github.com

 

最後に

まとめ

  • どちらかというと compose サブコマンドがおすすめ
  • docker-compose の後に compose サブコマンドが再開発された
  • compose サブコマンドは以前とほぼ同様の使用感で使える

AWS EC2 インスタンス上で tcp 接続できる Docker を構築する

はじめに

リモートのサーバー上で起動する Docker にローカル環境のIDEなどから接続できる環境が欲しく、

AWS ec2 インスタンス上で作ってしまおうというのが事の発端です。

正確には、リモートで接続できる環境を作るためのIDEからの接続確認できる環境をつくりたかったのが、AWSを使った理由です。

 

普段はPCのローカル上に、Docker for Windows、Docker for Mac などの上で Docker コンテナを起動させて、

その中でコードの開発とか、動作環境の構築をしているのですが、

ローカルでDocker 使えないケースとか、共有のDocker動作環境が必要というニーズが色々あって発生してしまいました。

 

他の Linux サーバーやWindows, Mac PC があればそちらを使った方がいいのかもしれないのですが、

自分の身近にはなかったので、「じゃあAWSでいいかな」というのが採用理由です。

 

本当は「 IDEIntelliJ)から ec2 上の Docker に接続するまで」とか、「VScode から接続する方法」まで書こうかと思ったのですが、

一旦接続先のコンテナ環境つくるまででひとつにしておこうかと思ったので、

EC2 上での Docker 環境の構築までになっております。

 

やったこと

やったことを書いていくのですが、

一部中身が薄いので、(特にすでに作成済みのサービス設定など)

後日追記できればしていこうと思います。

AWS のアカウントについて

自分の場合はアカウントはすでに持ってました。

アカウント自体はずいぶん前に作ってしまっていたので、手順の記憶がないのですが、

前職の中小企業で作った時も自分用に作った時も公式を参考にした記憶があります。

 

AWS アカウント作成の流れ | AWS

 

EC2 インスタンスの作成

EC2インスタンスを作成・起動します。

Docker サービスを動かすために使います。

 

作成画面へ

AWSマネジメントコンソール」画面から、

「ソリューションの構築」にある「仮想マシンの起動」を選択します。

 

OS の選択

EC2を起動するときに使うOSを選択します。

今回は Amazon Linux でいいんじゃ?という感じなので Amazon Linux2 の x86 を選択します。

 

インスタンスタイプの選択

インスタンスの性能などが決まる、インスタンスタイプの選択をします。

無料利用枠の t2.micro 使いたかったので、それを選んでいます。

 

選択した状態で「次のステップ:インスタンスの詳細の設定」ボタンを押して、次にすすみんでいきます。

 

インスタンスの詳細の設定~タグの追加まで

「次のステップ:インスタンスの詳細の設定」を押すと、インスタンスの詳細設定、ストレージの設定、およびタグの設定が待っていますが、

一旦ここはカスタマイズ不要なので、「次のステップ」を押して進んでいきます。

ステップ6 の セキュリティグループの設定まで進みます。

 

セキュリティグループの設定

セキュリティグループグループを変更します。

なぜかというと、セキュリティグループで通信可能なポートや接続元のIP・ポートを指定できるのですが、

これで必要な設定がされていないと、後で接続する際に通信レベルで弾かれてしまうからです。

 

今回用の設定がある場合は「セキュリティグループの割り当て」で「既存のセキュリティグループを選択する」を使っても大丈夫です。

が、こんな変な設定を持っていることはないでしょうから、作る場合を想定して書いています。

今回作った設定で作った内容と説明は以下になります。

  • カスタムTCPルールでポート2375 を許可
    Docker の tcp 接続用のポートです。
  • HTTP
    あとでHTTPサーバーとしてのDockerを動かす想定であればあったほうがいい?
    不要だったら無くてもいいです
  • HTTPS
    あとでHTTPSサーバーとしてのDockerを動かす想定であればあったほうがいい?
    不要だったら無くてもいいです

※接続元のIPなどは、範囲が限定できるなら絞った方が、余計なリクエストを受けなくていいので設定が必要です。自分はいつも使ってないときは止めてるのと、動作確認主体だったのでポート全開放してますが、やらない方が絶対にいいです。

 

これができたら「確認と作成」を押して、確認画面へ

 

インスタンス作成の確認

いままで設定したものが確認画面に表示されます。

問題なかったら「起動」を押してください。

 

キーペアの選択

EC2インスタンスへの SSH 接続時に使用する認証鍵ペアを、どれを使うか選択します。

すでに持っているのであれば、既存のキーペアを使うで良いと思います。

キーペアをまだ作ってないとか、認証鍵を無くした()という場合は、「新しいキーペアの作成」から認証鍵を取得します。

適当なキーペアを決めたら、「キーペアのダウンロード」を選択して、「<キーペア名>.pem」をダウンロードしてPCの安全な場所に保存してください。

 

EC2インスタンスへの接続確認

つくったEC2インスタンスSSH接続します。

 

SSHクライアントの準備

SSHログインするためのSSHクライアントを準備しましょう。

MacとかLinux だと ssh コマンドでもいいと思います。

作った時は WIndows から接続する必要があったので、TeraTermをインストールして使いました。

TeraTermを選んだ理由は、pem 形式のファイルをそのまま使えるからです(他のソフトだと鍵ファイルの形式を変換する必要があるものがあるため)。

 

接続先情報の確認

接続先のIP、またはHost名を確認しにいきましょう

AWSのコンソールにもどりまして、「サービス」の中からEC2を選ぶか、

もしかしたら「最近アクセスしたサービス」の中にEC2があるかもしれないので、

それを選択します。

移動先のページから、「インスタンス(実行中)」を選んで、インスタンス確認画面に移動します。

 

そうすると、さきほど作ったインスタンスがあるので、以下の情報を確認します。

  • パブリック IPv4 DNS
  • パブリック IPv4 アドレス

起動時にAWS側で自動でグローバルIPと、それに関連づくドメイン名をつけてくれます。

このどちらかで接続できるようになるので確認し、後から使えるようにメモなどをしておきます。

(どちらでもつながりますが、DNS名の方を後ほど使います)

 

接続

SSH接続します。

 

WindowsTeraTerm を使う場合は、TeraTerm起動して、

ファイル -> 新しい接続 を選択、

ホストにインスタンスDNS名をいれて、サービスはSSHを選択し、OKをボタンをクリックします。

 

SSH認証ダイアログ」で以下設定

  • ユーザー名:ec2-user
  • パスワード:(空のまま)
  • 認証方式:「RSA/DSA ...」を選択し、右にある「...」を押して秘密鍵を選択。
    秘密鍵は先ほどダウンロードしたキーペアの pem ファイルを選択します。

MacLinux の場合は、~/.ssh/config に以下の設定を書いて、SSHコマンドを実行すればつながるはず。

 

Host <EC2 instance host name>
User ec2-user
IdentityFile <path/to/save/directory>/key_pear.pem

 ※Hostの値や、IdentityFile のパス名は環境に応じて変更してください。

 

Dockerの設定

EC2インスタンス上で、Dockerを動作させるための準備を進めていきます。

Docker のインストール

EC2条に Docker をインストールします。

インストールするには、(Amazon Linux の場合は)以下のコマンドから

sudo amazon-linux-extras install docker

 

外部からの Docker 接続用の設定

リモートで Docker コンテナに接続したり、Dockerを起動させるため、

Docker動作用の TCP ポートを開放します。

 

手順は以下を参考にしました。

matsuand.github.io

 

主に「daemon.json」の設定を変更する方法、「service 設定を書き換える」方法があると思うのですが、

service 設定を書き換える方法を選択しました。

以下のコマンドから、docker の service 設定を編集、

nano を使って設定を後述の用に追記します。

 

sudo systemctl edit docker.service

 

[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://<自身のIPアドレス>:2375

 

設定変更が完了したら、設定の読み込みを実行し、service を再起動します。

sudo systemctl daemon-reload

sudo systemctl restart docker.service

 

EC2インスタンスの使用ポートを確認後、設定した 2375 が解放されているか確認します。

解放されていれば設定は完了です。

 

最後に

Dockerの起動する EC2 インスタンスの作成が完了しました。

後のIDEなどからの接続方法や、追記はまたのちほどします。

mockito で static メソッドの mock を使ったテストをしたい

はじめに

お久しぶりです。

最近、いろいろバタバタしていてここの更新もすすんでませんでしたが、

アウトプットの機会を継続できたらと思い、また何か書いていこうとしています。

4月に異動になってしまい、Java を使わない部署になったのに、なぜかスポット対応でJavaを書いていました。

今日はその時に見つけた内容の話になります。

内容としては「mockito と使った、static メソッドを mock 化する方法」です。

 

static メソッドの mock 化するモチベーション

mock 化したいのは、おもにユニットテストを書く際に、依存モジュールを好きな値をかえす、mock/stub を使ってテストしたいとなると思います。

 

Java で実装を行う場合、自分たちで static メソッドを作る場合もあると思います。

自分たちで作らない場合でも外部モジュールで実装された static メソッドを使ったりする場合もあると思います。

そういったものに依存するクラスを開発することになった場合、

ユニットテストを書こうとした際、mock 化するには

どのようにすると良さそうかということを考えます。

 

powermock

方法のひとつとして、 powermock を使ったテストというのが選択肢に入ってきたと思います。

ただ、どうも JUnit5 の対応がされず、何年も時間が経過してしまっているようでした。

github.com

※類似の内容の issue が他にも複数あがっています

 

JUnit4 であれば powermock 使えるのですが、

SpringBoot の test ライブラリはデフォルトで JUnit5 になっていたり、

そもそも PowerMock 使ってないようなレポジトリーはすでに JUnit5 を使っていたりするので、

powermock 使うためだけに JUnit4 を使うというのはあまりうれしくないなと思っていました。

 

代替案としての mockito の使用

そこで JUnit5 で使えて、static メソッドを mock 化できる方法はないかと調べたところ、

mockito 3.4 以降であれば、そういうことができるようになっていました。

実際に試したところ、うまく動かすこともできたので、やった内容も記載しておきたいと思います。

 

開発環境

開発環境する際に使った環境について。

Java なのであまり開発環境には依存しないと思いますが、

JDKやビルドツールについても一応記載します。

環境は以下になります。

 

実装方法

実装方法について記載していきます。

おおまかな流れは以下の通りです。

  • ビルドツールの依存関係に mockito-inline を追加する
  • テストコードのクラス作成
  • static メソッドをもつクラスの mock 化
  • mock の挙動定義
  • テストの実装

(プロジェクトの作成や、ロジックコード側はすでにあるものと思っています。)

mockito モジュールの追加

ビルドツール(今回は gradle )に、必要なモジュールを追記します。

私の場合は build.gradle に以下を記載しました。

dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
testImplementation 'org.mockito:mockito-inline:3.10.0'
testImplementation 'org.assertj:assertj-core:3.19.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
}

test {
// Junit5 を使う設定
useJUnitPlatform()
}

ライブラリのバージョンは当時の最新版だったり、なるべく新しいのを選びました。

 

mockito-inline というモジュールが、実際に static メソッドの mock 化を行ってくれるようなので、これを取り込む必要があります。

mockito-core は指定しなくても、mockito-inline が mockito-core を引っ張ってくれるので記述しなくても問題ありません。

(何らかの理由でバージョンのオーバーライドとかしたくて記述しても動作はするとおもいます。)

 

テストコードのクラス作成~テストの実装

実際のテストを実行するクラスを作成します。

前提条件で「すでにプロジェクトのロジックコードは存在する」としましたが、

今回は以下のようなクラスがあるとしてください。

  • static メソッドを持つクラス(StaticSample)
  • static メソッドを持つクラスに依存するクラス(UseStaticSample)
  • UseStaticSample をテストするためのテストクラス(UseStaticSampleTest)

 

ロジックコードとしては以下を実装します。

  • StaticSample
import java.util.Objects;

public class StaticSample {

public static String value1() {
return "value1";
}

public static boolean isEqual(String value1, String value2) {
return !Objects.isNull(value1) && value1.equals(value2);
}
}
  • UseStaticSample
public class UseStaticSample {

private final String value;

public UseStaticSample(String value) {
this.value = value;
}

public String function(String value) {
final boolean isEquals = StaticSample.isEqual(this.value, value);
if (isEquals) {
return String.format("Arg value is equal, value =[%s]", value);
} else {
return "Not Equal";
}
}
}

 

実際に、それらのクラスをテストするUseStaticSampleTest は以下のようになります

  • UseStaticSampleTest
@SuppressWarnings("NonAsciiCharacters")
class UseStaticSampleTest {

private UseStaticSample target;
private static final String INIT_VALUE = "value";

@BeforeEach
void setUp() {
target = new UseStaticSample(INIT_VALUE);
}

@Test
final void test() {
final String argValue = "someValue";
final String expected = String.format("Arg value is equal, value =[%s]", argValue);

try(MockedStatic<StaticSample> mocked = mockStatic(StaticSample.class)) {
mocked.when(() -> StaticSample.isEqual(anyString(), anyString()))
.thenReturn(true);

String actual = target.function(argValue);

assertThat(actual).isEqualTo(expected);
}
}
}

 

これを実行すると、UseStaticSample 内で使っている、StaticSample のメソッドの挙動が書き換わり、

他のクラスに依存しないテストコードを書くことができました。

テストの実装について説明

テストの実装までの実装した内容についても触れておきたいと思います。

mock インスタンスの生成について

org.mockito.MockedStatic メソッドで、static メソッドを持つクラスの mock インスタンスを生成します。

MockedStatic<StaticSample> mocked = mockStatic(StaticSample.class)
mock インスタンスのライフサイクルについて

mockStatic() によって mock を生成した場合、最後に close() を呼んでmock を停止させる必要があります。

// create mock
MockedStatic<StaticSample> mocked = mockStatic(StaticSample.class);
mocked.when(() -> StaticSample.isEqual(anyString(), anyString()))
.thenReturn(true);

String actual = target.function(argValue);
assertThat(actual).isEqualTo(expected);

// close mock
mocked.close();

 

これをしないと、他のテストで mock 化しようとした際にエラーとなってしまうためです。

ただ、上述したような、try-with-resource の形で、

try で受けてあげれば、try 句を抜ける際にいっしょに close してくれます。

 // create mock
try(MockedStatic<StaticSample> mocked = mockStatic(StaticSample.class)) {
mocked.when(() -> StaticSample.isEqual(anyString(), anyString()))
.thenReturn(true);

String actual = target.function(argValue);
assertThat(actual).isEqualTo(expected);

// close mock
}

 

mock のふるまいの定義

mock のふるまいについて定義します。

これは通常の mock 化された mock インスタンスと同じ要領で、when や thenReturn で挙動を決めています。

その時に定義する、呼び出されるメソッドはラムダ式で記述します。

mocked.when(() -> StaticSample.isEqual(anyString(), anyString()))
.thenReturn(true);

※StaticSample.isEqual() のふるまい定義。すべての文字列を引数に受けたときに true を返してほしい。

 

ラムダ式なので、引数のない場合はメソッド参照の形で記述することもできます。

引数のない、StaticSample.value1() の場合は以下のどちらかになります

  • メソッド参照の場合
mocked.when(StaticSample::value1)
.thenReturn("anyString");
mocked.when(() -> StaticSample.value1())
.thenReturn("anyString");

 

補足

mockito でコンストラクタの mock する方法について、以下で説明しました。

keyno63.hatenablog.com

最後に

いままで static メソッドのテストコード作成には結構悩まされてきましたが、

mockito で static メソッドのテストができるようになってくれたおかげで

テストライブラリのバージョン関係やテストの記述方法に関する問題が解消できたのが

非常に大きな収穫になりました。

 

もしこの記事を見かけた方で試したことのない方は是非お試しいただければと思います。

 

追記

コンストラクタを Mockito で mock する方法についても書きました

keyno63.hatenablog.com

Intellij IDEA で Cannot load infomation for github.com/アカウント名: Request response: Bad credentials が出る時の対処方法

概要

 IntelliJ IDEA を使っている際、「Share Project On GitHub」を選択したときに以下のエラーがでる時があります。

「Cannot load infomation for github.com/アカウント名: Request response: Bad credentials」

f:id:keyno63:20210208013744p:plain

 

これの原因と対処方法について記載します。

 

なお、IntelliJ と書きましたが、Android Studio でも同じ原因でこの事象が発生します。

同じ対処方法で解決することができます。

 

原因

GitHub側で登録した token 情報を regenerate (再発行)したり、削除したりして、

IDEに登録した GitHub の認証情報が変わったりして、連携機能が動作しなくなっています。

 

対処方法

Intellij の設定にある GitHub の項目から再度 GitHub にログイン・token 登録して情報を更新します。

VCS から変更するわけではないので、このへんややこしいですね。)

 

Windowsの場合は [File] -> [Setteings] を選択し、設定ダイアログを開きます。

Mac の場合は [Preferences])

 

[VersionControl] -> [GitHub] を選択します。

右のウィンドウをみると、赤文字で「Request response: Bad credentials」が表示されていると思います。

f:id:keyno63:20210208020710p:plain

 

 

青文字で表示されている「Log in」をクリックすると、以下のダイアログが表示されると思います。

f:id:keyno63:20210208015204p:plain

 

「Log In via GitHub...」、「Use Token」のどちらかをクリックし、

再度 GitHub にログイン・認証します。

 

以上で認証の再登録が完了し、GitHub連携が復活します。

IntelliJ IDEA でつくったローカルプロジェクトを GitHub 連携

IntelliJ IDEA で GitHub 連携する

最初に

IntelliJ IDEAを使って GitHub 連携について説明します。

仕事やそれ以外でも連携方法について説明したりする場面があるのですが、

せっかくなのでまとめておこうと思って記載しています。

 

Android Studio とかでも同じように設定できそうだったので、

他の JetBrain 系 IDE でも設定できると思われます。

 

私の使っている環境は以下です。

  • Windows 10 Pro
  • Intellij IDEA Ultimate 2020.3
    Intellij IDEA Community Edition 2020.3でも確認できています)

連携すると

何ができるかについて。

たとえば、手元で新規に Intellij のプロジェクトを作ったとき、

GitHubに保存し、履歴管理できるようにしたくなったとします。

その時に行う作業のひとつとして、以下の手順を踏むことになるかと思います。

  1. ローカルプロジェクトを git 登録し、git のローカルレポジトリを作成する(git init など)
  2. GitHub にレポジトリを作成する
  3. ローカルレポジトリの remote 先に、作成した GitHub のレポジトリに設定する
  4. ローカルレポジトリを push する

これらの作業をCLIから行わずにできますし、

一回連携すると次回から簡単に行うことができるようになります。

連携済みの場合はレポジトリ作成からGitHubへのPushまでやってくれます 。

 

設定方法 

Intellij からプロジェクトを作成します。

その後、[VCS] -> [Share project on GitHub] を選択します。

ローカルレポジトリのGitHub共有のためのダイアログが開きます。

 

すでに GitHub にログインできている場合

「Share」を押すと、GitHub上にレポジトリ作成し、ローカルのレポジトリの Push が完了します。

簡単ですが、これだけです。

f:id:keyno63:20210208011654p:plain

GitHub ログインからする場合

GitHubと未連携だった場合、右下の方に「Add account」というのが見えます。

f:id:keyno63:20210208000510p:plain

 

該当箇所をクリックすると、いくつかの方法で GitHub と連携できます

  • Log In via GitHub...
    GitHub に OAuth か何かでログインする
  • Log In with Token
    GitHub で作成した token を設定してログインする
  • Log In to GitHub Enterprise...
    企業向けの別にホスティングされたGitHubにログインする
    github.com じゃないやつ)
    今回はここについては触れてません(手元で確認もとれなかったので)

 

「Log In via GutHub」で追加する場合

該当のプルダウンを選択すると、JetBrain のWebページに遷移します。

「Authorize in GitHub」というボタンがあるはずなので、

それをクリックすると GutHub と連携が完了します。

 

 

「Log In with Token」で追加する場合

 

該当のプルダウンと選択後、以下のダイアログが表示されるので、

「Generate」クリックし、GitHub上で新規の token を作成できます。

f:id:keyno63:20210208004748p:plain

token 生成後、値を入力し、「Add Account」でGitHubと連携できます。

 

GitHub上ですでに作成済みの token があれば、それを選択することもできます。

github.com

 

作成済みの token を使う場合、以前に作成したものをどこかで控えていればそのまま使えます。

控えておらず、既存の token 権限を使いたい場合は 「Regenerate token」で token の再生成ができます。

(ただし、その場合はその token を使っていた他のアプリケーションが連携できなくなる可能性があるので注意が必要です。)

 

GitHub の連携できているかの確認

設定方法じゃないですが、いまのIDEGitHub と連携できているかの確認方法についても記載します。

 

Windows の場合は [File] -> [Settings] を選択し、設定ダイアログを開きます

Mac の場合は [Preferences]だったはず)

 

[VersionControl] -> [GitHub] を選択します。

右のウィンドウに自身のGitHubアカウントが表示されていれば連携できています。

f:id:keyno63:20210208012951p:plain

最後に

プロジェクトの作成かGitHubへの登録が簡単にできるようになりました。

CLI の方が(慣れているためか)好きだったのですが、

この方法を知ってからは頼りっきりです。

 

少しでも他の人の参考になれば幸いです。