こんにちは。JMDCインシュアランス本部ソリューション部の宮田です。
今年、JMDCではアドベントカレンダーに参加しています。 qiita.com 本記事はJMDCアドベントカレンダー 17日目の記事になります。
はじめに
JMDCではiOS/Android向けのPHRアプリの開発を担当しています。JMDCに入社して半年が経ち、その間モバイルアプリの新規開発を担当したので、ヘルスケアのドメイン知識に触れることができました。今まで経験していたドメイン領域と異なるヘルスケアの領域でテスト駆動開発を意識した単体テストを考えることで、さらにヘルスケアに対する理解が深められたらと思い、今回Dartのサンプルコードをもちいてヘルスケアの単体テストについて記載してみます。
単体テストを考える重要性について
Flutterには下記の粒度でテストが準備されています。
- unit test
- widget test
- integration test
unit testは単一の機能、関数、クラスをテストし、それらの正確性について担保するものです。
高速でコストも低く抑えることができ不具合の早期発見や品質向上に貢献します。
テスト対象ではない、外部依存対象についてはモックを使用してテストをすることがあります。
外部依存についてはデータの永続化層との通信等になりますが、Flutterのunit testにはこれらのテストをするケースが組み込まれていないので
mockitoなどのパッケージを使った例が公式のドキュメントにも記載されています。
また、unit testはソフトウェアが存在する価値をもつ重要な領域を重点的にテストする必要があるため、
ドメイン領域のunit testを書いたり読んだりすることはドメイン理解につながります。
unit testはFlutterプロジェクトを作成した際にデフォルトで作成され、実行するのにあたって追加の設定は不要です。
ただ、unit testだけを作成していれば良いというものでもなく、ユーザーから見てシステムが要件を満たしているかを確認するには、widget testやintegration testが必要になります。これらはUIの変更などに強く影響を受けるのでメンテナンスコストがかかりますが、コードが変更された際に自動でテストされる環境を構築することで、変更後のバグ発生を検知することができ、安心感をもって機能追加やリファクタリングを行えるというメリットがあります。
今回は詳しく触れませんが、Flutterの公式にwidget testやintegration testのドキュメントも豊富に準備されています。 integration testについては新規開発で挑戦してみましたが、WebViewの使用している部分のテストができなかったり、やはり作成するのに時間がかかることもあって思うように進んでいません。 Flutterのintegration test以外にもE2Eのテストツール等も積極的に調査していきたいと思います!
ヘルスケアに関わるクラス構成で単体テストを作成
Flutterのテストと単体テストの重要性について触れられましたので、ここからヘルスケアに関わるクラスで単体テストを作成していきます。
今回は摂取カロリーと消費カロリーの差分処理の単体テストを作成します。
テスト駆動開発に則って、先に単体テストを記載し後からコードを作成してみます。
摂取カロリーは食べ物に含まれるタンパク質/脂質/炭水化物から求め、消費カロリーはウォーキングの分数で求めるようにし、それぞれの差分が0になることを期待する単体テストとして必要になりそうなことを挙げてみます。
- 摂取カロリークラスは、タンパク質/脂質/炭水化物からカロリーを計算できること
→タンパク質/脂質/炭水化物を持つFoodクラスを作成し、摂取カロリークラスでカロリーを計算する。 - 消費カロリークラスは、分数からウォーキングの消費カロリーが計算できること
- カロリークラスは、二つのカロリーの差分が計算できること
この要件を満たすサンプルテストコードを下記に記載しました。 おにぎり一個のPFCから摂取カロリーを計算し、66分のウォーキングの消費カロリーと同一であるというテストを記載しています。
サンプルテストコード
test/calorie_test.dart
import 'package:flutter_test/flutter_test.dart'; void main() { test('calorie diff test', () { final food = Food(protein: 5.0, fat: 2.0, carbohydrate: 40); final intakeCalorie = IntakeCalorie.fromFood(food); final consumedCalorie = ConsumedCalorie.fromWalking(min: 66); expect(intakeCalorie.diff(consumedCalorie), 0); }); }
このままでは、クラスやメソッドが実装されていないエラーとなってしまうので、別のファイルに必要なクラスとメソッドを定義しました。
サンプルコード
lib/calorie.dart
abstract class Calorie { final double calorie; Calorie(this.calorie); double diff(Calorie other) { return calorie - other.calorie; } } class ConsumedCalorie extends Calorie { ConsumedCalorie._(super.calorie); factory ConsumedCalorie.fromWalking({required int min}) { final double cal = 3.0 * min; return ConsumedCalorie._(cal); } } class IntakeCalorie extends Calorie { final Food food; IntakeCalorie._(this.food, double calorie) : super(calorie); factory IntakeCalorie.fromFood(Food food) { final double cal = (food.protein * 4.0) + (food.fat * 9.0) + (food.carbohydrate * 4.0); return IntakeCalorie._(food, cal); } } class Food { final double protein; final double fat; final double carbohydrate; Food({required this.protein, required this.fat, required this.carbohydrate}); }
この状態でテストを実行すると、無事テストが成功しました!
簡単な消費カロリーと摂取カロリーの差分を求める処理でしたが、これだけのテストコードでも調査するなかで色々なことがわかりました。
- 脂質はタンパク質と炭水化物より二倍以上のカロリーがある。
- ウォーキングの消費カロリーは3kcal/分として計算したが、体重やペース(強度)によって消費カロリーは変わる。
- ウォーキングの消費カロリーは分数だけではなく、歩数や身長(歩幅の計算に必要)からも求めることができる。
- 消費カロリーには身体活動以外に基礎代謝や食事誘発性熱産生がある。
今回の入力は摂取カロリーはPFC、消費カロリーはウォーキングの分数からのみでしたが、
特に消費カロリーは別の作成方法が追加される可能性が高いと感じました。
例えば、下記が挙げられます。
- ヘルスコネクトやヘルスキット経由のデータを使用する。
- 取得した歩数から消費カロリーを作成する。
- ウォーキング以外にランニングや水泳を追加する。
- ウォーキングの消費カロリー算出を正確にするために身長や体重を計算式に追加する。
このようにドメイン層は変更され、ブラッシュアップされていく傾向があるためなるべくUI層とは分離して作成されることが重要になります。
この点でも単体テストを行う重要性が確認できました。
まとめ
簡単な単体テストを作成することを通して、ヘルスケア領域の知識の習得と単体テストの重要性の再確認を行いました。
JMDCではこのようにアプリから得られたデータ以外も大量に集積しており、「健康で豊かな人生をすべての人に」を企業理念に掲げ、医療・ヘルスケア分野のさまざまな社会課題の解決に取り組んでいます。
データ提供者のデータはセキュリティ観点からも厳重に管理しています。JMDCが提供しているモバイルアプリは利用できる方が限られているものもありますが、安心してご利用いただけます。積極的に活用して頂けますと幸いです!
明日18日目は、崔さんによる「DICOMデータ匿名化」です。お楽しみに!
JMDCでは、ヘルスケア領域の課題解決に一緒に取り組んでいただける方を積極採用中です!
フロントエンド /バックエンド/ データベースエンジニア等、様々なポジションで募集をしています。詳細は下記の募集一覧からご確認ください。
hrmos.co
まずはカジュアルにJMDCメンバーと話してみたい/経験が活かせそうなポジションの話を聞いてみたい等ございましたら、下記よりエントリーいただけますと幸いです。 hrmos.co
★最新記事のお知らせはぜひ X(Twitter)、またはBlueskyをご覧ください!
Tweets by jmdc_tech twitter.com
bsky.app