20210113のJavaに関する記事は4件です。

サイコロ占い

new java.util.Random().nextInt();を使って書いてみました。

dokojavaを使って書きました。

public class Main{
    public static void main(String[] args){
        int suuji = new java.util.Random().nextInt(6)+1;
        int suuji2 = new java.util.Random().nextInt(6)+1;
        int suuji3 = new java.util.Random().nextInt(6)+1;
        int goukei = suuji+suuji2+suuji3;
        System.out.println("サイコロの目の合計数は"+goukei);
        if(goukei == 3 || goukei == 18){
            System.out.println("今日は全てにおいて上手くいきます");
        }else if(goukei == 4 || goukei == 17){
            System.out.println("今日は恋愛運が最高です");
        }else if(goukei == 5 || goukei == 16){
            System.out.println("今日は仕事運が最高です");
        }else if(goukei == 6 || goukei == 15){
            System.out.println("今日は金運が最高です");
        }else if(goukei >= 9 && goukei <= 12 ){
            System.out.println("今日の運勢は全体的にそこそこです");
        }else{
            System.out.println("今日の運勢はイマイチです");
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

無駄に柔軟性がある FizzBuzz を作ってみた

多くのプログラマが通る有名な問題 FizzBuzz を無駄に極限まで柔軟性がある実装にしてみた
※ 最短部門ならちらほら見かけるが最柔軟部門はあんまり見かけないので

  • 方針
    • Java11
    • 標準ライブラリを積極的に使用
    • 最終的に Builder Pattern っぽいものになる

一般的な解

色々工夫すればもっと短くなるが教科書に載るような解答ならこんな感じでしょうか?

public class FizzBuzz {
    public static void main(String[] args) {
        for (int i = 1; i <= 100; i++) {
            if (i % 15 == 0) {
                System.out.println("FizzBuzz");
            } else if (i % 3 == 0) {
                System.out.println("Fizz");
            } else if (i % 5 == 0) {
                System.out.println("Buzz");
            } else {
                System.out.println(i);
            }
        }
    }
}

output (見やすくするように一行にまとめています)

1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz ...

魔改造開始

「3: Fizz」「5: Buzz」といったペアを外部化

  • Map 形式で保存
  • Stream で各数字の判定&文字列を結合を行う
Map<Integer, String> map = new HashMap<>();
map.put(3, "Fizz");
map.put(5, "Buzz");

for (int i = 1; i <= 100; i++) {
    final int current = i; // このあと消えます
    String result = map.entrySet().stream()
            .filter(v -> current % v.getKey() == 0)
            .map(Entry::getValue)
            .reduce("", (left, right) -> left + right);
    System.out.println(result.isBlank() ? i : result);
}

別メソッドに分離

  • 結果をリストとして返すメソッドに分離
  • ついでに開始と終了値もカスタマイズ可能に
public static List<String> fizzBuzz(int start, int end, Map<Integer, String> map) {
    return IntStream.rangeClosed(start, end).mapToObj(i -> {
        String result = map.entrySet().stream()
                .filter(v -> i % v.getKey() == 0)
                .map(Entry::getValue)
                .reduce("", (left, right) -> left + right);
        return result.isBlank() ? String.valueOf(i) : result;
    }).collect(Collectors.toList());
}

public static void main(String[] args) {
    Map<Integer, String> map = new HashMap<>();
    map.put(3, "Fizz");
    map.put(5, "Buzz");

    fizzBuzz(1, 100, map).forEach(System.out::println);
}

Builder pattern っぽいものにする

  • 一般的に Builder pattern における build メソッドはある独立したクラスを返すべきだが、ここでは List<String> を返すとする
    • ちゃんとやるなら多分「FizzBuzzBuilder」と「FizzBuzz」の 2 クラスに分けることになるかと
  • javadoc は割愛
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.*;

public class FizzBuzz {
    private Map<Integer, String> map = new HashMap<>();
    private int start = 1;
    private int end = 100;

    public List<String> build() {
        return IntStream.rangeClosed(start, end).mapToObj(i -> {
            String result = map.entrySet().stream()
                    .filter(v -> i % v.getKey() == 0)
                    .map(Entry::getValue)
                    .reduce("", (left, right) -> left + right);
            return result.isBlank() ? String.valueOf(i) : result;
        }).collect(Collectors.toList());
    }

    public FizzBuzz addPair(int value, String text) {
        map.put(value, text);
        return this;
    }

    public FizzBuzz start(int start) {
        this.start = start;
        return this;
    }

    public FizzBuzz end(int end) {
        this.end = end;
        return this;
    }

    public static void main(String[] args) {
        new FizzBuzz()
                .start(1)
                .end(20)
                .addPair(3, "Fizz")
                .addPair(5, "Buzz")
                .build()
                .forEach(System.out::println);
    }
}

試運転

  • せっかくなので互いに素の 3, 5, 11, 13 でテスト
  • 最小公倍数は 2145 なので 2130 ~ 2150 あたりを出力する
new FizzBuzz()
        .start(2130)
        .end(2150)
        .addPair(3, "Fizz")
        .addPair(5, "Buzz")
        .addPair(11, "Foo")
        .addPair(13, "Bar")
        .build()
        .forEach(System.out::println);

結果

FizzBuzz
2131
Bar
Fizz
Foo
Buzz
Fizz
2137
2138
Fizz
Buzz
2141
Fizz
2143
2144
FizzBuzzFooBar
2146
2147
Fizz
2149
Buzz

おまけ:JavaScript version

JavaScript のパラダイムをよくわかっていない人が作るとこうなる
本職の方が作るものと乖離しそうなのであくまで参考

(() => {
    class FizzBuzz {
        constructor() {
            this._from = 1;
            this._to = 100;
            this._pair = [];
        }
        build() {
            const convert = i => this._pair
                .filter(divisor => i % divisor.value == 0)
                .reduce((left, right) => left + right.text, "") || i;
            return [...Array(this._to - this._from + 1).keys()]
                .map(i => i + this._from)
                .map(convert);
        }
        addPair(value, text) {
            this._pair.push({value: value, text: text});
            return this;
        }
        from(from) {
            this._from = from;
            return this;
        }
        to(to) {
            this._to = to;
            return this;
        }
    };
    new FizzBuzz()
        .from(2130)
        .to(2150)
        .addPair(3, "Fizz")
        .addPair(5, "Buzz")
        .addPair(11, "Foo")
        .addPair(13, "Bar")
        .build()
        .forEach(result => console.log(result));
})();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Androidアプリで電話をかける処理の基本の基

Androidアプリで電話をかける処理の基本の基

Androidアプリで電話をかける処理をなんとなく実装していたため、改めてここに記載をし、自身の復讐をできればと思います。

※主に自身の毎日の復習・学習の機会創出、アウトプットによる知識の定着を目的としております。
暖かい目で見ていただけますと幸いです。

Androidアプリから電話をかける方法

IntentのACTION_CALLを利用する方法

IntentのACTION_CALLを利用することで、アプリ側で入力された番号を持って、電話をかけることができます。

※マニフェストで許可が必要
マニフェストでCALL_PHONEの許可が必要です。

IntentのACTION_DIALを利用する方法

IntentのACTION_DIAL利用することで、アプリ側で入力された番号を持って、ダイヤラーと電話アプリを開くことができます

※ACTION_CALLとACTION_DIALの違い
ACTION_CALL:電話がそのまますぐかかる
ACTION_DIAL:電話アプリ・ダイヤラーを開くだけ。そこから改めて電話アプリ・ダイヤラー側で発信の操作(タップ)が必要

Dangerous パーミッションの許可取得

Android 6.0らパーミッションの概念が変わり、ユーザーに許可を必要とするパーミッションと必要としないパーミッションの2種類にわかれました。

※おさらい
・Nomalパーミッション:アプリインストール時、AndroidManifest.xmlの記載を元に権限が得られる
・Dangerousパーミッション:ユーザーの許可により権限を得られる。ユーザーによって後から権限の取り消しも可能。

電話に関連するDangerousパーミッション(パーミッショングループPHONEに属するパーミッション)もありますので、用途に合わせて必要な権限を取得するよう設定しましょう。

基本的なコード

   Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:012345678"));
   startActivity(intent);

※マニフェストも許可が必要

AndroidManifest.xml
<uses-permission android:name="android.permission.CALL_PHONE" />

エラー時の処理

ダイヤログ・電話アプリへの接続権限がないなどで、繋がらない可能性があります。
この基本的なコードのままでは例外時は電話が繋がらずかつ画面上でもなんの通知もない状態となってしまい、ユーザーからはどうなっているのか全くわからない状態になってしまいます。

ですので、例外処理を追加し、例外時はユーザーに例外内容を通知するように設定しましょう。

※または、startActivityの前に権限をチェックする処理を記載する方法でも対応可(発信ボタンを押したら、まずは権限チェックして、あればstartActivityとか)

   Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:012345678"));

   //ここから例外処理
   try{
     startActivity(intent);
   }catch(e: SecurityException){
     Toast.makeText(context, "エラーです・・・・", Toast.LENGTH_SHORT).show()
   }

まとめ

・各権限を事前に取得しましょう。
・Intentを利用して電話をかけましょう。
・エラー時に備え、例外処理も実装しておきましょう。
・権限がないのに、アプリ上で発信操作をしようとするユーザーに対する対策も構築しておきましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SpringBoot + JUnit + Mockito で UnitTest

SpringBootのプロジェクトにCIを導入したくて、自動テストについて調べていたところ、いろいろなツール名が出てきて混乱したので整理がてらメモ書きを。
自動テスト初心者の記事なので嘘ついてたらごめんなさい。わざとじゃないです。

最終的にできたプロジェクトはこちら

TL;DR

  1. JUnit でテストクラスを実行する
  2. Mockito でモックを作成して依存先を置き換える
  3. private メソッドはリフレクションでテストができる
  4. Hamcrest はアサーション用のマッチャー

目次

1.用語
2.サンプルプロジェクトの作成
3.JUnitで単体テストの作成
4.Mockitoでモックを使用したテストの作成
5.privateメソッドのテスト
6.テスト結果レポート
7.おわりに

1. 用語

登場する用語を簡単に並べる

  • SpringBoot
    • Javaのフレームワーク
  • JUnit
    • Javaの単体テスト向けテストフレームワーク
    • 単体テストはこいつを使って実行されることが多い
  • Mokito
    • Javaの単体テスト向けモックフレームワーク
    • テスト対象が依存するモジュールをダミーとして置き換える
  • Report
    • テストの実行結果をみやすく表示したhtml郡
  • 依存関係
    • Class A の処理で Class B を使用している場合、Class A はClass B に依存しているといいます
  • カバレッジ
    • 自動テストでどの程度確認できているかの指標

2. サンプルプロジェクトの作成

なにはともあれ、テストを行うためのプロジェクトを作成します。
なんでもいいので Spring initializer で適当に作成しました。

key val
Project Gradle project
Language Java
Spring Boot 2.4.1
Group com.sample
Artifact testsample
Name testsample
Description Sample project
Package name com.sample.testsample
Packaging Jar
Java 11
Dependencies Spring Web
H2 Database
Spring Data JPA
Lombok

プロジェクトができたらよくあるユーザ登録風の処理を作成しましょう。
追加、変更したファイルは以下

  • testsample
    1. UserController.java
    2. UserEntity.java
    3. UserRepository.java
    4. UserService.java
  • resources
    1. application.properties
    2. schema.sql

src部分のディレクトリ構成はこんな感じ

src
├── main
│   ├── java
│   │   └── com
│   │       └── sample
│   │           └── testsample
│   │               ├── TestsampleApplication.java
│   │               ├── UserController.java
│   │               ├── UserEntity.java
│   │               ├── UserRepository.java
│   │               └── UserService.java
│   └── resources
│       ├── application.properties
│       ├── schema.sql
│       ├── static
│       └── templates
└── test
    └── java
        └── com
            └── sample
                └── testsample
                    └── TestsampleApplicationTests.java

ファイルの内容

UserController.java
package com.sample.testsample;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDate;

@RestController
public class UserController {
    @Autowired
    UserService userService;

    @RequestMapping(value="/index", method = RequestMethod.GET)
    public String index() {
        return "This page is user page";
    }

    @RequestMapping(value="/add", method = RequestMethod.POST)
    public String add(@RequestParam String name,
                      @DateTimeFormat(pattern = "yyyy-MM-dd")
                      @RequestParam LocalDate birthday) {
        userService.addUser(name, birthday);
        return "Success!!";
    }
}
UserEntity.java
package com.sample.testsample;

import lombok.Data;

import javax.persistence.*;

@Data
@Entity
@Table(name = "user")
public class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String name;
    private Long age;
}

※ Lombok の @Data が動かない場合、手動でアクセサを追加してもOK

UserRepository.java
package com.sample.testsample;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<UserEntity, Integer> {
}
UserService.java
package com.sample.testsample;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

@Service
public class UserService {
    @Autowired
    UserRepository userRepository;

    public void addUser(String name, LocalDate birthday) {
        // User entity の作成
        UserEntity entity = makeUserEntity(name, birthday);
        // 保存
        userRepository.save(entity);
    }

    private UserEntity makeUserEntity(String name, LocalDate birthday) {
        // User entity の作成
        UserEntity entity = new UserEntity();
        entity.setName(name);
        // 年齢の算出・設定
        LocalDate now = LocalDate.now();
        Long age = ChronoUnit.YEARS.between(birthday, now);
        entity.setAge(age);
        return entity;
    }
}
application.properties
spring.datasource.url=jdbc:h2:./test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=testsample
spring.datasource.password=testsample
spring.datasource.sql-script-encoding=UTF-8
spring.datasource.initialization-mode=always
spring.datasource.schema=classpath:schema.sql
schema.sql
DROP TABLE user;
CREATE TABLE user (
  id INTEGER NOT NULL AUTO_INCREMENT,
  name VARCHAR(256) NOT NULL,
  age INTEGER NOT NULL,
  PRIMARY KEY (id)
);

さて、ここまでで登録処理ができたので次にテストを作っていきましょう

3. JUnitで単体テストの作成

テストクラスは test パッケージ配下に xxxTests.java の命名規則で作成していきます。
今回の場合ロジックは UserService.java にあるので、これをテストしていきたいので UserServiceTests.java を追加します。
まずは簡単にテストできそうな calckAge() のテストから実装していきましょう。

UserServiceTests.java
package com.sample.testsample;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.time.LocalDate;

public class UserServiceTests {
    @Test
    public void 年齢算出のテスト() {
        UserService service = new UserService(null);
        LocalDate date = LocalDate.of(2000, 01, 01);
        Long age = service.calcAge(date);
        Assertions.assertEquals(21l, age);
    }
}

2021/1/11 現在、このテストは成功しますが問題が一つありますね。
来年になるとテストが失敗してしまいます :scream:
解決策としていくつか考えてみます...

  1. calcAge に LocalDate の引数を追加して差分を算出する
  2. 日付を取得するサービスを経由するような構成にして Interface を活用して日付を取得する
  3. 日付を取得するサービスを経由するような構成にしてモックを活用してテスト用に値を上書きする

などなど、いくつか考えられますが、 2 か 3 の手段が色々とやりやすいようですので、今回は 3 の手段を採用します。

4. Mockitoでモックを使用したテストの作成

日付取得用のサービス DateUtils.java を追加して、 UserServiceUserServiceTests を書き換えましょう

DateUtils.java
package com.sample.testsample;

import org.springframework.stereotype.Component;

import java.time.LocalDate;

@Component
public class DateUtils {
    public LocalDate getNowDate() {
        return LocalDate.now();
    }
}
UserService.java
 package com.sample.testsample;

 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;

 import java.time.LocalDate;
+import java.time.temporal.ChronoUnit;

 @Service
 public class UserService {
+    private final DateUtils dateUtils;
     private final UserRepository userRepository;

     @Autowired
     public UserService(UserRepository userRepository
+        , DateUtils dateUtils) {
         this.userRepository = userRepository;
+        this.dateUtils = dateUtils;
     }

...

 public Long calcAge(LocalDate birthday) {
-        LocalDate now = LocalDate.now();
-        Long age = ChronoUnit.YEARS.between(birthday, now);
+        Long age = ChronoUnit.YEARS.between(birthday, dateUtils.getNowDate());
         return age;
     }
 }
UserServiceTests.java
 package com.sample.testsample;

 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;

 import java.time.LocalDate;

 public class UserServiceTests {
     @Test
     public void 年齢算出のテスト() {
+        // Mockの作成
+        DateUtils dateUtils = Mockito.mock(DateUtils.class);
+        Mockito.when(dateUtils.getNowDate()).thenReturn(LocalDate.of(2021, 1, 11));

-        UserService service = new UserService(null);
+        UserService service = new UserService(null, dateUtils);
         LocalDate date = LocalDate.of(2000, 01, 01);
         Long age = service.calcAge(date);
         Assertions.assertEquals(21l, age);
     }
 }

Mockito.mock()DateUtils と同じメソッドを持ったモックを作成します。
ただし、モックですので呼び出したメソッドは null を返却してしまいます。
そこで Mockito.when()thenReturn() を使って dateUtils.getNowDate() の戻り値を上書きしてやります。
そうすることでこのテストは現在日付によらず成功を確認できるわけですね。

ポイントとしては UserService クラスの依存関係をコンストラクタインジェクションで解決させること。そうすることでモックの注入がしやすくなって幸せです :blush:

さて、次に UserEntity 作成メソッドのテストを作っていきましょう。

5. privateメソッドのテスト

UserEntitiymakeUserEntity() で作成されるのですが、このメソッドのアクセス修飾子は private です。
そのままでは呼び出しができないので、リフレクションを使用してテストを作成していきましょう。

UserServiceTests にテストケースを追加しましょう。

UserServiceTests.java
 public class UserServiceTests {
 /* write other test cases */
+    @Test
+    public void ユーザエンティティ作成のテスト() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        // Mockの作成
+        DateUtils dateUtils = Mockito.mock(DateUtils.class);
+        Mockito.when(dateUtils.getNowDate()).thenReturn(LocalDate.of(2021, 1, 11));
+        // reflection で private メソッドを取得
+        UserService service = new UserService(null, dateUtils);
+        Method method = service.getClass().getDeclaredMethod("makeUserEntity", String.class, LocalDate.class);
+        method.setAccessible(true);
+        UserEntity entity = (UserEntity) method.invoke(service, "Richter", LocalDate.of(2000, 1, 1));
+        // 結果の比較
+        Assertions.assertEquals(null, entity.getId());
+        Assertions.assertEquals("Richter", entity.getName());
+        Assertions.assertEquals(21l, entity.getAge());
+    }
 }

private メソッドのテスト方法はこちらを参考にさせていただきました。大感謝! :kissing_closed_eyes:
Junitでprivateメソッドのテスト方法 - Qiita

6. テスト結果レポート

さて、現在はサンプルなのでこの程度しかテストケースがありませんが、実際のシステムはもっとたくさんのテストケースが実行されていることも多いでしょう。
テストの結果をみやすく表示するためにテスト結果のレポートを確認してみましょう.
テストを実行すると
<project_root>/build/reports/tests/test/index.html
にテスト結果のレポートが作成されていますので確認してみましょう。

7. おわりに

以上でとても簡単ですが SpringBoot アプリケーションに JUnit でのテストを組み込んでみました。
誰かのはじめの一歩のサポートになれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む