简体   繁体   English

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

[英]How to automate unit testing of object state?

I have this serializable class which is my class for persisting game data. 我有这个可序列化的类,这是我保留游戏数据的类。

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

The class that utilizes this serializable class looks like this: 利用此可序列化类的类如下所示:

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 () {}
}

Note: GameData is inside the same file with SaveLoadGameData class. 注意:GameData与SaveLoadGameData类位于同一文件内。

As you can see GameData class has ton of stuff and creating test for each function inside SaveLoadGameData class is long and boring process. 如您所见,GameData类有很多东西,为SaveLoadGameData类内部的每个函数创建测试是漫长而枯燥的过程。 I have to create a fake object for each property inside GameData and test the functionality of the functions in SaveLoadGameData do they do what they are supposed to do. 我必须为GameData中的每个属性创建一个伪造的对象,并测试SaveLoadGameData中的功能是否执行了应做的工作。

Note: This is Unity3D 5 code and testing MonoBehaviors with stubs and mocks is almost immposible. 注意:这是Unity3D 5代码,使用存根和模拟程序测试MonoBehaviors几乎是不可能的。 Therefore I created helper function that creates fake object: 因此,我创建了创建虚假对象的辅助函数:

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

How would you automate this process? 您将如何自动执行此过程?

Yes two asserts inside one test is a bad practice but what would you do instead? 是的,在一个测试中有两个断言是不好的做法,但是您会怎么做呢?

Example test for SaveForWeb() 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);
}

Since Helper is static class containing only public constants I had to use BindingFlags.Static and BindingFlags.Public to iterate over its members, so I used this code snippet to automate asserting over several fields of different type: 由于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));
}

Note: defaults and helperFields have the same length as I am checking if helperFields have the default values after using ResetGameState(). 注意:defaults和helperFields的长度与我在使用ResetGameState()之后检查helperFields是否具有默认值的长度相同。 Though this answer is about ResetGameState() instead of SaveForWeb() function, this code can be applied wherever possible. 尽管此答案是关于ResetGameState()而不是SaveForWeb()函数的,但是此代码可以在任何可能的地方应用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM