初心者向けのDIおよびIoC、パート3

Unityを使用したDI / IoCのテーマ(パート1、2 )を続けて、オブジェクトを作成しない状況での使用方法を検討し、単体テスト用のフレームワークのアプリケーションも調べます。

同じタイプの複数のマッピングが登録されている状況は、登録されているすべてのタイプのサービス(つまり、 IService[] )を転送する必要がある場合に監視しIService[]ます。 しかし、コンテナから特定のサービスを取得する必要がある場合はどうでしょうか? これを行うために、Unityはオブジェクトに名前を付ける機能を提供します。 たとえば、このコードのアナログを実装するには
var svc10 = new MyService(10);<br/>
var svc15 = new MyService(15);<br/>
「登録済み」マッピングのみを登録する必要があります。
var uc = new UnityContainer();<br/>
//
uc.RegisterType<IService, MyService>( "ten" ,<br/>
new InjectionConstructor( new InjectionParameter(10)))<br/>
.RegisterType<IService, MyService>( "fifteen" ,<br/>
new InjectionConstructor( new InjectionParameter(15)));<br/>
//
var _10 = uc.Resolve<IService>( "ten" );<br/>
var _15 = uc.Resolve<IService>( "fifteen" );<br/>
また、すべての登録済みマッピングを取得することもできます。 これは、 ResolveAll()関数を使用して行われます。
foreach (IService svc in uc.ResolveAll<IService>() )<br/>
svc.DoSomething();<br/>

外部作成


DIを実行したいオブジェクトがコントロール外で作成される場合があります。 これが頻繁に発生する例は、WCF、WPF、さまざまなリモート処理/ SoAスクリプトです。 オブジェクトは他の誰かによって作成されたという事実にもかかわらず、全体のプロセスにオブジェクト(および依存オブジェクト)を接続できることは依然として素晴らしいことです。

別の合成例を次に示します。
public class HelperClass<br/>
{<br/>
public void DoSomething()<br/>
{<br/>
Console.WriteLine( "Doing something" );<br/>
}<br/>
}<br/>
public class SomeService<br/>
{<br/>
[Dependency]<br/>
public HelperClass MyHelperClass { get; set; }<br/>
public void Go() { MyHelperClass.DoSomething(); }<br/>
}<br/>
<br/>
⋮<br/>
<br/>
var uc = new UnityContainer();<br/>
var ss = Activator.CreateInstance<SomeService>();<br/>
Console.WriteLine(ss.MyHelperClass == null ); // True
オブジェクトの準備がすでに完了しているため、DIメカニズムは適用されませんでしたが、これは問題ではありませんBuildUp()を使用して事後的に取得できます。
var uc = new UnityContainer();<br/>
// ""
var ss = Activator.CreateInstance<SomeService>();<br/>
Console.WriteLine(ss.MyHelperClass == null ); // True
var ss2 = uc.BuildUp<SomeService>(ss) ;<br/>
Console.WriteLine(ss2.MyHelperClass == null ); // False
ss2.Go();<br/>
Console.WriteLine(ReferenceEquals(ss, ss2)); // True
コードからわかるように、 BuildUp()メソッドは、オブジェクトがIoCコンテナーを生成した場合に作成される依存関係を既存のオブジェクトに「構築」します。 この例では、上部構造が作成されない場合、新しいオブジェクトは作成されず、古いオブジェクトが使用されることも示しています。

もちろん、オブジェクトが外部で作成された場合、 Resolve()メソッドの動作は変わります。 特に、サービスがIService実装している場合、 IServiceから既存のオブジェクトにマップし、問題なく同じコピーを発行できます。 これを行うには、 RegisterInstance()メソッドを使用して既存のオブジェクトをRegisterInstance()ます。
var uc = new UnityContainer();<br/>
var ss = Activator.CreateInstance<SomeService>();<br/>
uc.BuildUp<SomeService>(ss);<br/>
// IService ss
uc.RegisterInstance<IService>(ss); <br/>
var svc = uc.Resolve<IService>();<br/>
svc.Go();<br/>

テスト中


単体テストを簡素化するのは、DIメカニズムの目標からです。 一番下の行は、ほとんどの開発者にとって、ユニットはクラスであるため、テスト内の他のクラスを置き換える必要があるということです。 別の簡単な例を見てみましょう。
public interface IAdder<br/>
{<br/>
int Add( int first, int second);<br/>
}<br/>
public sealed class AdderService : IAdder<br/>
{<br/>
public int Add( int first, int second)<br/>
{<br/>
return first + second;<br/>
}<br/>
}<br/>
public class MyApp<br/>
{<br/>
private IAdder adder;<br/>
public MyApp(IAdder adder)<br/>
{<br/>
this .adder = adder;<br/>
}<br/>
public int AddAndMultiply( int first, int second, int third)<br/>
{<br/>
return adder.Add(first, second) * third;<br/>
}<br/>
}<br/>
次のテストは統合です。なぜなら、 AdderServiceMyApp 2つのクラスの特定のコピーが含まれMyApp
[Test]<br/>
public void NotAUnitTest()<br/>
{<br/>
var uc = new UnityContainer();<br/>
uc.RegisterType<IAdder, AdderService>();<br/>
Assert.AreEqual(20, uc.Resolve<MyApp>().AddAndMultiply(2, 3, 4));<br/>
}<br/>
ところで、作業コードでは、コンテナは上記のとおりに正確に構成されます。 ただし、テスト時には、 AdderService別の「テスト」実装に置き換えることができます。 最も簡単なオプションは、単に定数を返すFakeAdderServiceを記述することです。
public class FakeAdderService : IAdder<br/>
{<br/>
public int Add( int first, int second)<br/>
{<br/>
return 5; // !
}<br/>
}<br/>
今、私たちはコンテナを変更します-ほら! -完全なユニットテストがあります。
[Test]<br/>
public void UnitTestWithFake()<br/>
{<br/>
var uc = new UnityContainer();<br/>
uc.RegisterType<IAdder, FakeAdderService >();<br/>
Assert.AreEqual(20, uc.Resolve<MyApp>().AddAndMultiply(2, 3, 4));<br/>
}<br/>
もちろん、私たちの偽物は特に柔軟性があります-結局、入力時に期待する値を正確に返します(つまり、テストでは「2、3」と書き込み、偽物では数値5を返します)。 そして、突然別のテストで他の値を使用する必要がある場合、どうすればよいですか? 別の偽オブジェクトを作成しますか? または、偽のパラメーター化可能にし、コンテナーでパラメーターを構成しますか?

幸いなことに、この種のものは何も必要ありません。 代わりに、Rhino MocksやTypemock Isolatorなどのモックフレームワークを使用できます。 その後、偽のIServiceオブジェクトが自動的に挿入されるように、独自の偽のクラスなしでテストを書き換えることができます。これにより、必要な値が正確に返されます。
var uc = new UnityContainer();<br/>
var svc = Isolate.Fake.Instance<AdderService>();<br/>
Isolate.WhenCalled(() => svc.Add(0, 0)).WillReturn(5);<br/>
uc.RegisterInstance<IAdder>(svc);<br/>
Assert.AreEqual(20, uc.Resolve<MyApp>().AddAndMultiply(2, 3, 4));<br/>
上記では、Typemockを使用して偽オブジェクトを作成し(これについてはこちらで確認できます )、 Add()メソッドを呼び出す動作を設定し(常に5を返します)、コンテナーに追加しました。 したがって、私は本格的な、簡単に構成可能な単体テストを受け取りました。

今のところすべてです。 Automockingとその他の興味深いことについては次回議論します。

Source: https://habr.com/ru/post/J63568/


All Articles