iT邦幫忙

4

【C#】小知識 #9 : 解決私有內部類別單元測試問題:使用 internal + AssemblyInfo.cs 與 protected+繼承,另外補充負面教材:反射Invoke方式。

c#

延續前篇【C#】小知識 #8 : 使用partial class + private內部類別,避免日後專案開發受限程度

筆者以單元測試做開發,發現使用非public內部類別雖然可以減低日後維護限制,但是沒辦法直接做單元測試,因為可見度問題。

20190129152804-image.png

雖然單元測試的意義以及封裝的用意"用戶原本就不需要了解,非public的行為",基本上我們不太希望把不該開放的類別或成員給外部的組件存取,但為了測試專案,不開放存取權限又很難做事,想要解決這問題:


舉例: 現在有一個類別裡面再包覆私有內部工具類別,想要做單元測試

public class MyClass
{
	private class NestedClass
	{
		private static string CommonConcat(params string[] paras)
		{
			return $"{para1},{para2}";
		}
	}
}

【一般作法1】internal + AssemblyInfo.cs 添加 [assembly: InternalsVisibleTo("單元測試專案名稱")]

邏輯:

  1. 在專案添加AssemblyInfo.cs
  2. cs檔內容添加[assembly:System.Runtime.CompilerServices.InternalsVisibleTo(測試專案名稱)]
  3. 將內部類別改為internal

結果就可以看到單元測試能取得訪問權限,如圖片
20190129214843-image.png

【一般作法2】改為protected權限

邏輯:放寬准許protected使繼承類別可以使用,如以下代碼
注意:不能測試實體類別,因為不能繼承。

namespace UnitTestProject
{
    public class MyClass
    {
        protected class NestedClass
        {
            public static string CommondConcat(params string[] paras)
            {
                return string.Join(",", paras);
            }
        }
    }

    [TestClass]
    public class TestClass : MyClass
    {
        [TestMethod]
        public void CommondConcatTest()
        {
            var excepted = "Hello,ITHelp";
            var result = MyClass.NestedClass.CommondConcat("Hello", "ITHelp");
            Assert.AreEqual(excepted, result);
        }
    }
}

20190129170553-image.png

【反面教材】反射Invoke直接呼叫私有方法

原先我是使用此方式,藉此直接呼叫非公開的內部類別,但是經過91老師指點才知道錯得離譜。
這違反測試的核心意義:『應該以使用實際情境去考量』,使用者正常不會使用反射來呼叫該方式,這樣只會造成誤導、可讀性差。

另外如以下Code,可以發現呼叫方式是使用弱維護字串,所以方法改名或是刪除無法在編譯前檢查,這樣會造成後續維護困擾。

public static class NestedTypeHelper
{
	public static object CallStaticNestedTypeMethod(System.Type type, string nestedClassName, string methodName, object[] parameters = null)
	{
		var nestedType = type.GetTypeInfo().DeclaredNestedTypes.SingleOrDefault(w => w.Name == nestedClassName);
		var methodInfo = nestedType.DeclaredMethods.SingleOrDefault(w => w.Name == methodName);
		return methodInfo.Invoke(null, parameters);
	}
}
var result = CallStaticNestedTypeMethod(type: typeof(MyStaticObject), nestedClassName: "MyClass",
		methodName: "CommonConcat", parameters: new[] { "Hello","ITHelp" });
Console.WriteLine(result); //結果:Hello,ITHelp

最後補上91老師的測試概念連結 : 連結請點擊 , 推薦讀者閱讀。


尚未有邦友留言

立即登入留言