- 投稿日:2021-01-13T17:55:21+09:00
サイコロ占い
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("今日の運勢はイマイチです"); } } }
- 投稿日:2021-01-13T12:14:53+09:00
無駄に柔軟性がある 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)); })();
- 投稿日:2021-01-13T11:03:51+09:00
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を利用して電話をかけましょう。
・エラー時に備え、例外処理も実装しておきましょう。
・権限がないのに、アプリ上で発信操作をしようとするユーザーに対する対策も構築しておきましょう。
- 投稿日:2021-01-13T01:17:39+09:00
SpringBoot + JUnit + Mockito で UnitTest
SpringBootのプロジェクトにCIを導入したくて、自動テストについて調べていたところ、いろいろなツール名が出てきて混乱したので整理がてらメモ書きを。
自動テスト初心者の記事なので嘘ついてたらごめんなさい。わざとじゃないです。最終的にできたプロジェクトはこちら
TL;DR
- JUnit でテストクラスを実行する
- Mockito でモックを作成して依存先を置き換える
- private メソッドはリフレクションでテストができる
- 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
- UserController.java
- UserEntity.java
- UserRepository.java
- UserService.java
- resources
- application.properties
- 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.javapackage 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.javapackage 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
が動かない場合、手動でアクセサを追加してもOKUserRepository.javapackage com.sample.testsample; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface UserRepository extends JpaRepository<UserEntity, Integer> { }UserService.javapackage 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.propertiesspring.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.sqlschema.sqlDROP 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.javapackage 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 現在、このテストは成功しますが問題が一つありますね。
来年になるとテストが失敗してしまいます
解決策としていくつか考えてみます...
- calcAge に LocalDate の引数を追加して差分を算出する
- 日付を取得するサービスを経由するような構成にして Interface を活用して日付を取得する
- 日付を取得するサービスを経由するような構成にしてモックを活用してテスト用に値を上書きする
などなど、いくつか考えられますが、 2 か 3 の手段が色々とやりやすいようですので、今回は 3 の手段を採用します。
4. Mockitoでモックを使用したテストの作成
日付取得用のサービス
DateUtils.java
を追加して、UserService
とUserServiceTests
を書き換えましょうDateUtils.javapackage com.sample.testsample; import org.springframework.stereotype.Component; import java.time.LocalDate; @Component public class DateUtils { public LocalDate getNowDate() { return LocalDate.now(); } }UserService.javapackage 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.javapackage 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
クラスの依存関係をコンストラクタインジェクションで解決させること。そうすることでモックの注入がしやすくなって幸せですさて、次に
UserEntity
作成メソッドのテストを作っていきましょう。5. privateメソッドのテスト
UserEntitiy
はmakeUserEntity()
で作成されるのですが、このメソッドのアクセス修飾子は private です。
そのままでは呼び出しができないので、リフレクションを使用してテストを作成していきましょう。
UserServiceTests
にテストケースを追加しましょう。UserServiceTests.javapublic 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 メソッドのテスト方法はこちらを参考にさせていただきました。大感謝!
Junitでprivateメソッドのテスト方法 - Qiita6. テスト結果レポート
さて、現在はサンプルなのでこの程度しかテストケースがありませんが、実際のシステムはもっとたくさんのテストケースが実行されていることも多いでしょう。
テストの結果をみやすく表示するためにテスト結果のレポートを確認してみましょう.
テストを実行すると
<project_root>/build/reports/tests/test/index.html
にテスト結果のレポートが作成されていますので確認してみましょう。7. おわりに
以上でとても簡単ですが SpringBoot アプリケーションに JUnit でのテストを組み込んでみました。
誰かのはじめの一歩のサポートになれば幸いです。