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.
As you can see GameData class has ton of stuff and creating test for each function inside SaveLoadGameData class is long and boring process. 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.
Note: This is Unity3D 5 code and testing MonoBehaviors with stubs and mocks is almost immposible. 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()
[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);
}