依存を解消する.

11894 ワード

前言:ユニットテストでは、コントロールできないオブジェクトに耐えられるオブジェクトがよく現れます.そのため、このときは自分でコントロールできるオブジェクトに代わって耐えられないオブジェクトから抜け出さなければなりません.

1:依存を解消する理由


1.1:運転速度が遅い


たとえば,ユーザIdによってユーザが存在するか否かを判断すると,この方法はデータベースに依存する.これにより統合テストとなり、大量のテストでは速度が遅くなります.

1.2:構成が必要


データベースに依存するため、データベースに関連するファイルが構成されます.

1.3:一度に多くの内容をテストし、エラー情報を特定できない


例えば、この方法の誤りは、入力されたユーザIdが空であるか、または入力されたユーザIdが存在しないか、データベース接続が切断されているかなどによる可能性があり、セルテストの目的を果たすことができません.

2:スタブ


外部依存項目:システム内のオブジェクトを指し、テストされたコードはこのオブジェクトとインタラクティブですが、このオブジェクトを制御できません.フロントエンドエンジニアとバックグラウンドエンジニアが協力し、フロントエンドエンジニアがバックグラウンドから返されるデータを待って処理する場合、バックグラウンドは外部依存項目です.バックグラウンドのコードを制御できないからです
定義:1つのスタブ(stub)は、システムに存在する依存項目(または協力者)の制御可能な代替物です(制御できないオブジェクトを置き換えるオブジェクトを探します).ルートを使用することで、コードをテストするときにこの依存項目を直接処理する必要はありません.(はっきり言って、コントロールできないオブジェクトの代わりに自分で定義したオブジェクトです)

3:コード設計を再構築し、コードのテスト性を高める


3.1:インタフェースを抽出して下位層を置換可能にする


実際には、下位レベルではインタフェースを使用する必要があります.このように、上位レベルのコードは、特定のオブジェクトではなくインタフェースに依存し、プロジェクトをより良い拡張性にする必要があります.もちろん、ここでは、より良いテストのために働いています.
最下位メソッドからインタフェースを抽出
public interface IUser
    {
        /// <summary>
        ///  
        /// </summary>
        /// <param name="userId"> </param>
        /// <returns></returns>
        bool IsExist(string userId);
    }

最下位アクセス・データベースのクラス
public class User:IUser
    {
        public bool IsExist(string userId)
        {
            // 
            // true
        }
    }

テスト対象の作業ユニット
public bool IsExistUser(string userId)
        {            
             var user = new User();
             return user.IsExist(userId);
        }                
        

制御可能なルート
public class FackUser:IUser
    {
        public bool WillBevalid = false;        
        public bool IsExist(string userId)
        {
            return WillBevalid;    
        }
    }

次にスタブを注入し始めます.

4:依存注入(テストされたユニットに擬似実装を注入)


4.1:構築パラメータ注入


その名の通りインスタンス化するときにパラメータを構築するときに擬似オブジェクトを注入する
このとき、上のクラスを次のように変更します.
テスト対象クラス
public class UserBll
    {
        private readonly IUser _user;
        public UserBll(IUser user)
        {
            this._user = user;
        }
        public bool IsExistUser(string userId)
        {            
            return _user.IsExist(userId);                        
        }       
}

テストコード
         [Test]
        public void IsExistUser_ExistUser_ReturnsTrue()
        {
            var fackUser = new FackUser {WillBevalid = true};
            var user = new UserBll(fackUser);// 
            bool result = user.IsExistUser("1");
            Assert.IsTrue(result);
        }

コンストラクション関数注入についてのまとめ:コンストラクション関数注入を用いた比較的簡単な直感的可読性と理解の面でもよい.しかし、依存が多くなると、構造関数のパラメータが多くなるとメンテナンスが難しくなるという問題もあります.
シーンの使用:apiの設計を例えると、一部の使用者自体がパラメータ付きの構造関数であればそうすることができます.

4.2:属性(get;set)を使用してアーティファクトを注入する


テスト対象クラス
public class UserBll
    {
        public IUser User { get; set; }

        public UserBll(IUser user)
        {
            User = new User();// 
        }

        public bool IsExistUser(string userId)
        {
            return User.IsExist(userId);    
        }       
}

コードテスト
       [Test]
        public void IsGetName_NormalGetName_ReturnsTrue() {
            var fackUser = new FackUser { WillBevalid = true };
            var user = new UserBll { User = fackUser };// 
            bool result = user.IsExistUser("1");
            Assert.IsTrue(result);
        }

属性注入についてまとめます:構造関数注入と似ていますが、読みやすく、書きやすいです.
≪プロパティ・インジェクションの使用|Use Attributeインジェクション|emdw≫:テストされたクラスの依存項目がオプションであるか、デフォルトで作成された依存項目を安心して使用できるかを示すために、プロパティ・インジェクションを使用します.

4.3:工場でメンバーを偽造する(ダミー)


まず工場類を見てみましょう
public class UserFactory
    {
        private  IUser _user = null;

        public  IUser Create()
        {
            if (_user != null)
                return _user;
            return new User();
        }

        [Conditional("DEBUG")]
        public  void SetUser(IUser muser)
        {
            _user = muser;
        }
    }

テスト対象クラス
public class UserBll
    {        
        public bool IsExistUser(string userId)
        {                            
                var userFactory = new UserFactory();                                          
return userFactory.Create().IsExist(userId); }

テストコード
        [Test]
        public void IsGetName_NormalGetName_ReturnsTrue() {
            var fackUser = new FackUser { WillBevalid = true };
            var userFactory = new UserFactory();
            userFactory.SetUser(fackUser);// 
            bool result = new UserBll().IsExistUser("1");
            Assert.IsTrue(result);
        }

偽造方法についてのまとめ:この方法は簡単で、工場にコントロールする偽依存項目を追加します.テストされたコードに何の変化もありません.すべてはそのままです.
この方法は明らかに前の2つよりよい.工場に入ったバッファに相当します.ここでは論理的な処理をすることができます.

4.4:抽出と書き換え


この方法を使用する手順は、次のとおりです.
テスト対象クラス:
  • 本物の虚工場に戻る方法を追加します.
  • 通常のコードでファクトリメソッド
  • を使用
    テスト項目:
  • 新しいクラス
  • を作成
  • この新しいクラス継承被試験クラス
  • を宣言する
  • 置換するインタフェースタイプの共通フィールド(属性は不要)
  • を作成します.
  • ダミー書き換え方法
  • は、共通フィールド
  • を返す
    テストコード:
  • は、ルートクラスのインスタンスを作成します.このスタブ実装に必要なインタフェース
  • テストクラスではなく新しい派生クラスを作成するインスタンス
  • 工場の方法を偽造する
    public class UserBll
        {        
            public bool IsExistUser(string userId)
            {            var user = UserManager();
                    return user.IsExist(userId);                     
            }
    
            protected virtual IUser UserManager()
            {        
                return new User();
            }

    新しいクラスを作成し、テスト対象クラスを統合
    public class TestUser : UserBll
        {
            public TestUser(IUser user) {
                _muser = user;
            }
    
            private readonly IUser _muser;
    
            protected override IUser UserManager() {
                return _muser;
            }        
        }

    テストコード:
            [Test]
            public void IsGetName_NormalGetName_ReturnsTrue() {
                var fackUser = new FackUser { WillBevalid = true };// 
                var testUser = new TestUser(fackUser);// ( )
                bool result = testUser.IsExistUser("1");
                Assert.IsTrue(result);
            }

    抽出と書き換え注入に関するまとめ:より少ないインタフェースを書くと、コードが置き換えやすくなります.この方法が一番いいと思います.テストだけでなく、いつかこのコードが悪いことに気づいたら、下の階に新しい置換を追加すればいいと思います.元のコードには影響しません.
    いつ使用するか:外部依存項目を呼び出したときに自分の望む値をシミュレートしたい場合に特に使用されます.

    4.5:再構築技術変種


    まず被験クラスを見て
    public class UserBll
        {        
            public bool IsExistUser(string userId)
            {
    return UserManager(userId);         
            }
    
            protected virtual bool UserManager(string userId)
            {
                IUser user = new User();
                return user.IsExist(userId);
            }        
        }

    新しいクラスを作成し、テスト対象クラスを統合
    public class TestUser : UserBll
        {    
            public bool IsSupported;
            protected bool IsGetUserName(string userId) {
                return IsSupported;
            }
        }

    テストクラス
    public void IsGetName_NormalGetName_ReturnsTrue() {
                var testUser = new TestUser { IsSupported = true };
                bool result = testUser.IsExistUser("1");
                Assert.IsTrue(result);
            }

    まとめ:これは前の方法と似ていますが、これはもっと徹底しています.この方法はもっと簡単です.多くのコンストラクション関数、設定方法、またはファクトリクラスは追加されていません.しかし、オブジェクト向けのパッケージの原則には合致しません.ユーザーが見たものを変えないことが明らかになった.
    様々な依存注入を柔軟に使用します.個人的には後の3種類がいいと思います.