在用 C# 開發的方法或類別時,可以用存取修飾詞來限制存取範圍層級,例如用 internal 來修飾某個類別只能讓相同專案的程式碼使用,而不開放給其他專案使用,不過 internal 也造成了無法讓單元測試專案直接使用該方法或類別,這時候可以怎麼處理呢?

存取修飾詞

先來簡單複習一下存取修飾詞的用法,詳請參考官方說明文件

C# Class 存取範圍

已宣告存取範圍 意義
public 未限制存取
private 存取限於包含類型
protected 存取限於包含類別或衍生自包含類別的類型
internal 存取限於目前組件
protected internal 存取限於目前組件或衍生自包含類別的類型
private protected 存取限於目前組件內包含類別或衍生自包含類別的類型(自 C# 7.2 起可用)

如何測試 internal 的類別

internal 的存取範圍僅限於當前的組件,也就是該程式碼所在的專案下,而在撰寫單元測試的時候,都會額外建立一個單元測試專案,再將要測試的對象參考近來,這也就讓單元測試專案”看不見”有標示 internal 的存取類別或方法。

若要讓這個有 internal 類別或方法的測試對象,讓它能讓單元測試專案”看見”,方法其實很簡單,開啟測試對象的 .csproj 專案檔,在裡面加上下列這段程式碼:

<ItemGroup>
    <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
      <_Parameter1>MyUnitTestProject.Test</_Parameter1>
    </AssemblyAttribute>
</ItemGroup>

這段是設定要開放被標示為 internal 存取範圍的類別或方法,要給 MyUnitTestProject.Test 這個專案存取,因此這裡我們只要改成我們的單元測試專案名稱即可。

大多時候,單元測試專案會和測試對象名稱長很像,通常專案名稱就後面加個 .Test 後綴,為了讓這段程式碼更為通用,你可以使用 $(MSBuildProjectName) 這個 MSBuild 保留的屬性變數,讓建置時自動去使用編譯專案名稱去替換,較為通用的程式碼如下:

<ItemGroup>
    <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
      <_Parameter1>$(MSBuildProjectName).Test</_Parameter1>
    </AssemblyAttribute>
</ItemGroup>

詳細一點

上面對 .csproj 專案檔的修改,在建置時會產生一個 AssemblyInfo.cs 檔案,並且包含以下內容:

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("MyUnitTestProject.Test")]

這段的作用是影響 CLR (Common Language Runtime) 在處理 internal 存取範圍時,讓 MyUnitTestProject.Test 可以看的到那些類別,更多資訊可以參考 System.Runtime.CompilerServices 官方文件

了解背後的運作後,我們其實也可以手動加入 Assembly Information File,也就是 AssemblyInfo.cs 檔案到專案中,手動加入上面的程式碼內容,也會有一樣的效果。

另外,如果你發現加了上面的設定還是沒有效果了話,可以檢查看看 .csproj 專案檔中,是否做了下面這種設定,這會設定建置時不產生 AssemblyInfo.cs 檔案。

<PropertyGroup>
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>

參考資料:


Poy Chang

Trial and Error