ApexEloquent v2 の新機能について
先日、無事にApexEloquentのv2.0.0をリリースしました!
(急遽追加したい機能があったため、すでにv2.0.xに進んでいますが)
今回はその中で追加した機能にフォーカスを当てて紹介していきたいと思います!
エラーメッセージの統一
今までは標準のQueryExceptionを投げていたのですが、これを変更し、ApexEloquentExceptionを投げるようにしました。
このApexEloquentExceptionは
- どこで起きたか
- どんなエラーか
- 理由は何か
- 次に取るべきアクションは何か
をひとまとめにログに出してくれるので、人が読んでももちろんわかりやすいですし、AIが実行しても次にどうすればいいのかが明らかなのでApexEloquentの使い方により迷いにくくなりました!
Scribe
初期用 of メソッドの新設
今まで、Scribeの起点は
Scribe oppScribe = Scribe.source(Opportunity.getSObjectType());
でしたが、ちょっと長くてみづらかったのでApexBlueprintと合わせて
Scribe oppScribe = Scribe.of(Opportunity.class);
で宣言できるようにしました。特にネストしたクエリでは可読性が上がったと思います!
子リレーションの動的解決の高速化
Scribeで子リレーションを書く時
Scribe scribe = Scribe.of(Account.class)
.field('Name')
.withChildren(
Scribe.asChild(Contact.class)
.fields(new List<String>{'Id', 'Email'})
.whereNotNull('Email')
);
と書くと
SELECT name, (SELECT id, email FROM Contacts WHERE Email != NULL) FROM Account
というクエリが発行されます。
asChild(Contact.class) で宣言された部分が、親のAccountをみながらどんなリレーション名かを動的に解決しているんですが
この解決方法に二分探索を用いるようにしました。
具体的な内容はまたブログで書きますが、親が持つ子のオブジェクト名がUnicodeの順に並んでいるので、ここで二分探索を仕込みました。
重い処理であるgetDescribeの呼ぶ回数がグッと減る実装になったため、テスト時間がv2になり減りました!
この二分探索は組織が大きくなってリレーションが増えても影響が少ないため、テストの高速化により貢献できる機能かと思います
IEntry
getChildrenByRelationName を getChildren に統合
Scribe.relationName('xxx__r') を指定したとき、子の取得は getChildrenByRelationName('xxx__r') を使う必要がある (getChildren('xxx__r') ではない) のですが、claudeさんがこれに引っかかっていたそうなので
getChildren('xxx__r') で取れるようにしました。
AIフレンドリー!
MockEntry
モックデータに関する機能をかなり増やしました。テストを書くのがもっと楽に、楽しくなるはずです!
初期化用 of メソッドの新設
Scribeと同様に
MockEntry.of(Opportunity.class)
と書けるようになりました!スッキリしますね
SELECT忘れ検知機能をOFFにする機能
ScribeでfieldやfieldsでSELECTする項目を追加しますが、そこに入れていない項目にアクセスした時、モックデータでもエラーで落とすという「SELECT忘れ検知機能」をOFFにする機能ですね
具体的には
MockEntry.withoutFieldValidation()
のように、withoutFieldValidation()メソッドをチェーンに入れるだけです!
きっとどこかで役に立つはずです!
asAggregateResultで集計系のモックであることを表現する
MockEntryはAggregateResultもモックできますが、宣言する際にコツがあったので
MockEntry.asAggregateResult().set('count', 10);
のように集計のモックであることをわかりやすく表現できるようになりました!
フィールドの設定を set メソッドで行う
今まで add メソッドでフィールドの設定などをしていましたが、set を追加しました。
add と set で挙動は同じですが、v3でaddは削除予定です。
自分で書いていて、フィールドを設定する時についset を書いてしまうことから、addの方が一般的ではないかもと思いsetを追加しました!
aliasと、aliasを使った抜き出し
次のようにidや値をセットした時、その値を抜き出す手段がありませんでした。
MockEntry oppEntry = MockEntry.of(Opportunity.class).autoId(1).set('Name', 'TestOpp');
しかしテストを書いているときに、↑で作ったoppEntryのIdが欲しいなぁというケースが結構ありました。
そこで、
aliasgetByAliasgetAliasId
というAPIを追加しました!使い方は
- MockEntryに
aliasでエイリアスを付与 getByAliasでそのMockEntryを取り出す(親子も自動的にたどるよ!)- Idだけ欲しい時は
getAliasIdで取り出す!
MockEntry oppEntry = MockEntry.of(Opportunity.class)
.alias('opp') // エイリアスとしてoppを付与
.autoId(1)
.set('Name', 'TestOpp');
// Idを直接取り出す
Id mockOppId = oppEntry.getAliasId('opp');
// MockEntryを取り出す
MockEntry mockOpp = oppEntry.getByAlias('opp');
String mockOppName = mockOpp.getName();
これ、Assertを書く時にすごく便利です!ぜひ活用ください!
template メソッドの追加
テストデータのセットアップをするときに、同じ構成がたくさん出る時はtemplateメソッドの出番です!
private static Map<String, Object> getTemplate() {
return new Map<String, Object>{
'Name' => 'testOpp'
...
}
}
こんな感じで、よく使う項目をまとめて返すメソッドを用意しておいて、テストクラスで
MockEntry oppEntry = MockEntry.of(Opportunity.class)
.template(xxxTest.getTemplate())
.set(...)
のようにすれば、共通セットアップが完了です!うまくDRYをしましょう!
IEloquent
getAggregateをgetメソッドに統合
とにかくわかりづらく、私もわからなかったのでいっそ統合しました!
rawSOQLを受け入れるように
Scribeを強要していたのを、文字列のrawSOQLを受け入れるようになりました!
String soql = 'SELECT Id, Name FROM Opportunity LIMIT 10';
List<IEntry> oppEntries = (new Eloquent()).rawSoql(soql);
なお、この時は「SELECT忘れ検知機能」が強制的にOFFになりますのでご注意ください!
MockEloquent
failOnXxx 系のメソッドの追加
MockEloquentは、getやdoUpdateとかで意図的にエラーを投げられるようになっていますが、コンストラクタの第二引数にエラーを入れるという形で非常にわかりにくかったため
- failOnGet
- failOnFirst
- failOnDoInsert
- failOnDoUpsert
... などの failOnXxx メソッドを追加しました!
IEloquent eloquent = (new MockEloquent(mockEntry)).failOnGet();
このように定義して、DIするとgetメソッドが呼ばれたタイミングでエラーが投げられます!
これでキャッチブロックやリトライの検証がさらにやりやすくなりましたね!
deletedCountを追加
doDeleteした時に渡されたレコードを数を記録するようになりました。これで
// プロダクションコード
this.eloquent.doDelete(deleteTargets);
// テストクラス
Assert.areEqual(10, this.eloquent.deletedCount);
という、デリートに関わるアサートが可能になりました!