C#でMSTESTでInternal,Privateメソッドをテストしたい

# 1. はじめに
* C#でInternalメソッドをMSTESTで単体テストしたい
* C#でPrivateメソッドをMSTESTで単体テストしたい
* MSTESTでChainingAssertionを使ってAssertを簡略化したい

# 2. 開発環境
* C#
* .NET 6
* Visual Studio 2022
* Windows 11
* ChainingAssertion (NuGet)

# 3. ChainingAssertionのインストール
* NuGetから「ChainingAssertion.Core.MSTEST」をインストールする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/37b0cc92-6974-6004-16c2-ba5fa4bde637.png)

# 4. Internalメソッドのテスト
## 4.1. テスト対象プロジェクト
* ```AssemblyInfo.cs```に1行追加する（ファイルがない場合は追加する）
    * ```[assembly: InternalsVisibleTo("テストプロジェクト名")]```
```csharp:AssemblyInfo.cs
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// このような SDK スタイルのプロジェクトの場合、以前はこのファイルで定義していたいくつかのアセンブリ属性がビルド時に自動的に追加されて、プロジェクトのプロパティで定義されている値がそれに設定されるようになりました。組み込まれる属性と、このプロセスをカスタマイズする方法の詳細については、次を参照してください:
// https://aka.ms/assembly-info-properties


// ComVisible を false に設定すると、このアセンブリ内の型は COM コンポーネントから参照できなくなります。このアセンブリ内の型に COM からアクセスする必要がある場合は、その型の
// ComVisible 属性を true に設定してください。

[assembly: ComVisible(false)]

// このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります。

[assembly: Guid("25875ee2-0be7-41d5-a31b-895339760029")]
[assembly: InternalsVisibleTo("ConsoleAppTests")]
```

## 4.2. テスト対象プロジェクト
* Internalメソッドを単体テストクラスから直接実行できる
```csharp:CalcTests.cs
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ConsoleApp;
using System;
using ChainingAssertion;

namespace ConsoleApp.Tests
{
    [TestClass()]
    public class CalcTests
    {
        [TestMethod()]
        public void AddTest()
        {
            Calc calc = new Calc();
            Assert.AreEqual(3, calc.Add(1, 2));
        }
    }
}
```

* （参考）テスト対象クラス
```csharp:Calc.cs
namespace ConsoleApp
{
    public class Calc
    {
        internal int Add(int x, int y)
        {
            return x + y;
        } 
    }
}
```
# 5. Privateメソッドのテスト
* .NET系ではPrivateObjectが使用できないため、代替クラスを作成する
```csharp:PrivateObject.cs
using System.Reflection;

public class PrivateObject
{
    private readonly object _obj;
    public PrivateObject(object obj)
    {
        _obj = obj;
    }

    public object Invoke(string methodName, params object[] args)
    {
        var type = _obj.GetType();
        var bindingFlags = BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance;
        try
        {
            return type.InvokeMember(methodName, bindingFlags, null, _obj, args) ?? "PrivateMethod UnKnown";
        }
        catch
        {
            throw;
        }
    }
}
```

* PrivateOjectを使用してPrivateメソッドを単体テストクラスから直接実行できる
```csharp
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ConsoleApp;
using System;
using ChainingAssertion;

namespace ConsoleApp.Tests
{
    [TestClass()]
    public class PrivateTests
    {
        [TestMethod]
        public void TestMethod()
        {
            var myClass = new MyClass();
            var privateObject = new PrivateObject(myClass);
            int number = (int)privateObject.Invoke("PrivateMethod",3);
            number.Is(3);
        }

        // privateメンバーを持つテスト対象クラス
        public class MyClass
        {
            private int PrivateMethod(int number)
            {
                return number;
            }
        }
    }
}
```

# 6. 参考文献

https://qiita.com/TomoProg/items/bf7250a0b8df45900d77

https://qiita.com/drafts/612d36fec693e6fa0429/edit

https://qiita.com/kojimadev/items/25b753e31e52f2d0a1a7