2015年10月14日 星期三

使用NSubstitute輔助單元測試及TDD(2)

 NSubstitute   C#  

背景

本篇練習NSubstitute的重點在於:
1.  Unit Test中,如何Mock一些原始專案讀取內部資源(例如AppConfig)的方式。
2.  搭配Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject,撰寫更有效率的測試代碼。

以先前寫的WebHook程式碼,加上單元測試為例。
這個專案的其中一個類別 PostCallbackRepositor提供一個方法

/// <summary>
/// 取得要Post CallbackClient Uri
/// <para>有效的Client Uri必須符合以下條件:</para>
/// <para>1.資料庫註記IsEnabled==true</para>
/// <para>2.不在Config設定的IP黑名單中</para>
/// </summary>
/// <returns>WebHookCallback list</returns>
public IEnumerable<WebHookCallback> GetClientUris()

所以在測試這個方法時,我們至少需要利用NSubstitute建立兩個Mock objects
l   存取資料庫
l   存取Config

以正確的測試此方法。




相關文章



實作


Verify the codes which you wanna test


上面提到我們要測試的程式碼如下:

public class PostCallbackRepository:IDisposable
{
        private IAppconfigManager appconfigManagerNsub;
        private IWebHookCallbackService whCallbackServiceNsub;


        public PostCallbackRepository()
        {
            this.appconfigManagerNsub = new AppconfigManager();
            this.whCallbackServiceNsub = new WebHookCallbackService();
        }

        /// <summary>
        /// 取得要Post CallbackClient Uri
        /// <para>有效的Client Uri必須符合以下條件:</para>
        /// <para>1.資料庫註記IsEnabled==true</para>
        /// <para>2.不在Config設定的IP黑名單中</para>
        /// </summary>
        /// <returns>WebHookCallback list</returns>
        public IEnumerable<WebHookCallback> GetClientUris()
        {
            var entities = whCallbackServiceNsub.Get(
                x => !String.IsNullOrEmpty(x.Uri) &&
                x.IsEnabled == true).ToList();

            for (int i= (entities.Count-1); i>=0; i--)
            {
                if(this.chkIfLockedIp(entities[i].Uri))
                {
                    entities.Remove(entities[i]);
                }
            }

            return entities.AsEnumerable();
        }

        private bool chkIfLockedIp(String uri)
        {
            foreach(var lockedIp in appconfigManagerNsub.LockIPs)
            {
                if (uri.IndexOf(lockedIp) >= 0)
                     return true;
            }

            return false;
        }

        public void Dispose()
        {
            this.appconfigManagerNsub = null;
            this.whCallbackServiceNsub = null;
        }
}

PS. 在讀取Config的設定值這一段,如果沒有建立一個Interface來處理,則無法使用NSubstitute來處理,這邊是比較可能需要重構的地方。






By the way,
以下是資料表結構和Config檔的參考








Unit Test

如前述,在測試代碼中,只要針對DAL和讀取Configinterfaces建立NSub Mock objects,並利用Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject注入, 即可順利進行測試。
相關說明可直接參考程式碼註解。

[TestMethod]
public void TestTriggerCallback()
{
            #region Create the Substitutes
            //Create the Nsubs for the interfaces
            var appconfigManagerNsub = Substitute.For<IAppconfigManager>();
            var whCallbackServiceNsub = Substitute.For<IWebHookCallbackService>();
            #endregion

            #region Set the return value of the mock objects

            var mockupClientUris = new List<WebHookCallback>() {
                    new WebHookCallback() { WebHookCallbackId= 1, IsEnabled=true, Uri= "http://10.1.2.3:13050/api/Receive/Receive1" },
                    new WebHookCallback() { WebHookCallbackId= 2, IsEnabled=true, Uri= "http://10.4.5.6:13050/api/Receive/Receive1" },
                    new WebHookCallback() { WebHookCallbackId= 3, IsEnabled=true, Uri= "http://localhost:13050/api/Receive/Receive1" },
                    new WebHookCallback() { WebHookCallbackId= 4, IsEnabled=true, Uri= "http://localhost:13050/api/Receive/Receive2" },
                    new WebHookCallback() { WebHookCallbackId= 5, IsEnabled=true, Uri= "http://localhost:13050/api/Receive/Receive3" },
                };
           
            whCallbackServiceNsub.Get(
Arg.Any<Func<WebHookCallback, Boolean>>()).Returns(
                mockupClientUris.AsQueryable());


            appconfigManagerNsub.LockIPs.Returns(new String[] { "10.1.2.3", "10.4.5.6" });
            #endregion


            #region Intialize the test target
            var pcbRepository =
                new JB.Sample.WebHook.WebApi.PostCallbackRepository();

            PrivateObject accessor = new PrivateObject(pcbRepository);
            accessor.SetField("appconfigManagerNsub", appconfigManagerNsub);
            accessor.SetField("whCallbackServiceNsub", whCallbackServiceNsub);
            #endregion


            #region Test Codes
            IEnumerable<WebHookCallback> actualWhCallbacks = pcbRepository.GetClientUris().OrderBy(x=>x.WebHookCallbackId);
            var expectedCnt = 3;
            Assert.AreEqual(expectedCnt, actualWhCallbacks.Count());
            Assert.AreEqual(actualWhCallbacks.ElementAt(0).Uri, mockupClientUris[2].Uri);
            Assert.AreEqual(actualWhCallbacks.ElementAt(1).Uri, mockupClientUris[3].Uri);
            Assert.AreEqual(actualWhCallbacks.ElementAt(2).Uri, mockupClientUris[4].Uri);
            #endregion

        }

測試結果 :







Reference



沒有留言:

張貼留言