Upgrade to Pro — share decks privately, control downloads, hide ads and more …

dbt Pythonモデルで実現するSnowflake活用術

Trs
February 21, 2025

dbt Pythonモデルで実現するSnowflake活用術

Trs

February 21, 2025
Tweet

More Decks by Trs

Other Decks in Programming

Transcript

  1. © LayerX Inc. 2 ⾃⼰紹介 平田 拓也 (@TrsNium) 株式会社LayerX バクラク事業部 機械学習・データ部

    DataOps チーム データエンジニア 2023年11月よりLayerXにjoin 普段はデータ基盤の運用開発を中心にやっています !
  2. 3 © LayerX Inc. 「すべての経済活動を、デジタル化する。」をミッションに、AI SaaSとAI DXの事業を展開 事業紹介 バクラク事業 企業活動のインフラとなる業務を

    効率化するクラウドサービス Fintech事業 ソフトウェアを駆使したアセットマネジ メント‧証券事業を合弁会社にて展開 AI‧LLM事業 社内のナレッジやノウハウをデータ ベース化するAIプラットフォーム AI SaaSドメイン AI DXドメイン
  3. ⽬次 Agenda • はじめに • Python Modelの基礎知識 • Python Modelの仕組み(Snowflake編)

    • Python Modelの便利な機能と使い⽅ • テストとパフォーマンスの課題 • Snowflake環境でのログ出⼒と重要性 • Future Works と今後の展望
  4. ⽬次 Agenda • はじめに • PythonModelの基礎知識 • Python Modelの仕組み(Snowflake編) •

    Python Modelの便利な機能と使い⽅ • テストとパフォーマンスの課題 • Snowflake環境でのログ出⼒と重要性 • Future Works と今後の展望
  5. © LayerX Inc. 6 dbt Python Model とは? はじめに 概要

    • dbt Python Model は、従来の SQL モデルに加え、Python でデータ変換ロジックを記述できる機能です。 def model(dbt, session): # モデル設定(例: テーブルとしてマテリアライズ) dbt.config(materialized='table') # シンプルなデータ取得処理 return session.sql("SELECT * FROM source_table") models/example_python_model.py source: source_table model: example_python_model model: other_sql_model
  6. © LayerX Inc. 7 dbt Python Model で実現できること はじめに 柔軟なデータ変換

    • Python の豊富なライブラリやロジックを利用可能 • API 連携や複雑なデータ整形が容易に実装できる dbt のエコシステムとの統合 • マクロ、テスト、ドキュメント生成など、既存の dbt機能をそのまま活用 • SQLモデルと同様に依存関係管理が可能
  7. © LayerX Inc. 8 対応プラットフォーム & 実⾏環境 はじめに 対応プラットフォーム •

    dbt Python Modelは、Snowflake, BigQuery, Databricksなどがサポートされている 各プラットフォームの実行環境 • Snowflake ◦ Warehouse 上で実行され、Snowpark API を利用して処理が行われる • BigQuery ◦ Python モデルは Dataproc 上の PySpark ジョブとして実行され、 BigQuery のテーブルやビューと連携して処理結果を返す • Databricks ◦ Databricks 環境上で PySpark コードとして実行される
  8. © LayerX Inc. 9 深堀する運⽤の課題と取り組み はじめに • 弊社では、dbt Python Model

    を部分的に活用していますが、その運用の中で様々な課題に直面してきました。 • まず、dbt Python Model がどのように動作する仕組みについてご紹介し、その内部処理の流れを明らかにします。 • 次に、Snowflake 環境における実行の特徴、特に Snowpark API を活用したデータ処理の詳細なプロセスについてご説明します • さらに、テスト、デバッグ 、ログ出力に関して、 実際に直面した課題と、それらに対する改善策や工夫のポイントをお話しします。 • 最後に、これらの課題を踏まえた今後の展望として、 Programmatic Invocations の活用やテスト環境の整備など、次のステップに向けた取 り組みについてご紹介します。
  9. ⽬次 Agenda • はじめに • Python Modelの基礎知識 • Python Modelの仕組み(Snowflake編)

    • Python Modelの便利な機能と使い⽅ • テストとパフォーマンスの課題 • Snowflake環境でのログ出⼒と重要性 • Future Works と今後の展望
  10. © LayerX Inc. 11 .py ファイルがモデルと認識されるまでの流れ Python Modelの基礎知識 全体の流れ Pythonモデル定義(.pyファイル)

    静的解析 (AST解析) コンパイル ターゲット環境(例: Snowflake)で実⾏ なぜ静的解析をするのか? • Pythonコードを実行せずに「構造」を把握することで、予期せぬ副作用や 実行時エラーを防止 • 再現性のある、信頼性の高いモデル定義を実現 静的解析のメリットと制約 • メリット : コードがどのように書かれているか(関数や変数の配置な ど)を、実際に実行する前にしっかり確認できる → これにより、予期せぬ動作やエラーを未然に防ぐことが可能に ! • 制約: 動的な変数(envやvarsなど)や実行時ロジックは解析対象外
  11. © LayerX Inc. 12 静的解析とASTの例 Python Modelの基礎知識 • Pythonの静的解析には astモジュールが利用可能

    • ast.parse(source_code)を使うと、ソースコードが抽象構文木 (AST)に変換され、コードをツリー構造で扱うことができる import ast source_code = """ x = 42 def hello(): message = "Hello, dbt!" print(message) """ tree = ast.parse(source_code) print(ast.dump(tree, indent=4)) Module( body=[ Assign(targets=[Name(id="x", ctx=Store())], value=Constant(value=42)), FunctionDef( name="hello", args=arguments( posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[] ), body=[ Assign( targets=[Name(id="message", ctx=Store())], value=Constant(value="Hello, dbt!"), ), Expr( value=Call( func=Name(id="print", ctx=Load()), args=[Name(id="message", ctx=Load())], keywords=[], ) ), ], decorator_list=[], ), ], type_ignores=[])
  12. © LayerX Inc. 13 AST解析実装の詳細 Python Modelの基礎知識 ソースコードの読み込みと AST生成 •

    ソースコードを文字列として読み込み、 ast.parse() によりASTを作成 カスタムAST Visitorの利用 • AST Visitor とは? : AST Visitor は、AST の各ノードを訪問し、必要な情報を取り出すためのクラス • ast.NodeVisitor を継承したクラスを用いて、 AST を巡回しながら、モデル定義に必要なノード(例 : 関数定義、変数代入、 dbt.ref や dbt.config などの dbt 特有の呼び出し)を抽出 参考 • dbt-core/core/dbt/parser/models.py
  13. © LayerX Inc. 14 コンパイルフェーズとターゲット環境での実⾏ Python Modelの基礎知識 コンパイルフェーズ • dbt

    は、PythonコードをAST解析により解析し、そこで抽出された定義を元にモデルオブジェクトを生成 • その過程で、dbt.ref や dbt.config などのdbt固有の関数が評価され、必要な変数の置換やマクロの展開が行われ、最終的に SQLやス トアドプロシージャに変換される ターゲット環境(Snowflake)での実行 • 変換されたモデルは、 Snowflake上でストアドプロシージャとしてラップされ、実際に実行される • 実行中は、プロシージャ内でデータの処理が行われ、その結果が最終的にテーブルなどに保存される
  14. © LayerX Inc. 15 マクロ評価と変数管理の注意点 Python Modelの基礎知識 マクロ評価の注意点 • コンパイル時はmodel()

    の実際の実行は行われず、コード構造から必要な情報のみが抽出される • 動的な変数(例 : env や vars)は直接扱えないため、代わりに schema.yml や静的な設定で値を渡す必要がある version: '2' models: - name: workspace__example__dbt_meetup config: schema: example database: "{{ var(target.name)['database'][workspace] }}" alias: dbt_meetup foo: "{{ var(target.name)[example][foo] }}" var: "{{ var(target.name)[example][foo] }}" def model(dbt, session): dbt.config(materialized='table') foo = dbt.config.get('foo') var = dbt.config.get('var') … return df
  15. ⽬次 Agenda • はじめに • Python Modelの基礎知識 • Python Modelの仕組み(Snowflake編)

    • Python Modelの便利な機能と使い⽅ • テストとパフォーマンスの課題 • Snowflake環境でのログ出⼒と重要性 • Future Works と今後の展望
  16. © LayerX Inc. 17 SnowflakeにおけるPython Modelの概要 Python Modelの仕組み(Snowflake編) Python Modelの実行方法

    • Pythonコードは、Snowflake上でストアドプロシージャとして実行 される • use_anonymous_sproc を True にした場合、匿名ストアドプロ シージャが利用される ◦ メリット : 実行が高速になり、クエリ履歴がクリーンに保た れる Snowflakeとの連携 • dbt のコンパイルフェーズで、 Pythonコードが AST 解析・変換さ れ、最終的な SQL やストアドプロシージャに変換される • 変換されたSQL/ストアドプロシージャーが Warehouse上で実行 される Pythonモデル定義(.pyファイル) 静的解析(AST)/コンパイル 変換されたSQL/ストアドプロシージャ Warehouse 上で実⾏ Snowpark API を利⽤したデータ処理 結果をテーブルに保存
  17. © LayerX Inc. 18 プロシージャ内での処理の詳細 Python Modelの仕組み(Snowflake編) データマテリアライズまでの流れ • dbt

    Python Model の model 関数が実行され、加工された データが得られる • 取得したデータは、 Snowparkのsave_as_table メソッドを 用いてSnowflake上にマテリアライズされる dbt内部ロジックとの統合 • dbt.ref, dbt.config, など、dbt固有の関数により、上流モデ ルの解決や差分更新、データ変換処理が統合され、 dbtモ デルとして動作する プロシージャーの実⾏ model(dbt, session) の実⾏ materialize() 呼び出し save_as_table でマテリアライズ
  18. © LayerX Inc. 19 コード例 ‒ Python Modelとdbt内部ロジック Python Modelの仕組み(Snowflake編)

    def model(dbt, session): dbt.config(materialized='table') # 上流モデルを参照(コンパイル時に⽣成されるロジックによりリソース解決) df = dbt.ref("my_first_dbt_model") return df # 以降のコードはコンパイル時に⾃動で⽣成される def ref(*args, dbt_load_df_function): refs = {"my_first_dbt_model": "DEMO_DB.DEMO_SCHEMA.my_first_dbt_model"} key = ".".join(args) return dbt_load_df_function(refs[key]) # 略: source(), config, this, dbtObj などの実装
  19. © LayerX Inc. 20 コード例 ‒ エントリーポイントとマテリアライズ処理 Python Modelの仕組み(Snowflake編) def

    materialize(session, df, target_relation): import pandas if isinstance(df, pandas.core.frame.DataFrame): df = session.createDataFrame(df) # テーブルとして結果を保存する df.write.mode("overwrite").save_as_table("DEMO_DB.DEMO_SCHEMA.my_first_python_model", table_type='') def main(session): dbt = dbtObj(session.table) df = model(dbt, session) materialize(session, df, dbt.this) return "OK"
  20. ⽬次 Agenda • はじめに • Python Modelの基礎知識 • Python Modelの仕組み(Snowflake編)

    • Python Modelの便利な機能と使い⽅ • テストとパフォーマンスの課題 • Snowparkライブラリに関する課題 • Snowflake環境でのログ出⼒と重要性 • Future Works と今後の展望
  21. © LayerX Inc. 22 Python Modelの便利な機能と使い⽅ Python Modelの便利な機能と使い⽅ • dbt

    Python Modelは、Pythonならではの柔軟なデータ操作が可能 • SQLモデルと同様に、 dbtの便利な関数・マクロを活用できる ◦ dbt.is_incremental, dbt.ref など • インクリメンタル更新により、差分のみの処理が実現できる
  22. © LayerX Inc. 23 dbt.is_incremental の活⽤ Python Modelの便利な機能と使い⽅ インクリメンタル実行の判定 •

    dbt.is_incremental を使い、現在の実行が初回か差分更新かを判定可能 実用例 • 既存テーブルから最新の更新時刻を取得 • その時刻を元に新たな開始時刻として設定し、新規データのみ取得 参考 • https://docs.getdbt.com/docs/build/incremental-models def model(dbt, session: Session) -> DataFrame: # モデルをインクリメンタルとして設定(unique_keyも指定) dbt.config(materialized='incremental', unique_key='id') # 初回実⾏時は固定の開始⽇時(例: 2024-09-01)からデータ取得 start_time = int(time.mktime(datetime.date(2024, 9, 1).timetuple())) # インクリメンタル実⾏時は、既存データの最終更新時刻から開始時刻を調整 if dbt.is_incremental: query = f"SELECT EXTRACT(EPOCH FROM MAX(updated_at)) FROM {dbt.this}" last_updated_at = int(session.sql(query).collect()[0][0]) start_time = last_updated_at - 60 # 1分前のタイムスタンプを設定 # APIからデータ取得(詳細は省略) data = fetch_data(start_time) schema = infer_schema(data) return session.create_dataframe(data, schema=schema)
  23. © LayerX Inc. 24 dbt.ref を使った依存関係管理 Python Modelの便利な機能と使い⽅ モデル間の参照 •

    dbt.ref("other_model") を使用して、他のモデルへの依存関係を明示 利点 • dbtが依存関係を自動解決し、正しい実行順序を保証 • コードの保守性と再利用性が向上 参考 • https://docs.getdbt.com/docs/build/python-models#referencing-other-models
  24. ⽬次 Agenda • はじめに • Python Modelの基礎知識 • Python Modelの仕組み(Snowflake編)

    • Python Modelの便利な機能と使い⽅ • テストとパフォーマンスの課題 • Snowparkライブラリに関する課題 • Snowflake環境でのログ出⼒と重要性 • Future Works と今後の展望
  25. © LayerX Inc. 26 弊社でのテストの⽅法とアプローチ テストとパフォーマンスの課題 テストフレームワーク • Pytest を利用

    ◦ ユニットテストと統合テストの両面から、 dbt Python Model の各処理(外部 API 呼び出し、データフレーム変換、フィルタ処理) の正確な動作を検証 • モック化手法の活用 ◦ monkeypatch を利用して、requests.get などの外部依存の挙動を固定のダミーデータで再現 ◦ モック化により、安定した再現性の高いテスト環境を実現 テスト環境のセットアップ • Snowflake セッションの再現 ◦ Snowflake セッションを再現するためのラッパー関数( new_session())を用意 ◦ 接続情報が未設定の場合は、ローカルテスト用の DummySession を使用して簡易な環境を構築
  26. © LayerX Inc. 27 モデル関数 (model) の例 テストとパフォーマンスの課題 ポイント •

    外部APIからユーザーデータを取得 • pandas.json_normalize によりネストされたデータをフラッ ト化 • 必要なカラムのみ抽出し、 create_dataframe に渡して DataFrame を生成 def model(dbt, session): dbt.config(materialized='table', packages=['requests']) url = "https://何かサービス/users" response = requests.get(url) response.raise_for_status() data = response.json() # APIデータをフラット化して必要なカラムを抽出 df_flat = pd.json_normalize(data) data_records = df_flat[['id', 'name', 'username']].to_dict(orient="records") # Snowflake の create_dataframe はリストの辞書形式を受け⼊れる df = session.create_dataframe(data_records) return df
  27. © LayerX Inc. 28 ダミーオブジェクトの定義 テストとパフォーマンスの課題 ポイント DummyDBT • dbt.config()

    の呼び出しを模倣して、設定内容を出力 DummySession • 実際の Snowflake セッションの代わりに、テスト用に pandas DataFrame を返すシンプルな実装 # ダミーの dbt オブジェクト class DummyDBT: def config(self, **kwargs): print("dbt.config called with:", kwargs) # ダミーの Session オブジェクト(テスト⽤) class DummySession: def create_dataframe(self, data): # Snowpark の create_dataframe の代わりに pandas DataFrame を返す return pd.DataFrame(data)
  28. © LayerX Inc. 29 テストクラスの環境構築部分 テストとパフォーマンスの課題 ポイント • new_session() で環境変数に応じてテスト用のダミーセッ

    ションまたは実際のセッションを生成 • テスト環境に応じたセッション管理を実現 class TestDataProcessingProcedure: @contextlib.contextmanager def new_session(self): if os.environ.get('DBT_SNOWFLAKE_ACCOUNT', 'fake_account') == 'fake_account': yield DummySession() else: snowflake_account = os.environ.get('DBT_SNOWFLAKE_ACCOUNT') snowflake_user = os.environ.get('DBT_SNOWFLAKE_USER') session = Session.builder.configs({ 'account': snowflake_account, 'user': snowflake_user, … # ロールやデータベース、スキーマなどを指定する }).create() try: yield session finally: session.close()
  29. © LayerX Inc. 30 外部APIモック テストとパフォーマンスの課題 ポイント • 外部API呼び出しをモックし、固定のダミーデータを返す def

    fake_requests_get(self, url): # ダミーの API データを返す dummy_data = [ {"id": 1, "name": "User One", "username": "userone", "email": "[email protected]"}, {"id": 2, "name": "User Two", "username": "usertwo", "email": "[email protected]"}, {"id": 3, "name": "User Three", "username": "userthree", "email": "[email protected]"} ] class DummyResponse: def __init__(self, data): self.data = data def raise_for_status(self): pass def json(self): return self.data return DummyResponse(dummy_data)
  30. © LayerX Inc. 31 テスト関数の実装 テストとパフォーマンスの課題 ポイント • モック化した requests.get

    を利用して model() を実行 • Snowpark の DataFrame を Pandas DataFrame に変換し、 カラム名を小文字化して期待値と比較 • check_dtype=False により、dtype の違いは無視して内容の み比較 def test_model_function(self, monkeypatch): # requests.get をモック化 monkeypatch.setattr(requests, "get", self.fake_requests_get) dbt = DummyDBT() with self.new_session() as session: df = model(dbt, session) # Snowpark DataFrameの場合は toPandas() で変換 if hasattr(df, "toPandas"): df = df.toPandas() # Snowpark ではカラム名が⼤⽂字になるので⼩⽂字に変換 df.columns = [col.lower() for col in df.columns] expected = pd.DataFrame([ {"id": 1, "name": "User One", "username": "userone"}, … ]) pd.testing.assert_frame_equal(df.reset_index(drop=True), expected, check_dtype=False)
  31. © LayerX Inc. 32 単体実⾏例 テストとパフォーマンスの課題 ポイント • テストクラス外で、ダミーの dbt

    と new_session() を使って model() を実行 • 結果の DataFrame をコンソールに出力し、動作確認可能 if __name__ == '__main__': dbt = DummyDBT() with TestDataProcessingProcedure().new_session() as session: result_df = model(dbt, session) print(result_df)
  32. © LayerX Inc. 33 内部モジュール (_snowflake) の概要 テストとパフォーマンスの課題 専用内部モジュールとしての _snowflake

    • _snowflake は、Snowflake の UDFやStreamlitなどに内部的に提供されるモジュール • 実行中にのみ利用され、セキュアなシークレットアクセスなどの用途で使用する
  33. © LayerX Inc. 34 テストにおける課題: 内部モジュール テストとパフォーマンスの課題 ローカル開発と本番環境のギャップ • ローカル開発環境では、

    _snowflake モジュールを利用する手段はない。 • 本番環境の実行時には、専用の内部モジュールとして _snowflake が用いられており、この内部実装は秘密管理などに特化した機 能を提供する。 ◦ ローカルでの挙動と本番での挙動は根本的に異なる。 モック/スタブの実装の必要性 • 本番環境の内部モジュールの動作を再現するため、ローカルテストでは専用のモックやスタブの構築が必要な場合がある。 • これにより、テストコードが複雑化し、保守性やデバッグが難しくなる可能性がある。
  34. © LayerX Inc. 35 テストにおける課題: 環境差異の問題 テストとパフォーマンスの課題 実環境とテスト環境の違い • 実際の

    Snowflake 環境とローカルテスト環境では、接続設定や権限、リソース管理などに差異があり、テスト結果に影響を与える データ型・カラム名の相違 • Snowpark は返す DataFrame のカラム名を大文字に変換するため、テスト時に変換処理が必要となる • データ型(例: int8 vs int64)の違いも検証を複雑にする 統合テストの再現性 • モックやスタブで完全に実環境を再現するのは難しく、実環境での統合テストとのギャップが発生する可能性
  35. © LayerX Inc. 36 パフォーマンスの課題と考慮点 テストとパフォーマンスの課題 データ処理の効率 • インクリメンタル更新により、必要なデータのみを処理しリソースの無駄を削減 •

    Snowparkを利用することで、分散処理により大規模データでも高速処理が可能 メモリ管理の課題 • Snowflakeの分散アーキテクチャにより、全データが一度にメモリに乗らない仕組み • ただし、データのロードやデータの処理の仕方が悪いことによりメモリリークが起こる可能性がある ログ出力とモニタリング • 実行時ログを活用して、パフォーマンスのボトルネックやエラー発生箇所を特定 • 定期的なパフォーマンステストにより、処理効率を継続的に評価
  36. ⽬次 Agenda • はじめに • Python Modelの基礎知識 • Python Modelの仕組み(Snowflake編)

    • Python Modelの便利な機能と使い⽅ • テストとパフォーマンスの課題 • Snowflake環境でのログ出⼒と重要性 • Future Works と今後の展望
  37. © LayerX Inc. 38 Snowflake のログ‧トレーシング設定 Snowflake環境でのログ出⼒と重要性 Telemetry Levels の概要

    • Snowflake では、ログ出力とトレーシングの詳細度を制御するために Telemetry Levels を設定できます。 • 設定により、エラー情報、警告、情報レベル、デバッグ情報などの出力を調整可能。 設定方法の参考 • 詳細は Snowflake Telemetry Levels を参照
  38. © LayerX Inc. 39 ログ&トレーシングを組み込む Snowflake環境でのログ出⼒と重要性 ポイント • logger.info で各処理段階の情報を出力

    • トレーシングで「 fetch_and_process_data」スパン内の処理 を記録し、デバッグを支援 import logging import requests from snowflake import telemetry from opentelemetry import trace logger, trace = logging.getLogger("example"), trace.get_tracer("example") def model(dbt, session): dbt.config(materialized='table', packages=['snowflake-telemetry-python', 'opentelemetry-api', 'requests']) logger.info("Model execution started") with tracer.start_as_current_span("fetch_and_process_data"): data = requests.get("https://何かサービス/users") logger.info("Data retrieved from API") … df = session.create_dataframe(data_records) logger.info("DataFrame created successfully") return df
  39. © LayerX Inc. 40 Snowflake ログ出⼒ & トレーシングの重要性 Snowflake環境でのログ出⼒と重要性 運用監視・デバッグ

    • リアルタイムでエラー・パフォーマンスを監視し、トラブルシューティングに役立 てる トレーシングによる処理の可視化 • 処理の各段階(データ取得、変換、 DataFrame作成)の流れを追跡し、ボトル ネックを特定
  40. ⽬次 Agenda • はじめに • Python Modelの基礎知識 • Python Modelの仕組み(Snowflake編)

    • Python Modelの便利な機能と使い⽅ • テストとパフォーマンスの課題 • Snowflake環境でのログ出⼒の重要性 • Future Works と今後の展望
  41. © LayerX Inc. 42 Programmatic Invocations の活⽤ Future Works と今後の展望

    概要 • Programmatic Invocations は、dbt モデルの実行をプログラム的に制御する仕組み 自動エラーハンドリング • モデル実行時にエラーが発生した場合、そのエラー情報を自動でキャプチャし、適切なハンドリングを実施 • エラー発生時の自動リトライや通知、あるいはエラーをログに記録することで、迅速な対処を実現 参考 • https://docs.getdbt.com/reference/programmatic-invocations
  42. © LayerX Inc. 43 テスト‧デバッグ環境の改善 Future Works と今後の展望 現状の課題 •

    共通で利用できるテスト環境が未整備のため、各モデルごとに個別のモックやスタブ実装が必要 • ローカルテストと本番環境の差異により、デバッグが困難な場合がある 今後の取り組み • ユニットテスト環境の提供 ◦ ローカルでのユニットテストが確実に動作するよう、共通のテストユーティリティやラッパー関数を整備し、再現性の高い環境 を構築 ◦ e2e テストは dbt test で十分なため、ユニットテストの精度向上に注力する • 共通ライブラリの整備と活用 ◦ 処理をUDF やストアドプロシージャとして切り出すか、 Snowflake に Zip でパッケージ化し置くか、共通ライブラリの配置・管 理方法を検討し、全モデルで再利用できる仕組みを構築する ◦ ログ出力、トレーシング、モック化のベストプラクティスを作成し、 CI/CD パイプラインに組み込むことで、環境差異の影響を最 小化
  43. © LayerX Inc. 45 まとめ • dbt Python Model の全体像について紹介し、

    PythonコードはAST解析を経てモデル定義に変換され、 Snowflake上で実行される仕 組みについて解説しました。 • Pytestを用いたユニットテスト環境とモック/スタブの工夫により、環境差異への対応やデバッグを容易にする方法について紹介しま した。 • 将来的には、Programmatic Invocationsによる自動エラーハンドリングや統一されたテスト・デバッグ環境の整備を目指しています。