0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-06-20

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

追記: (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 にリポジトリ配置済み。ご利用はご自由にどうぞ。

プロジェクト構成

アプリ本体

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

ユーザー(名前、権限コード を所有)が何人か存在し
サービスが認可した権限コードを所有したユーザーを表示する
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.csAuthorizedUserList()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().Count0 になる。

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)

0
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?