如何测试只有开关逻辑的功能?
How to test a function that has only switch logic?
我有一个 Save() 函数的代码,它里面只有 switch 语句。所以基本上它会根据所选平台进行保存。但是,我已经对 UpdateGameState()、SaveForWeb() 和 SaveForX86() 函数进行了测试。由于单元测试规则说,如果您的函数中有逻辑,无论它多么简单,您都必须测试该函数。
public void Save ()
{
switch(Helper.BUILD_TYPE)
{
case Helper.BUILD_FOR_WEB:
SaveForWeb();
break;
case Helper.BUILD_FOR_WIN_X86:
SaveForX86();
break;
default:
Debug.Log("Save method: " + Helper.WRONG_BUILD_TYPE_SELECTED_ERR);
break;
}
}
在测试中调用测试也打破了测试的隔离规则,所以我似乎必须将测试逻辑复制到我的其他测试中,只是为了检查 Save() 逻辑是否在 SaveForWeb() 和SaveForX86().
在这种情况下,您将如何测试此功能?
我可以在测试中做到这一点:
Helper.BUILD_TYPE = Helper.BUILD_FOR_WEB;
其中 BUILD_TYPE 是静态的,但不像 BUILD_FOR_WEB 和 BUILD_FOR_WIN_X86 那样恒定。
这是正在测试的 class:
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;
void Awake ()
{
Init();
}
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.Save();
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
}
public void SaveForX86 ()
{
UpdateGameState();
try
{
BinaryFormatter bf = new BinaryFormatter();
FileStream fs = File.Create(Application.persistentDataPath + Helper.GAME_DATA_FILE_NAME);
GameData data = new GameData();
data.experience = experience;
data.score = score;
bf.Serialize(fs, data);
fs.Close();
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
}
public void Save ()
{
switch(Helper.BUILD_TYPE)
{
case Helper.BUILD_FOR_WEB:
SaveForWeb();
break;
case Helper.BUILD_FOR_WIN_X86:
SaveForX86();
break;
case Helper.BUILD_FOR_ANDROID:
break;
default:
Debug.Log("Save method: " + Helper.WRONG_BUILD_TYPE_SELECTED_ERR);
break;
}
}
public void LoadForWeb ()
{
try
{
experience = PlayerPrefs.GetFloat(Helper.EXP_KEY, Helper.DEFAULT_EXPERIENCE);
score = PlayerPrefs.GetFloat(Helper.SCORE_KEY, Helper.DEFAULT_SCORE);
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
}
public void LoadForX86 ()
{
try
{
if (File.Exists(Application.persistentDataPath + Helper.GAME_DATA_FILE_NAME))
{
BinaryFormatter bf = new BinaryFormatter();
FileStream fs = File.Open(Application.persistentDataPath + Helper.GAME_DATA_FILE_NAME, FileMode.Open);
GameData data = (GameData)bf.Deserialize(fs);
experience = data.experience;
score = data.score;
fs.Close();
}
else
{
Save();
}
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
}
public void Load ()
{
switch(Helper.BUILD_TYPE)
{
case Helper.BUILD_FOR_WEB:
LoadForWeb();
break;
case Helper.BUILD_FOR_WIN_X86:
LoadForX86();
break;
case Helper.BUILD_FOR_ANDROID:
break;
default:
Debug.Log("Load method: " + Helper.WRONG_BUILD_TYPE_SELECTED_ERR);
break;
}
UpdateGameState();
}
public void UpdateGameState ()
{
gameState.experience = experience;
gameState.score = score;
}
public void ResetGameState ()
{
experience = Helper.DEFAULT_EXPERIENCE;
score = Helper.DEFAULT_SCORE;
Save();
}
}
[Serializable]
class GameData
{
public float experience = Helper.DEFAULT_EXPERIENCE;
public float score = Helper.DEFAULT_SCORE;
}
注意:我从 Save() 中删除了 UpdateGameState(),现在它是 SaveForWeb() 和 SaveForX86()。
如果您已经对 UpdateGameState()、SaveForWeb() 等进行了测试,则不应 "double" 测试您的逻辑。您应该只验证在设置不同枚举时调用的方法。这意味着 Save 方法本身可能是它自己的 class,并且依赖于其他方法的一个或多个接口。
根据 testing MonoBehaviors 的本教程,我使用单独的 class 和接口
对 MonoBehavior 功能和其他可测试功能进行了解耦
using System;
using UnityEngine;
namespace Assets.Scripts
{
/// <summary>
/// Description of ISaveLoadGameData.
/// </summary>
public interface ISaveLoadGameData
{
void SaveForWeb();
void SaveForX86();
void Save();
void UpdateGameState();
}
}
using System;
using UnityEngine;
namespace Assets.Scripts
{
/// <summary>
/// Description of SaveLoadGameDataController.
/// </summary>
[Serializable]
public class SaveLoadGameDataController : ISaveLoadGameData
{
ISaveLoadGameData slgdInterface;
GameObject gameObject;
public static SaveLoadGameDataController gameState;
public float experience = Helper.DEFAULT_EXPERIENCE;
public float score = Helper.DEFAULT_SCORE;
public void SetSaveLoadGameData (ISaveLoadGameData slgd)
{
slgdInterface = slgd;
}
public void SaveForWeb ()
{
slgdInterface.SaveForWeb();
}
public void SaveForX86 ()
{
slgdInterface.SaveForX86();
}
public void Save ()
{
slgdInterface.Save();
}
public void UpdateGameState ()
{
slgdInterface.UpdateGameState();
}
}
}
这样我就可以像这样对 Save() 函数进行干净简单的测试:
[Test]
[Category(Helper.TEST_CATEGORY_SAVE_GAME_STATE)]
public void SaveTest_SetBuildTypeToWebAndRunSave_PassesIfSaveFunctionCalledSaveForWebFunction ()
{
// arrange
Helper.BUILD_TYPE = Helper.BUILD_FOR_WEB;
var slgdController = FakeSaveLoadGameDataController();
// act
slgdController.ClearReceivedCalls();
slgdController.Save();
// assert
slgdController.Received().SaveForWeb();
}
[Test]
[Category(Helper.TEST_CATEGORY_SAVE_GAME_STATE)]
public void SaveTest_SetBuildTypeToX86AndRunSave_PassesIfSaveFunctionCalledSaveForX86Function ()
{
// arrange
Helper.BUILD_TYPE = Helper.BUILD_FOR_WIN_X86;
var slgdController = FakeSaveLoadGameDataController();
// act
slgdController.ClearReceivedCalls();
slgdController.Save();
// assert
slgdController.Received().SaveForX86();
Helper.BUILD_TYPE = Helper.BUILD_FOR_WEB;
}
FakeSaveLoadGameDataController() 看起来像这样:
SaveLoadGameDataController FakeSaveLoadGameDataController ()
{
SaveLoadGameDataController slgdController = Substitute.For<SaveLoadGameDataController>();
ISaveLoadGameData slgd = Substitute.For<ISaveLoadGameData>();
slgdController.SetSaveLoadGameData(slgd);
slgdController.experience = Arg.Is<float>(x => x > 0);
slgdController.score = Arg.Is<float>(x => x > 0);
return slgdController;
}
我有一个 Save() 函数的代码,它里面只有 switch 语句。所以基本上它会根据所选平台进行保存。但是,我已经对 UpdateGameState()、SaveForWeb() 和 SaveForX86() 函数进行了测试。由于单元测试规则说,如果您的函数中有逻辑,无论它多么简单,您都必须测试该函数。
public void Save ()
{
switch(Helper.BUILD_TYPE)
{
case Helper.BUILD_FOR_WEB:
SaveForWeb();
break;
case Helper.BUILD_FOR_WIN_X86:
SaveForX86();
break;
default:
Debug.Log("Save method: " + Helper.WRONG_BUILD_TYPE_SELECTED_ERR);
break;
}
}
在测试中调用测试也打破了测试的隔离规则,所以我似乎必须将测试逻辑复制到我的其他测试中,只是为了检查 Save() 逻辑是否在 SaveForWeb() 和SaveForX86().
在这种情况下,您将如何测试此功能?
我可以在测试中做到这一点:
Helper.BUILD_TYPE = Helper.BUILD_FOR_WEB;
其中 BUILD_TYPE 是静态的,但不像 BUILD_FOR_WEB 和 BUILD_FOR_WIN_X86 那样恒定。
这是正在测试的 class:
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;
void Awake ()
{
Init();
}
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.Save();
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
}
public void SaveForX86 ()
{
UpdateGameState();
try
{
BinaryFormatter bf = new BinaryFormatter();
FileStream fs = File.Create(Application.persistentDataPath + Helper.GAME_DATA_FILE_NAME);
GameData data = new GameData();
data.experience = experience;
data.score = score;
bf.Serialize(fs, data);
fs.Close();
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
}
public void Save ()
{
switch(Helper.BUILD_TYPE)
{
case Helper.BUILD_FOR_WEB:
SaveForWeb();
break;
case Helper.BUILD_FOR_WIN_X86:
SaveForX86();
break;
case Helper.BUILD_FOR_ANDROID:
break;
default:
Debug.Log("Save method: " + Helper.WRONG_BUILD_TYPE_SELECTED_ERR);
break;
}
}
public void LoadForWeb ()
{
try
{
experience = PlayerPrefs.GetFloat(Helper.EXP_KEY, Helper.DEFAULT_EXPERIENCE);
score = PlayerPrefs.GetFloat(Helper.SCORE_KEY, Helper.DEFAULT_SCORE);
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
}
public void LoadForX86 ()
{
try
{
if (File.Exists(Application.persistentDataPath + Helper.GAME_DATA_FILE_NAME))
{
BinaryFormatter bf = new BinaryFormatter();
FileStream fs = File.Open(Application.persistentDataPath + Helper.GAME_DATA_FILE_NAME, FileMode.Open);
GameData data = (GameData)bf.Deserialize(fs);
experience = data.experience;
score = data.score;
fs.Close();
}
else
{
Save();
}
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
}
public void Load ()
{
switch(Helper.BUILD_TYPE)
{
case Helper.BUILD_FOR_WEB:
LoadForWeb();
break;
case Helper.BUILD_FOR_WIN_X86:
LoadForX86();
break;
case Helper.BUILD_FOR_ANDROID:
break;
default:
Debug.Log("Load method: " + Helper.WRONG_BUILD_TYPE_SELECTED_ERR);
break;
}
UpdateGameState();
}
public void UpdateGameState ()
{
gameState.experience = experience;
gameState.score = score;
}
public void ResetGameState ()
{
experience = Helper.DEFAULT_EXPERIENCE;
score = Helper.DEFAULT_SCORE;
Save();
}
}
[Serializable]
class GameData
{
public float experience = Helper.DEFAULT_EXPERIENCE;
public float score = Helper.DEFAULT_SCORE;
}
注意:我从 Save() 中删除了 UpdateGameState(),现在它是 SaveForWeb() 和 SaveForX86()。
如果您已经对 UpdateGameState()、SaveForWeb() 等进行了测试,则不应 "double" 测试您的逻辑。您应该只验证在设置不同枚举时调用的方法。这意味着 Save 方法本身可能是它自己的 class,并且依赖于其他方法的一个或多个接口。
根据 testing MonoBehaviors 的本教程,我使用单独的 class 和接口
对 MonoBehavior 功能和其他可测试功能进行了解耦using System;
using UnityEngine;
namespace Assets.Scripts
{
/// <summary>
/// Description of ISaveLoadGameData.
/// </summary>
public interface ISaveLoadGameData
{
void SaveForWeb();
void SaveForX86();
void Save();
void UpdateGameState();
}
}
using System;
using UnityEngine;
namespace Assets.Scripts
{
/// <summary>
/// Description of SaveLoadGameDataController.
/// </summary>
[Serializable]
public class SaveLoadGameDataController : ISaveLoadGameData
{
ISaveLoadGameData slgdInterface;
GameObject gameObject;
public static SaveLoadGameDataController gameState;
public float experience = Helper.DEFAULT_EXPERIENCE;
public float score = Helper.DEFAULT_SCORE;
public void SetSaveLoadGameData (ISaveLoadGameData slgd)
{
slgdInterface = slgd;
}
public void SaveForWeb ()
{
slgdInterface.SaveForWeb();
}
public void SaveForX86 ()
{
slgdInterface.SaveForX86();
}
public void Save ()
{
slgdInterface.Save();
}
public void UpdateGameState ()
{
slgdInterface.UpdateGameState();
}
}
}
这样我就可以像这样对 Save() 函数进行干净简单的测试:
[Test]
[Category(Helper.TEST_CATEGORY_SAVE_GAME_STATE)]
public void SaveTest_SetBuildTypeToWebAndRunSave_PassesIfSaveFunctionCalledSaveForWebFunction ()
{
// arrange
Helper.BUILD_TYPE = Helper.BUILD_FOR_WEB;
var slgdController = FakeSaveLoadGameDataController();
// act
slgdController.ClearReceivedCalls();
slgdController.Save();
// assert
slgdController.Received().SaveForWeb();
}
[Test]
[Category(Helper.TEST_CATEGORY_SAVE_GAME_STATE)]
public void SaveTest_SetBuildTypeToX86AndRunSave_PassesIfSaveFunctionCalledSaveForX86Function ()
{
// arrange
Helper.BUILD_TYPE = Helper.BUILD_FOR_WIN_X86;
var slgdController = FakeSaveLoadGameDataController();
// act
slgdController.ClearReceivedCalls();
slgdController.Save();
// assert
slgdController.Received().SaveForX86();
Helper.BUILD_TYPE = Helper.BUILD_FOR_WEB;
}
FakeSaveLoadGameDataController() 看起来像这样:
SaveLoadGameDataController FakeSaveLoadGameDataController ()
{
SaveLoadGameDataController slgdController = Substitute.For<SaveLoadGameDataController>();
ISaveLoadGameData slgd = Substitute.For<ISaveLoadGameData>();
slgdController.SetSaveLoadGameData(slgd);
slgdController.experience = Arg.Is<float>(x => x > 0);
slgdController.score = Arg.Is<float>(x => x > 0);
return slgdController;
}