如何自动执行对象状态的单元测试

本文关键字:单元测试 状态 对象 何自动 执行 | 更新日期: 2023-09-27 18:36:16

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

[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 代码,使用存根和模拟测试 MonoBehaviors 几乎是不可能的。因此,我创建了创建假对象的辅助函数:

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;
}

您将如何自动化此过程?

是的,在一个测试中两个断言是一种不好的做法,但你会怎么做?

SaveForWeb() 的示例测试

[Test]
public void SaveForWebTest_CreateFakeGameStateObjectRunTheFunctionAndCheckIfLongestChainKeyExists_PassesIfLongestChainKeyExistsInPlayerPrefs()
{
    // arrange
    saveLoadObject = CreateFakeSaveLoadObject();
    // act
    saveLoadObject.SaveForWeb();
    // assert
    Assert.True(PlayerPrefs.HasKey(Helper.LONGEST_CHAIN_KEY));
    Assert.AreEqual(saveLoadObject.longestChain, PlayerPrefs.GetInt(Helper.LONGEST_CHAIN_KEY, Helper.DEFAULT_LONGEST_CHAIN));
    GameObject.DestroyImmediate(gameStateObject);
}

如何自动执行对象状态的单元测试

由于 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));
}

注意:默认值和帮助程序字段的长度与我在使用 ResetGameState() 后检查帮助程序字段是否具有默认值的长度相同。虽然这个答案是关于ResetGameState()而不是SaveForWeb()函数的,但只要有可能,就可以应用这段代码。