[英]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.