Slide 1

Slide 1 text

Unitテストの基礎を理解する 株式会社イーディーエー システム開発部 瓜生 遥輝

Slide 2

Slide 2 text

自己紹介 瓜生 遥輝 / Uriu Haruki 株式会社イーディーエー / Flutter Engineer Qiitaアカウント: https://qiita.com/haru-qiita 趣味: フットサルとサッカー観戦

Slide 3

Slide 3 text

目次 01 Unitテストの基本 02 Widgetテストを学ぶ 03 単一責任に焦点を当てる

Slide 4

Slide 4 text

01 Unitテストの基本

Slide 5

Slide 5 text

テストコード実行手順 パッケージを追加 テストコードを格納するディレクトリを作成 テストコードの実装 テスト実行

Slide 6

Slide 6 text

my_project/ ├── lib/ │ ├── models/ │ │ └── user.dart │ ├── services/ │ │ └── user_service.dart │ └── utils/ │ └── string_utils.dart └── test/ ├── models/ │ └── user_test.dart ├── services/ │ └── user_service_test.dart └── utils/ └── string_utils_test.dart

Slide 7

Slide 7 text

$ flutter test 00:02 +3: All tests passed! 実装が完了した後は、flutter test をターミナルで 実行して、テスト結果を確認します。 00:02 : テストが実行された総時間 +3 : テストが成功したことを 示す値、成功であれば`+` 失敗した 場合は `-` が表示されます。 All tests passed! : すべてのテストが成功したというメッ セージです。 プロジェクト内のすべてのテストケース が正常に実行されていることを表して います。 失敗 成功

Slide 8

Slide 8 text

基本メソッド ● testOn ● timeout ● skip ● tags ● onPlatform ● retry test() expect() その他 ● reason ● skip ● matcher ● group() ● setUp() ● setUpAll() ● tearDown() ● tearDownAll()

Slide 9

Slide 9 text

test() テストケースを定義するためのメソッドです test('subtract', () { // Arrange const a = 8; const b = 3; // Act final result = mathOperations.subtract(a, b); // Assert expect(result, equals(5)); }); } body description 必須のプロパティは、以下の2つになります。 ● description: テストケースの説明を文字列で指定します。 この説明はテスト結果の表示やテスト実行時のログに 使用されます ● body: テストの本体であるテストコードを記述します。

Slide 10

Slide 10 text

expect() ユニットテストでアサーションを行うためのメソッドです。必須のプロパティは、以下の 2つになります。 ● actual: テストで得られた実際の結果や値を指定します。 ● matcher: 期待する結果を表すマッチャーを指定します。 expect(actual, matcher);

Slide 11

Slide 11 text

"matcher"とは、期待する結果を表すためのオブジェクトです。 matcher を使用することで、テストケースで得られた実際の結果と期待する 結果を比較し、アサーションを行うことができます。 なぜ matcherを使用するのか? 1. 複雑な条件をチェック 2. テストの可読性とメンテナンス性の向上 3. より詳細なエラーメッセージ matcher

Slide 12

Slide 12 text

matcher メソッド 機能 equals マッチャー 2つのオブジェクトが等しいことを確認します。 isTrue マッチャー 真偽値が true であることを確認します。 isFalse マッチャー 真偽値が false であることを確認します。 isNull マッチャー 値が null であることを確認します。 contains マッチャー コレクションが指定した要素を含んでいることを確認します。 isA マッチャー オブジェクトが指定した型であることを確認します。 throwsA マッチャー 指定した例外をスローすることを確認します。 hasLength マッチャー コレクションが指定した長さであることを確認します。

Slide 13

Slide 13 text

// equals マッチャー test('equals sample', () { int actual = 5; int expected = 5; expect(actual, equals(expected)); }); // isTrue・isFalse マッチャー test('isTrue・isFalse sample', () { bool condition = true; expect(condition, isTrue); }); // isNull マッチャー test('isNull sample', () { String? value; expect(value, isNull); }); // contains マッチャー test('contains sample', () { List numbers = [1, 2, 3, 4, 5]; int value = 3; expect(numbers, contains(value)); });

Slide 14

Slide 14 text

isA マッチャー オブジェクトが指定した型で あることを確認します。 // isA マッチャー test('isA sample', () { expect('Hello', isA()); }); throwsA マッチャー 指定した例外をスローすることを確認 します。 // throwsA マッチャー test('throwsA sample', () { expect(() => throw Exception('TestException'), throwsA(const TypeMatcher())); }); // hasLength マッチャー test('hasLength sample', () { expect('Hello', hasLength(5)); }); // isEmpty マッチャー test('isEmpty sample', () { List emptyList = []; expect(emptyList, isEmpty); });

Slide 15

Slide 15 text

setUp() 各テストケースの前に共通の処理を 実行するために使用されます。 tearDown() 各テストケースの後に共通の処理を 実行するために使用されます。 setUpAll() テストグループ内のすべての テストの開始前に1回だけ 実行されます。 tearDownAll() テストグループ内のすべての テストの終了後に1回だけ 実行されます。

Slide 16

Slide 16 text

Unitテストの書き方 Unitテストコードを書く際は、 AAAパターン (Arrange-Act-Assert) という パターンを遵守して実装します。 AAA パターンとは、テストコードを Arrange(準備)、Act(実行)、Assert(確認)の3 つのフェーズに分けて書くことを言います。 AAAパターンを採用することで、テストが 明確になり、テストコードが読みやすくなります。

Slide 17

Slide 17 text

具体例として、加算(足し算)・減算(引き算)の例を 考えてみましょう。 右のコードは足し算・引き算の関数を 定義した MathOperations クラスです。 class MathOperations { int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } }

Slide 18

Slide 18 text

import 'package:flutter_test/flutter_test.dart'; import 'package:tester/main.dart'; void main() { late MathOperations mathOperations; setUp(() { mathOperations = MathOperations(); }); test('add', () { // Arrange const a = 5; const b = 3; // Act final result = mathOperations.add(a, b); // Assert expect(result, equals(8)); }); test('subtract', () { // Arrange const a = 8; const b = 3; // Act final result = mathOperations.subtract(a, b); // Assert expect(result, equals(5)); }); }

Slide 19

Slide 19 text

02 Widgetテストを学ぶ

Slide 20

Slide 20 text

Widgetテストとは、UIコンポーネント(ウィジェット)が期待通りに 描画されていることを検証するためのテストです。 Unitテスト 単一の関数や メソッドを対象に ビジネスロジックを 検証します。 Integrationテスト アプリ全体の動作を 検証しウィジェット間の連携 や外部サービスとの統合を 検証します。 Widgetテスト 単一または複数の ウィジェットを対象に UIのレンダリングと 動作を検証します。 Widgetテストとは

Slide 21

Slide 21 text

メソッド 機能 pumpWidget 指定されたウィジェットをレンダリングします pump フレームを進めてウィジェットツリーを再描画します tap 特定のウィジェットをタップします enterText 特定のテキストフィールドにテキストを入力します drag 特定のウィジェットをドラッグします longPress 特定のウィジェットを長押しします 基本メソッド

Slide 22

Slide 22 text

メソッド 機能 find.text 特定のテキストを持つウィジェットを検索します find.byType 指定されたタイプ(クラス)のウィジェットを検索します find.byKey 特定のキーを持つウィジェットを検索します find.byIcon 特定のアイコンを持つウィジェットを検索します find.byWidget 特定のウィジェットインスタンスを検索します find.byTooltip 特定のツールチップテキストを持つウィジェットを検索します Finderメソッド Finderメソッドとは、テスト環境でレンダリングされたWidgetツリーの中から特定の Widgetを見つけるためのメソッドです。

Slide 23

Slide 23 text

findsOneWidget 1つのウィジェットが見つかる ことを確認するために使用します。 findsNothing ウィジェットが見つからない ことを確認するために使用します。 findsWidgets 2つ以上のウィジェットが見つかること を確認するために使用します。 findsNWidgets 指定された数のウィジェットが 見つかることを確認するために 使用します。

Slide 24

Slide 24 text

testWidgets('Counter increments Widget test', (WidgetTester tester) async { // Arrange: テストデータの準備 await tester.pumpWidget(MyApp()); // 初期状態の検証 expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); // Act: テストの実行 await tester.tap(find.byIcon(Icons.add)); await tester.pump(); // Assert: テスト結果の検証 expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); }); 1. Arrange(準備) テスト対象のウィジェットをレンダリングし、 初期状態を設定します。 2. Act(実行) tester.tapメソッドを使って、フローティングアク ションボタン(FAB)をタップし、その後 tester.pump メソッドを使ってウィジェットツリーを再描画します。 3. Assert(検証) 期待される結果が得られているかを確認します。 expectメソッドを使って、テキスト '0'が見つからず、 テキスト'1'が見つかることを確認します。

Slide 25

Slide 25 text

03 単一責任に焦点を当てる

Slide 26

Slide 26 text

単一責任原則 (Single Responsibility Principle) とは Robert C. Martinの定義  "Every software component should have one and only one responsibility." 各クラスや関数が一つの責任を持つ状態 理解しやすい && 保守しやすいコード

Slide 27

Slide 27 text

なぜ単一責任原則 (SRP) が重要なのか? ・コードのモジュール化・テストの簡易化 ・バグの特定と修正が容易 ・コードの再利用性の向上 変更に強いコード 変更が必要な時に影響範囲が小さい

Slide 28

Slide 28 text

class UserManager { final UserRepository _userRepository; final AnalyticsService _analyticsService; UserManager(this._userRepository, this._analyticsService); Future signIn(String username, String password) async { User user = await _userRepository.signIn(username, password); _analyticsService.trackSignIn(user.id); } Future signOut() async { await _userRepository.signOut(); _analyticsService.trackSignOut(); } } <問題点> ・UserManager クラスが UserRepository と AnalyticsService の両方の責任を持っている。 ・signIn と signOut のテストが難しくなる。 SRPが遵守されていないコード

Slide 29

Slide 29 text

class AuthService { final UserRepository _userRepository; AuthService(this._userRepository); Future signIn(String username, String password) { return _userRepository.signIn(username, password); } Future signOut() { return _userRepository.signOut(); } } class AnalyticsManager { final AnalyticsService _analyticsService; AnalyticsManager(this._analyticsService); void trackSignIn(String userId) { _analyticsService.trackSignIn(userId); } void trackSignOut() { _analyticsService.trackSignOut(); } } <改善点> ・AuthService と AnalyticsManager という 2つのクラスに分割。 ・それぞれのクラスが単一の責任を持つ。 SRPが遵守されたコード

Slide 30

Slide 30 text

まとめ 01. Unitテストの基本  - 単一の関数やメソッドをテストする  - 入力値と期待する出力値を用意する Arrange(準備)、Act(実行)、Assert(確認)の3ステップ  - matcherを用いてAssert (確認) を実施する 03. 単一責任に焦点を当てる  - 1つのテストケースで1つの振る舞いのみをテストする  - テストケースが複数の責任を持つと、保守性が低下する  - テストの意図が明確になり、他の開発者が理解しやすくなる 02. Widgetテストを学ぶ  - 他のテスト(UnitTest、IntegrationTest)と比較して、UIにフォーカスしたテスト  - Widgetテストとは、個々のウィジェットのレイアウトや動作を検証するテスト  - Finderとmatcherを使用してウィジェットの存在と状態を確認

Slide 31

Slide 31 text

ご清聴ありがとうございました 詳細情報: https://qiita.com/haru-qiita/items/7a539e6bee0a175bd94f