Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/http/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何自动化对象状态的单元测试?_C#_Unit Testing_Unity3d_Nunit_Nsubstitute - Fatal编程技术网

C# 如何自动化对象状态的单元测试?

C# 如何自动化对象状态的单元测试?,c#,unit-testing,unity3d,nunit,nsubstitute,C#,Unit Testing,Unity3d,Nunit,Nsubstitute,我有一个可序列化的类,它是用于持久化游戏数据的类 [Serializable] class GameData { public float experience = Helper.DEFAULT_EXPERIENCE; public float score = Helper.DEFAULT_SCORE; public float winPercent = Helper.DEFAULT_WIN_PERCENT; public int tasksSolved = He

我有一个可序列化的类,它是用于持久化游戏数据的类

[Serializable]
class GameData
{
    public float experience = Helper.DEFAULT_EXPERIENCE;
    public float score = Helper.DEFAULT_SCORE;
    public float winPercent = Helper.DEFAULT_WIN_PERCENT;
    public int tasksSolved = Helper.DEFAULT_NUM_OF_TASKS_SOLVED;
    public int correct = Helper.DEFAULT_NUM_OF_CORRECT;
    public int additions = Helper.DEFAULT_NUM_OF_ADDITIONS;
    public int subtractions = Helper.DEFAULT_NUM_OF_SUBTRACTIONS;

    public bool useAddition = Helper.DEFAULT_USE_ADDITION;
    public bool useSubtraction = Helper.DEFAULT_USE_SUBTRACTION;
    public bool useIncrementalRange = Helper.DEFAULT_USE_INCREMENTAL_RANGE;
    public bool gameStateDirty = Helper.DEFAULT_GAME_STATE_DIRTY;
    public bool gameIsNormal = Helper.DEFAULT_GAME_IS_NORMAL;
    public bool operandsSign = Helper.DEFAULT_OPERANDS_SIGN;
}
使用此可序列化类的类如下所示:

using UnityEngine;
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

public class SaveLoadGameData : MonoBehaviour
{
    public static SaveLoadGameData gameState;

    public float experience = Helper.DEFAULT_EXPERIENCE;
    public float score = Helper.DEFAULT_SCORE;
    public float winPercent = Helper.DEFAULT_WIN_PERCENT;
    public int tasksSolved = Helper.DEFAULT_NUM_OF_TASKS_SOLVED;
    public int correct = Helper.DEFAULT_NUM_OF_CORRECT;
    public int additions = Helper.DEFAULT_NUM_OF_ADDITIONS;
    public int subtractions = Helper.DEFAULT_NUM_OF_SUBTRACTIONS;

    public bool useAddition = Helper.DEFAULT_USE_ADDITION;
    public bool useSubtraction = Helper.DEFAULT_USE_SUBTRACTION;
   public bool useIncrementalRange = Helper.DEFAULT_USE_INCREMENTAL_RANGE;
    public bool gameStateDirty = Helper.DEFAULT_GAME_STATE_DIRTY;
    public bool gameIsNormal = Helper.DEFAULT_GAME_IS_NORMAL;
    public bool operandsSign = Helper.DEFAULT_OPERANDS_SIGN;

    void Awake () {}

    public void init () 
    {
        if (gameState == null)
        {
            DontDestroyOnLoad(gameObject);
            gameState = this;
        }
        else if (gameState != this)
        {
            Destroy(gameObject);
        }
    }

    public void SaveForWeb () 
    {
        UpdateGameState();
        try
        {
            PlayerPrefs.SetFloat(Helper.EXP_KEY, experience);
            PlayerPrefs.SetFloat(Helper.SCORE_KEY, score);
            PlayerPrefs.SetFloat(Helper.WIN_PERCENT_KEY, winPercent);
            PlayerPrefs.SetInt(Helper.TASKS_SOLVED_KEY, tasksSolved);
            PlayerPrefs.SetInt(Helper.CORRECT_ANSWERS_KEY, correct);
            PlayerPrefs.SetInt(Helper.ADDITIONS_KEY, additions);
            PlayerPrefs.SetInt(Helper.SUBTRACTIONS_KEY, subtractions);

            PlayerPrefs.SetInt(Helper.USE_ADDITION, Helper.BoolToInt(useAddition));
            PlayerPrefs.SetInt(Helper.USE_SUBTRACTION, Helper.BoolToInt(useSubtraction));
            PlayerPrefs.SetInt(Helper.USE_INCREMENTAL_RANGE, Helper.BoolToInt(useIncrementalRange));
            PlayerPrefs.SetInt(Helper.GAME_STATE_DIRTY, Helper.BoolToInt(gameStateDirty));
            PlayerPrefs.SetInt(Helper.OPERANDS_SIGN, Helper.BoolToInt(operandsSign));

            PlayerPrefs.Save();
        }
        catch (Exception ex)
        {
            Debug.Log(ex.Message);
        }

    }

    public void SaveForX86 () {}

    public void Load () {}

    public void UpdateGameState () {}

    public void ResetGameState () {}
}
注意:GameData与SaveLoadGameData类位于同一文件中

正如您所看到的,GameData类有很多东西,在SaveLoadGameData类中为每个函数创建测试是一个漫长而乏味的过程。我必须为GameData中的每个属性创建一个假对象,并测试SaveLoadGameData中函数的功能—它们是否执行了它们应该执行的操作

注意:这是Unity3D 5代码,使用存根和模拟测试单行为几乎是不可能的。因此,我创建了创建伪对象的辅助函数:

SaveLoadGameData saveLoadObject;
GameObject gameStateObject;

SaveLoadGameData CreateFakeSaveLoadObject ()
{
    gameStateObject = new GameObject();
    saveLoadObject = gameStateObject.AddComponent<SaveLoadGameData>();
    saveLoadObject.init();

    saveLoadObject.experience = Arg.Is<float>(x => x > 0);
    saveLoadObject.score = Arg.Is<float>(x => x > 0);
    saveLoadObject.winPercent = 75;
    saveLoadObject.tasksSolved = 40;
    saveLoadObject.correct = 30;
    saveLoadObject.additions = 10;
    saveLoadObject.subtractions = 10;

    saveLoadObject.useAddition = false;
    saveLoadObject.useSubtraction = false;
    saveLoadObject.useIncrementalRange = true;
    saveLoadObject.gameStateDirty = true;
    saveLoadObject.gameIsNormal = false;
    saveLoadObject.operandsSign = true;

    return saveLoadObject;
}

由于Helper是只包含公共常量的静态类,我必须使用BindingFlags.static和BindingFlags.public来迭代其成员,因此我使用此代码段来自动断言不同类型的几个字段:

FieldInfo[] helperFields = typeof(SaveLoadGameData).GetFields();
FieldInfo[] defaults = typeof(Helper).GetFields(BindingFlags.Static | BindingFlags.Public);
for(int i = 0; i < defaults.Length; i += 1)
{
    Debug.Log(helperFields[i].Name + ", " + helperFields[i].GetValue(saveLoadObject) + ", " + defaults[i].GetValue(null));
    Assert.AreEqual(helperFields[i].GetValue(saveLoadObject), defaults[i].GetValue(null));
}
FieldInfo[]helperFields=typeof(SaveLoadGameData).GetFields();
FieldInfo[]默认值=typeof(Helper).GetFields(BindingFlags.Static | BindingFlags.Public);
对于(int i=0;i
注意:使用ResetGameState()后,默认值和helperFields的长度与我检查helperFields是否具有默认值的长度相同。
虽然这个答案是关于ResetGameState()而不是SaveForWeb()函数,但只要有可能,就可以应用这段代码。

因为Helper是只包含公共常量的静态类,所以我必须使用BindingFlags.static和BindingFlags.public来迭代其成员,因此,我使用此代码片段对不同类型的几个字段进行自动断言:

FieldInfo[] helperFields = typeof(SaveLoadGameData).GetFields();
FieldInfo[] defaults = typeof(Helper).GetFields(BindingFlags.Static | BindingFlags.Public);
for(int i = 0; i < defaults.Length; i += 1)
{
    Debug.Log(helperFields[i].Name + ", " + helperFields[i].GetValue(saveLoadObject) + ", " + defaults[i].GetValue(null));
    Assert.AreEqual(helperFields[i].GetValue(saveLoadObject), defaults[i].GetValue(null));
}
FieldInfo[]helperFields=typeof(SaveLoadGameData).GetFields();
FieldInfo[]默认值=typeof(Helper).GetFields(BindingFlags.Static | BindingFlags.Public);
对于(int i=0;i
注意:使用ResetGameState()后,默认值和helperFields的长度与我检查helperFields是否具有默认值的长度相同。
虽然这个答案是关于ResetGameState()而不是SaveForWeb()函数,但只要可能,这段代码都可以应用。

我很困惑。
GameData
SaveLoadGameData
的关系如何?行
saveLoadObject.experience
如何编译
SaveLoadGameData
没有名为
experience
的属性,或者我遗漏了什么?在您的
[Test]
中,为
助手设置一个先验值有什么意义。最长链键
默认最长链
(预期值)如果在下一行中,您要求Unity查找,如果不存在,则返回
DEFAULT\u LONGEST\u CHAIN
。这是糟糕的测试设计,你不知道实际的结果是你的代码还是统一的结果。如果要将这两个断言分为两个测试,我需要对每个属性进行额外的24个测试,以测试PlayerPrefs中的属性是否等于内存中的属性(SaveLoadObject)。第一个断言检查PlayerPref是否包含该属性,下一个断言检查是否相等。但是你对这两个断言的看法是对的,它们应该是一个单独的测试。对单个属性进行单元测试是没有意义的。单元测试用于测试行为或编排,而不是原子状态。现在您可以为“saveforweb”场景编写一个单元测试,它不仅包括save机制,还包括state。这更有意义。当人们觉得他们必须为每一个公共方法编写一个测试时,情况也是如此——你不需要。您只需要努力获得高代码覆盖率就可以了。你也可以使用反射。但是通常你希望你的测试尽可能简单,否则你需要一个测试来测试:)我很困惑。
GameData
SaveLoadGameData
的关系如何?行
saveLoadObject.experience
如何编译
SaveLoadGameData
没有名为
experience
的属性,或者我遗漏了什么?在您的
[Test]
中,为
助手设置一个先验值有什么意义。最长链键
默认最长链
(预期值)如果在下一行中,您要求Unity查找,如果不存在,则返回
DEFAULT\u LONGEST\u CHAIN
。这是糟糕的测试设计,你不知道实际的结果是你的代码还是统一的结果。如果要将这两个断言分为两个测试,我需要对每个属性进行额外的24个测试,以测试PlayerPrefs中的属性是否等于内存中的属性(SaveLoadObject)。第一个断言检查PlayerPref是否包含该属性,下一个断言检查是否相等。但是你对这两个断言的看法是对的,它们应该是一个单独的测试。对单个属性进行单元测试是没有意义的。单元测试用于测试行为或编排,而不是原子状态。现在您可以为“saveforweb”场景编写一个单元测试,它不仅包括save机制,还包括state。这更有意义。当人们觉得他们必须为每一个公共方法编写一个测试时,情况也是如此——你不需要。您只需要努力获得高代码覆盖率就可以了。你也可以使用反射。但通常您希望测试尽可能简单,否则您需要测试来测试测试:)