NUnit で private static メンバ変数 を stub する方法を検討(具体例あり)

リフレクション 以外に方法ありますかね。ご存知の方お手すきにご教示頂ければ。

追記: (Expression でもできるのでは、と教えていただく。知識が追い付いていないので記事には未反映)

# 完成形の一例(テストコード)

```
using System.Reflection;
using UnitTestStaticMember.Model;

namespace NUnit.Reflection
{
    [TestFixture]
    public class UserManagerTest
    {
        [Test]
        public void AuthorizedUserList()
        {
            // ここからリフレクション
            // private static メンバ変数に割り当てる値を用意
            var authzCode = new List<AuthorityCode>() { AuthorityCode.S, AuthorityCode.A };
            // 値割り当てする private static メンバ変数を FieldInfo で参照する
            FieldInfo authorizedCode = typeof(ServiceManager).GetField("authorizedCode", BindingFlags.NonPublic | BindingFlags.Static);
            // メンバ変数、メンバ変数を保持しているクラス、割り当てたい値、それぞれを関連付ける
            authorizedCode.SetValue(new ServiceManager(), authzCode);
            // ここまでリフレクション

            var user1 = Substitute.For<IUser>();
            user1.AuthCode.Returns(AuthorityCode.A);
            var userList = new List<IUser>() { user1 };
            var userManger = new UserManager(userList);

            Assert.AreEqual(1, userManger.AuthorizedUserList().Count);
        }
    }
}
```

# 例題プロジェクト

```
ユーザー(名前、権限コード を所有)が何人か存在し
サービスが認可した権限コードを所有したユーザーを表示する
```

というコンソールアプリを示す。

github にリポジトリ配置済み。ご利用はご自由にどうぞ。

https://github.com/ymatsukawaTLDR/cs_nunit_sample_reflection_static

## プロジェクト構成

### アプリ本体

以下記載通りで特記なし。組み込まれているクラスを追っていく。

```
ユーザー(名前、権限コード を所有)が何人か存在し
サービスが認可した権限コードを所有したユーザーを表示する
```

```Program.cs
using UnitTestStaticMember.Model;

namespace UnitTestStaticMember
{
    class Program
    {
        static void Main(string[] args)
        {
            // サービス情報を初期化する
            // Init 内で "認可された 権限コード(AuthorityCode) のリスト" を初期化する
            ServiceManager.Init();

            // User(Name, AuthorityCode) のリストを作成する(userManager に渡すため)
            var users = new List<IUser>()
            {
                new User("foo", AuthorityCode.A), new User("bar", AuthorityCode.B)
            };

            // サービスにて認可された 権限コード(AuthorityCode) を所有したユーザーだけを表示する
            // プロジェクト実装上 User:"foo" だけが表示される
            var userManager = new UserManager(users);
            var authzUserList = userManager.AuthorizedUserList();
            foreach(var authzUList in authzUserList)
            {
                System.Console.WriteLine(authzUList.Name);
            }
        }
    }
}
```

### サービスのデータ管理クラス

```Init()``` により初期化したデータを ```static メンバ変数``` に突っ込んでいく。

```authorizedCode``` は後出ユーザー管理クラス ```UserManager``` 内の機能で利用する。

```Model/ServiceManager.cs
namespace UnitTestStaticMember.Model
{
    public class ServiceManager
    {
        /// <summary>
        /// アプリ立ち上げ時に実行するメソッド
        /// 初期化処理を突っ込む
        /// </summary>
        public static void Init()
        {
            InitServiceAuthorizedCode();
        }

        /// <summary>
        /// 初期化した static 変数を取得するメソッド
        /// 徒に static 変数を public へしないための対策
        /// </summary>
        /// <returns></returns>
        public static List<AuthorityCode> GetServiceAuthorizedCode()
        {
            return authorizedCode;
        }

        private static void InitServiceAuthorizedCode()
        {
            authorizedCode.Add(AuthorityCode.S);
            authorizedCode.Add(AuthorityCode.A);
        }

        private static List<AuthorityCode> authorizedCode = new List<AuthorityCode>();
    }
}
```

### 定数管理クラス

権限コードを定数化したもの。サービスのデータ管理クラスでは

```S```, ```A``` を認可した権限コードとして扱っている。

```Model/AuthorityCode.cs
namespace UnitTestStaticMember.Model
{
    public enum AuthorityCode
    {
        S, A, B, C
    }
}
```

### ユーザーモデル

一人一人のユーザーを表現するクラス。名前と権限コードを持っている。

```Model/User.cs
namespace UnitTestStaticMember.Model
{
    public interface IUser
    {
        string Name { get; }
        AuthorityCode AuthCode { get; }
    }

    public class User : IUser
    {
        public User(string name, AuthorityCode authorityCode)
        {
            this.name = name;
            this.authorityCode = authorityCode;
        }

        public string Name
        {
            get { return this.name; }
        }

        public AuthorityCode AuthCode
        {
            get { return authorityCode; }
        }

        private string name;
        private AuthorityCode authorityCode;
    }
}
```

### ユーザー管理クラス

ユーザーに関わるデータを管理するクラス。

```AuthorizedUserList()``` より

```サービスで認可された権限コードを所有したユーザーを取得する``` 機能を提供する。

```Model/UserManager.cs
namespace UnitTestStaticMember.Model
{
    public class UserManager
    {
        public UserManager(List<IUser> userList)
        {
            this.userList = userList;
        }

        /// <summary>
        /// get users they have service's authorized code; S or A.
        /// return type is list.
        /// </summary>
        public List<IUser> AuthorizedUserList()
        {
            var authzCodes = ServiceManager.GetServiceAuthorizedCode();
            return userList.Select(user => user)
                           .Where(user => authzCodes.Contains(user.AuthCode))
                           .ToList();
        }

        private List<IUser> userList;
    }
}
```

# 本題

## 単体テストを作る

```Model/UserManager.cs``` の ```AuthorizedUserList()``` を ```NUnit``` で単体テストを作る。

以下のような単体テストがひとまず考えられる。

```NUnit.Reflection/UserManagerTest.cs
namespace NUnit.Reflection
{
    [TestFixture]
    public class UserManagerTest
    {
        [Test]
        public void AuthorizedUserList()
        {
            var user1 = Substitute.For<IUser>();
            user1.AuthCode.Returns(AuthorityCode.A);
            var userList = new List<IUser>() { user1 };
            var userManger = new UserManager(userList);

            Assert.AreEqual(1, userManger.AuthorizedUserList().Count);
        }
    }
}
```

## 課題

テストを作ったは良いものの、検証してみると

```
Assert.AreEqual(1, userManger.AuthorizedUserList().Count);
```

より ```userManger.AuthorizedUserList().Count``` が ```0``` になる。

```userManger.AuthorizedUserList()``` > ```ServiceManager.GetServiceAuthorizedCode()``` が空リストを返しているからだ。

もう少し突っ込むとテスト実施前に ```ServiceManager.Init()``` していなかったからだ。

### (横道にずれて... 単体テストという観点で考えると、何を考えるべきか)

少し立ち止まって単体テストという観点では「検証する機能以外の機能」はスタブにしておきたい。例えばの話

```ServiceManager.Init()``` することで、どうしても ステージングサーバーに繋がってしまう(そういうアレな)

仕組みが出来上がっているならば ```ServiceManager.Init()``` の利用は避けたいものだ。

どうにかしてスタブできるものはスタブしよう。

## 課題の探索

* ```Model/UserManager.cs``` > ```ServiceManager.GetServiceAuthorizedCode()```　がある
* このメソッドをスタブしたい
   * ```GetServiceAuthorizedCode()``` は ```static メソッド``` だからスタブ難しそう(できるかどうか)
   * ```GetServiceAuthorizedCode()``` メソッド内で ```return``` している ```static メンバ変数``` はスタブできないか
       * ```private``` だ。どうしようか。

## 方針案

リフレクションを使って、```private static メンバ変数のデータ``` はテスト時、動的に書き換えられる様子。

これをやろう。

### テストコード案

```NUnit.Reflection/UserManagerTest.cs
using System.Reflection;
using UnitTestStaticMember.Model;

namespace NUnit.Reflection
{
    [TestFixture]
    public class UserManagerTest
    {
        [Test]
        public void AuthorizedUserListNotEmpty()
        {
            // ここからリフレクション
            // ServiceManager > authorizedCode(private static) に値割り当てる
            // 結果的に ServiceManager > GetServiceAuthorizedCode() の返却値がスタブされている
            var authzCode = new List<AuthorityCode>() { AuthorityCode.S, AuthorityCode.A };
            FieldInfo authorizedCode = typeof(ServiceManager).GetField("authorizedCode", BindingFlags.NonPublic | BindingFlags.Static);
            authorizedCode.SetValue(new ServiceManager(), authzCode);
            // ここまでリフレクション

            var user1 = Substitute.For<IUser>();
            user1.AuthCode.Returns(AuthorityCode.A);
            var userList = new List<IUser>() { user1 };
            var userManger = new UserManager(userList);

            Assert.AreEqual(1, userManger.AuthorizedUserList().Count);
        }
    }
}
```

# C# リフレクション参考ページ

[FieldInfo.SetValue メソッド (Object, Object)](https://msdn.microsoft.com/ja-jp/library/6z33zd7h(v=vs.110).aspx)