2016-03-01 2 views
0

У меня есть код моей функции 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.

Вот класс под тест:

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

Примечание: я удалил UpdateGameState() из Save(), и теперь она является как SaveForWeb() и SaveForX86().

+1

Следует ли сохранять файлы SaveForWeb и SaveForX86, или они могут быть закрыты и протестированы с помощью метода сохранения? Должен ли UpdateGameState вызываться из Save, кажется, что Saving and Updating - это две совершенно разные вещи ... Вы * можете * вывести логику в другой класс, а затем издеваться над ее взаимодействиями. Вы * можете * извлечь часть логики из ваших других тестов в общие методы, которые вызываются обоими тестами. Многое зависит от преимуществ различных подходов и количества кода (теста/производства), о котором вы на самом деле говорите ... – forsvarir

ответ

-1

В соответствии с этим учебником для testing MonoBehaviors я сделал развязку функциональности 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; 
} 
+0

Возможно, создание разделенных классов для сохранения и загрузки было бы лучше, но на данный момент это выполнило эту работу. – Vlad

1

Вы не должны «удваивать» проверку своей логики, если у вас уже есть тесты для UpdateGameState(), SaveForWeb() и т. Д. Вы должны только проверять, что методы вызывается, когда заданы разные перечисления. Это означает, что сам метод Save может быть классом его собственного и принимать зависимости от одного или нескольких интерфейсов для других методов.

+0

Да, хорошее мышление. Я получил еще одну правдоподобную идею, возможно, добавить вспомогательную функцию, которая содержит только утверждения (без [Test] anotation) для каждой из функций и повторное использование этой вспомогательной функции в обоих тестах для SaveForWeb(), UpdateGameState(), SaveForX86(), а также Save()) – Vlad

+1

привет @ vlad, не могли бы вы «отметить» ответ, чтобы закрыть вопрос и помочь сохранить QA в порядке, спасибо! – Fattie

Смежные вопросы