Moqで利用されているAvatarライブラリについて少しだけ調べてみた
はじめに
C#のテストライブラリであるMoqは、インターフェイスをジェネリックで定義し、その後に振る舞いを注入できます。
具体的には、以下のテストコードの通りです。
https://github.com/moq/moq/blob/main/src/Moq.Tests/MoqTests.cs#L112
...
[Fact]
public void CanSetupMethodWithArgumentsViaReturns()
{
var calculator = Mock.Of<ICalculator>();
calculator.Add(2, 3).Returns(5);
var result = calculator.Add(2, 3);
Assert.Equal(5, result);
}
...
上記をどのような仕組みで実現しているのか?について調べ始めたら、Avatarライブラリに触れずには言及できそうにありませんでした。
そこで、Avatarライブラリについて色々調べた結果を残します。
https://github.com/devlooped/avatar
Avatarとは
はじめにを機械翻訳したところ、以下の通りとなりました。
Avatar はプロキシ・パターンを実装した最新の傍受ライブラリで、物理的な iOS デバイスやゲーム機のようにランタイムコード生成 (Reflection.Emit) が禁止または制限されている場所でも、コンパイル時のコード生成によりどこでも実行可能です。プロキシの動作は、ビヘイビアパイプラインと呼ばれるものを使ってコードで設定されます。
Reflection.Emitはリンク先の通り、動的にビルドしたりコンパイラしたりできる、といったものになります。
自分なりの理解で簡単に述べると、制限のある中でも、動的にコードの振る舞いを変更できるようにした、というものがAvatarであるという認識です。
利用方法についてもReadmeの中に記載されています。
https://github.com/devlooped/avatar#usage
Avatarの内部コード
Avatarの中身を少しだけ確認してみようと思います。
利用方法の中で触れられている、以下のコードを参考に少しだけ処理内容を確認してみます。
ICalculator calc = Avatar.Of<ICalculator>();
calc.AddBehavior((invocation, next) => ...);
Avatar.Of<ICalculator>()
で、振る舞いをカスタマイズ可能なICalculatorが返されます。
このとき、呼び出されるメソッドを遡っていくとIAvatarFactory.CreateAvatar
メソッドが呼び出されます。
上記I/Fを実装しているクラスはいくつかあるのですが、まだ読みやすいStaticAvatarFactoryのCreateAvatarメソッドを例に読み込んでみます。
https://github.com/devlooped/avatar/blob/main/src/Avatar/StaticAvatarFactory.cs#L25
27行目:名前空間を作成します。baseTypeがICalculator
である場合、AvatarNaming.GetFullNameではデフォルトの名前空間と組み合わせて"Avatars.Avatar.ICalculator"のような名前が生成されます。
そしてAvatarのアセンブリを読み込み、上記の名前からインスタンスを実際に生成します。
(しかし、その場合"Avatars.Avatar.ICalculator"インスタンスを生成できる状態(Assembly内に上記のインスタンスが定義されている)でないといけないため、DynamicAvatarFactoryが実際には利用されているはず。なにかわかったらまた記事にします))
その後、AddBehavior
メソッドを用いて振る舞いを追加していきます。
追加した振る舞いは、たとえばAnonymousBehaviorとして登録されますが、そのBehaviorを実行することで任意の振る舞いをさせられる仕組みなのだと思います。
(利用イメージはテストコードからも少し理解できそうです)
おわりに
正直不明点がまだまだ残っているので、もう少し詳しく中身を見ていく必要がありそうです。
avatarライブラリの中でcastleライブラリを利用しているようなので、そちらにも視点を向けて読み進めようと思います。