ネスト Trace と 2 つのモード
ネスト Trace と 2 つのモード
Usecase の中から別の Usecase を呼ぶような場面では、Trace が ネスト します。ApexTrace はネスト構造を LIFO スタックで厳密に管理し、不整合があれば例外を投げます。
Outer / Inner の順序ルール
ネスト Trace は「Inner を先に閉じてから Outer を閉じる」が原則です。
Trace outer = Trace.of('Outer'); outer.start();
Trace inner = Trace.of('Inner'); inner.start();
inner.log('inside inner'); // ✅
inner.finish(); // ✅ Inner を先に閉じる
outer.finish(); // ✅
Inner が start した後、finish / skip / abort のいずれかで閉じる前に Outer の log / finish を呼ぶと、Strict モードでは即例外が投げられます。
Strict モードと Relaxed モード
TraceFlow には 2 つのモードがあり、実行コンテキストで自動切替されます。
| モード | デフォルトの切替 | 挙動 |
|---|---|---|
| Strict | テスト実行時 (Test.isRunningTest() == true) | ネスト不整合・順序違反で即例外。バグを早期検出する |
| Relaxed | 本番実行時 | 多少の不整合は 自動で abort して吸収。本番が落ちにくくなる |
「テストで通った = 本番でも通る」と思いがちですが、モード差で挙動が変わることを意識しておきます。
⚠️ 切替は
Test.isRunningTest()による自動判定のみです。 利用者から明示的に切り替える API はありません (changeModeToとTraceModeはどちらもprivate)。
Trace.of() は instance field の初期化で 1 回だけ
Trace.of(...) は Usecase クラスの instance field の 初期化時に一度だけ 呼びます。invoke() 内で呼び直すと、毎回新しい Trace インスタンスができ、ネスト・集約のロジックが破綻します。
public with sharing class YourUsecase {
private Trace t = Trace.of('処理名'); // ✅ instance field で 1 回だけ
public void invoke() {
// private Trace t = Trace.of(...); // ❌ invoke 内で呼び直さない
this.t.start();
// ...
}
}
テストの冒頭で Trace.of('...').start() を書く理由
これまでに見たテスト例では、メソッドの冒頭に必ず次の 2 行が並びます。
Trace t = Trace.of('正常系: ...');
t.start();
そして末尾で t.finish(); を呼びます。これには 第一の理由 と 副次効果 があります。
第一の理由: Usecase 側の Trace ライフサイクル不整合を事前に防ぐため。Usecase の内側で finish / skip / abort を呼ばずに return してしまうと、開始と終了がミスマッチした「開きっぱなしの Trace」がコンテキストに残ります。テスト側でアウター Trace に包んでおくと、Strict モード (テスト実行時の default) がそのミスマッチを テストの段階で検出 してくれます。本番に出る前に、Usecase 側のライフサイクル抜けに気づける仕組みです。
副次効果: 直前の Trace 状態をリセットする。前のテストが残した Trace コンテキストを、新しいテストの冒頭で明示的に巻き直せます。
テストクラス全体の形
すべてのテストメソッドで、最初と最後に置きます。 クラス全体では次の形になります。
@isTest(seeAllData=false)
private class ResummarizeUsecase_T {
@isTest
static void testInvoke_WhenOpportunitiesExist_ThenSummaryUpdated() {
Trace t = Trace.of('正常系: 商談があるとき取引先のサマリが更新されること');
t.start();
// Arrange
...
// Act
...
// Assert
...
Assert.isTrue(TraceFlow.isLastFinish());
t.finish();
}
@isTest
static void testInvoke_WhenNoOpportunities_ThenSkipped() {
Trace t = Trace.of('正常系: 商談が 1 件も無いときスキップされること');
t.start();
// Arrange
...
// Act
...
// Assert
Assert.isTrue(TraceFlow.isLastSkip());
t.finish();
}
}
t.start() と t.finish() がメソッドの最初と最後を挟むのがポイントです。この形になっていれば、内側の Usecase が終了を呼び忘れた瞬間に Strict モードが検出します。
もう 1 つの効能: 検証内容を命名規則の外に書ける
テストメソッド名は test{メソッド}_When{条件}_Then{結果} のような形式に縛られます。grep できる識別子である以上これは正しい制約ですが、識別子の制約の中では、書ける説明の量も語彙も限られます。
Trace.of(...) の引数はただの文字列なので、その制約の外にあります。
| 役割 | 制約 | |
|---|---|---|
| メソッド名 | 機械可読。grep と実行結果の識別子 | 命名規則・識別子として妥当な文字 |
Trace.of の引数 | 人間可読。何を検証しているかの説明 | 無し |
英語圏以外のチームでは、これが可読性に直結します。 メソッド名は規約どおり英語のまま置き、説明は母語で書くという分担ができるからです。「何を検証しているのか」を母語で読めるほうが、レビューでも障害調査でも確実に速く、しかもメソッド名の規約を崩さずに済みます。
この文字列はデバッグログにも出力されるので、テストが落ちたときのログにも説明がそのまま載ります。
📌 Apex Stem の規約では、この文字列を
正常系:/異常系:/エッジケース:のいずれかで始めます。区分ラベルの後ろに、人間可読の説明文を続けてください。
関連ドキュメント
- Trace のライフサイクル: 4 メソッドと 3 つの終了パス
- TraceFlow で経路を検証する: Strict モードが叩き出す不整合
- ApexTrace ガイド: ガイド目次に戻る