Slide 1

Slide 1 text

© Findy Inc. 2025.11 Findy AI Meetup ⽣成AIが出⼒するテストコードのリアル よくあるコードと改善のヒント 1 ファインディ株式会社 テックリードマネージャー ⼾⽥ 千隼 @starfish0206

Slide 2

Slide 2 text

© Findy Inc. 2 ⽣成AI時代のテストコードの役割

Slide 3

Slide 3 text

© Findy Inc. 3 暴⾛を防ぐためのガードレール ● テストコードが持つ役割 ○ 仕様を把握するための情報源 ○ 暴⾛しないためのガードレール ● ⽣成AIが出⼒したコードが原因で既存のテストコードが失敗した場合 ○ エラー内容を元に⽣成AIが実装または、テストコードを修正する ○ エラーの原因がどこにあるのかの判断を間違えないことがポイント ● 「何が正しいのか」を⽣成AIが理解することで、出⼒内容の⽅向性がズレないようにする

Slide 4

Slide 4 text

© Findy Inc. 4 ⽣成されるテストコードの実状と改善⽅法

Slide 5

Slide 5 text

© Findy Inc. 不要な新規ケースが追加される 5

Slide 6

Slide 6 text

© Findy Inc. 6 どんどんテストケースが追加されてしまう ● 既存のテストケースに⼿を加えればいいだけなのに、新しいテストケースが追加されてし まう ○ モデルなどに項⽬追加する時に⾒られる ● テストケースの名称がポイントになる

Slide 7

Slide 7 text

© Findy Inc. 7 class TestUserModel: def test_user_attributes(self): user = User( name="Test User", is_deleted=True ) assert user.name == "Test User" assert user.is_deleted is True def test_user_default_is_deleted(self): user = User( name="Test User", ) assert user.is_deleted is False

Slide 8

Slide 8 text

© Findy Inc. 8 class TestUserModel: def test_user_attributes(self): user = User( name="Test User", role="admin", is_deleted=True ) assert user.name == "Test User" assert user.role == "admin" assert user.is_deleted is True def test_user_default_is_deleted(self): user = User( name="Test User", ) assert user.is_deleted is False def test_user_default_role(self): user = User( name="Test User", ) assert user.role == "member"

Slide 9

Slide 9 text

© Findy Inc. 9 class TestUserModel: def test_user_attributes(self): user = User( name="Test User", role="admin", is_deleted=True ) assert user.name == "Test User" assert user.role == "admin" assert user.is_deleted is True def test_user_default_is_deleted(self): user = User( name="Test User", ) assert user.is_deleted is False def test_user_default_role(self): user = User( name="Test User", ) assert user.role == "member"

Slide 10

Slide 10 text

© Findy Inc. 10 class TestUserModel: def test_user_attributes(self): user = User( name="Test User", role="admin", is_deleted=True ) assert user.name == "Test User" assert user.role == "admin" assert user.is_deleted is True def test_user_default_attributes(self): user = User( name="Test User", ) assert user.role == "member" assert user.is_deleted is False

Slide 11

Slide 11 text

© Findy Inc. 11 class TestUserModel: def test_user_attributes(self): user = User( name="Test User", role="admin", is_deleted=True ) assert user.name == "Test User" assert user.role == "admin" assert user.is_deleted is True def test_user_default_attributes(self): user = User( name="Test User", ) assert user.role == "member" assert user.is_deleted is False まずは既存のテストコードを⾒直す

Slide 12

Slide 12 text

© Findy Inc. テストを通すためのテスト 12

Slide 13

Slide 13 text

© Findy Inc. 13 無理やりテストを通そうとする ● テストを通すための修正をやりがち ○ テストが通るまでトライアンドエラーを繰り返す ○ ⾊々試している流れでテストが通ったら、それが正解だと判断されてしまう ● 適切にmockを使いましょう

Slide 14

Slide 14 text

© Findy Inc. 14 class TestEmailService: def test_create_invite_mail_body(self): to_email = "[email protected]" invite_link = "https://example.com/invite/12345" text_body = email_service.create_invite_mail_body(to_email, invite_link) assert f"{to_email}様" in text_body assert invite_link in text_body assert "このリンクの有効期限は " in text_body assert "です" in text_body

Slide 15

Slide 15 text

© Findy Inc. 15 class TestEmailService: def test_create_invite_mail_body(self): to_email = "[email protected]" invite_link = "https://example.com/invite/12345" text_body = email_service.create_invite_mail_body(to_email, invite_link) assert f"{to_email}様" in text_body assert invite_link in text_body assert "このリンクの有効期限は " in text_body assert "です" in text_body - 有効期限の⽇時の確認が漏れている - 意図しない⽂字列が⼊っていても通っ てしまう

Slide 16

Slide 16 text

© Findy Inc. 16 class TestEmailService: def test_create_invite_mail_body(self, monkeypatch): mock_datetime = MagicMock() mock_datetime.now.return_value = datetime(2025, 11, 1, 0, 0, 0, tzinfo=UTC) monkeypatch.setattr("src.service.email.datetime", mock_datetime) to_email = "[email protected]" invite_link = "https://example.com/invite/12345" text_body = email_service.create_invite_mail_body(to_email, invite_link) assert text_body == f"{to_email}様\n{invite_link}\nこのリンクの有効期限は 2025/11/15 です。"

Slide 17

Slide 17 text

© Findy Inc. 17 class TestEmailService: def test_create_invite_mail_body(self, monkeypatch): mock_datetime = MagicMock() mock_datetime.now.return_value = datetime(2025, 11, 1, 0, 0, 0, tzinfo=UTC) monkeypatch.setattr("src.service.email.datetime", mock_datetime) to_email = "[email protected]" invite_link = "https://example.com/invite/12345" text_body = email_service.create_invite_mail_body(to_email, invite_link) assert text_body == f"{to_email}様\n{invite_link}\nこのリンクの有効期限は 2025/11/15 です。" ケースに応じたmockの使⽤⽅法を カスタムインストラクションなどに 追記

Slide 18

Slide 18 text

© Findy Inc. システムを守るテスト 18

Slide 19

Slide 19 text

© Findy Inc. 19 現状は問題ないが、拡張してもコケてくれない ● テストを通すことが⽬的ではない ○ 適切な理由とタイミングでコケるのが良いテスト ● 意図しない変更を、テストがコケることで事前に検知したい ○ 何がどうなったらテストがコケてくれるのかをイメージしましょう

Slide 20

Slide 20 text

© Findy Inc. 20 class TestGetUserById: def test_get_user_by_id_success(self, client): target_user = User( name="Test User", ) response = client.get(f"/api/v1/users/{target_user.id}") assert response.status_code == status.HTTP_200_OK assert response["id"] == target_user.id assert response["name"] == "Test User"

Slide 21

Slide 21 text

© Findy Inc. 21 class TestGetUserById: def test_get_user_by_id_success(self, client): target_user = User( name="Test User", ) response = client.get(f"/api/v1/users/{target_user.id}") assert response.status_code == status.HTTP_200_OK assert response["id"] == target_user.id assert response["name"] == "Test User" responseに項⽬が 追加されてもコケない

Slide 22

Slide 22 text

© Findy Inc. 22 class TestGetUserById: def test_get_user_by_id_success(self, client): target_user = User( name="Test User", ) response = client.get(f"/api/v1/users/{target_user.id}") assert response.status_code == status.HTTP_200_OK assert response.json() == { "id": target_user.id, "name": "Test User", }

Slide 23

Slide 23 text

© Findy Inc. class TestGetUserById: def test_get_user_by_id_success(self, client): target_user = User( name="Test User", ) response = client.get(f"/api/v1/users/{target_user.id}") assert response.status_code == status.HTTP_200_OK assert response.json() == { "id": target_user.id, "name": "Test User", } 23 意図しない項⽬追加時に コケてくれる ケースごとの テストのサンプルコードを カスタムインストラクションに追記

Slide 24

Slide 24 text

© Findy Inc. 24 まとめ

Slide 25

Slide 25 text

© Findy Inc. まとめ 25 ● ⽣成AIが出⼒するテストコードの質には伸び代がある ○ 伸び代ポイントを⾒つけた場合 ■ 既存のテストコードの⾒直し ■ カスタムインストラクションなどにサンプルコードを記述 ● ⽣成AI時代のテストコードが持つ役割は、今までよりも重要になった ○ 出⼒される実装コードの質を向上させるために、テストコードとも向き合いましょう ファインディに興味がある⽅は、ぜひカジュアル⾯談しましょう

Slide 26

Slide 26 text

© Findy Inc. ご清聴ありがとうございました 26