やわらかテック読者になるやわらかテック興味のあること。業務を通して得られた発見。Rspecのモック(double)はどのように作られているのか2022-12-02Rspecのモック(double)はどのように作られているのかRuby普段の業務でrspecのモック機能には非常にお世話になっています。使い方は非常に簡単で、既存クラスに定義された関数の振る舞いを変更することが出来ます。endendすごいですね。やって作られているのでしょうか。最近はOSSのコードを読む機会が多いのでせっかくなので、rspecのモック(double)がどのように作られているのかコードを読みながら深ぼってみました。www.okb-shelf.work結論としては、Doubleクラスのインスタンスを作成しclass_execやdefine_methodを使用してシングルトンクラスの振る舞いを定義することで、モックを作成していました。処理の流れDoubleクラスのインスタンス作成まず呼び出されたdouble関数ではExampleMethodsのdeclare_double関数が呼び出されます。declare_double関数は第一引数で指定されたクラスのインスタンスを作成しています。endendここまででDoubleクラスのインスタンスが作られました。Doubleクラスは内部でTestDoubleモジュールを読み込んでおり、実質、Doubleクラスのインスタンス作成はTestDoubleモジュールの呼び出しとなっています。次にTestDoubleの実装を見てみるとinitialize関数(インスタンス作成時に呼び出される)で2つの引数を定義しています。bar!')が指定されます。endend第一引数のクラス名は@nameに記録され、第二引数のハッシュはassign_stubs関数に渡されます。この関数内では指定されたキーバリューのペアごと:RSpec::Mocks.space.proxy_for(self)に定義されたadd_simple_stub関数が呼び出されます。この部分の呼び出し階層がかなり深いため、情報を割愛しますが、最終的に::RSpec::Mocks.space.proxy_for(self)はTestDoubleProxyクラスのインスタンスを返します。GitHubTestDoubleProxyクラスはProxyクラスを継承しており、resetの振る舞いだけオーバーライドしています。先程のadd_simple_stub関数はProxyクラスに実装された関数です。endendadd_simple_stub関数ではmethod_double_for関数の呼び出しを通して@method_doublesに記録されたバリューを取得しています。initialize関数の@method_doublesのHash.newにはブロック引数が指定されており、この指定があることでキーに対応するバリューが存在しなかった場合にブロック引数に指定された挙動が実行されます。self)が実行されてMethodDoubleクラスのインスタンスが作成されます。MethodDoubleクラスに定義されたadd_simple_stub関数が呼び出される訳ですね。階層が深くて頭がこんがらがってきました。シングルトンクラスとメタプログラミングここに来るまでに多くのクラスを中継してきましたが、いよいよ核部分であるMethodDoubleに辿り着きました。ExampleMethodsDouble(TestDouble)TestDoubleProxyMethodDouble長くなるため、部分的に割愛しています。呼びだされたadd_simple_stub関数から色々な関数を中継していきます。重要なのはdefine_proxy_method関数です。他の関数やインスタンス変数はmutex等を使ってすでに記録した関数の振る舞いやモックとなったクラスに実装されている振る舞いなどを記録しています。GitHubdefine_proxy_method関数でdefinition_target関数が呼ばれることで、おそらく最後のobject_singleton_class関数が呼び出されます。endendobject_singleton_class関数の内部を見てみると何やら見慣れない記述が...。selfと書きますが、これも記述したクラスのシングルトンクラスに対して実装を行うという宣言のために記述していたということに今になって気づきました。bar!そして、取得したシングルトンクラスに対してclass_exec関数とdefine_method関数を用いてメタプログラミングで関数の振る舞いを定義していきます。内部でproxy_method_invoked関数が呼び出されていますが、非常に処理が難解です。クラス変数の配列に値を記録したり削除したりして、振る舞いを管理しているようです。気になる方はぜひコードを見てみてください。これで作成したモックが呼び出された時の振る舞いが定義されました。前回調べた、travel_toと同じような実装がされていました。特定の振る舞いを固定したい場合はシングルトンクラスを使うのがよくあるパターンなのかなと感じました。09:49Tweet関連記事2023-02-14【ええな〜コード】作って学ぶRailsのenumの仕組みについてRailsを使っていて何かと便利なのが「enum」です。22:23名古屋発のベンチャーでWEBアプリのバックエンドをメインに作っています。エンジニア育成やチームビルディングにもちょっとだけ携わっています。3はてなブログをはじめよう!takamizawa46さんは、はてなブログを使っています。ブログを報告する引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。