Search Unity

Resolved Unit Testing and assigning stuff from the project

Discussion in 'Testing & Automation' started by FlightOfOne, Dec 10, 2020.

  1. FlightOfOne

    FlightOfOne

    Joined:
    Aug 1, 2014
    Posts:
    668
    Hi,

    I recently started using the Unit test after watching one of Uncle Bob's TDD videos. This has been a life-changer for me and really loving this idea. It's really nice to know your code going to always work without any surprises. As a side effect, my new code has been much cleaner, smaller, and much, much easier to troubleshoot.

    I do have a question though. Is it possible to access project files from a test script? I want to get an audio clip from the project folder so I can test it. This is what I am doing now. And It works, but it seems like a very hacky way of doing this.

    Is there a right way to do this? what if I want to add an array or something?


    Code (CSharp):
    1.  
    2.  
    3.         [UnityTest]
    4.         public IEnumerator AudioManager_EditMode_TesterWithEnumeratorPasses()
    5.         {
    6.             AudioTestClip testClip = new GameObject("TestClip").AddComponent<AudioTestClip>();
    7.             AudioSource audioSource = new GameObject("Source1").AddComponent<AudioSource>();
    8.             AudioOptions options = new AudioOptions();
    9.  
    10.             Assert.IsTrue(testClip.main);
    11.  
    12.             SFX.Play(testClip.main, options, audioSource);
    13.             yield return new WaitForSecondsRealtime(1);
    14.      
    15.             yield return null;
    16.         }
    17.  
    clip.png

    Thanks!!
     
  2. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,659
    It's much the same as accessing assets from code in regular non-test code.

    In the Editor, you can use the AssetDatabase functions to load and use particular assets.

    In the Player, you can use things like AssetBundles/Addressables, the StreamingAssets folder, or loading specific test scenes that you've added to the build settings for test purposes (and you can write hooks that are called when building your project for testing so that you can get those things prepared, without it affecting your regular project build).
     
  3. FlightOfOne

    FlightOfOne

    Joined:
    Aug 1, 2014
    Posts:
    668
    Yeah, I assumed this would work, but I was hoping maybe there's away to expose something in the inspector. For now I just created a scriptable object with an array and added that. Thanks!
     
    Smurjo likes this.
  4. joshrs926

    joshrs926

    Joined:
    Jan 31, 2021
    Posts:
    115
    I just figured out a way to have SerializeFields for Unit Tests. I'd love if someone from Unity could chime in and let me know if the way I'm doing this is a bad idea for some reason. It seems to work without error and the references persist after closing and reopening the editor. You simply have your test class inherit from ScriptableObject. Then the script asset itself can have default fields that you can drag and drop assets from the project window (not from inside a scene) into. Then tests can reference those fields. Be sure to include "using UntityEngine.TestTools" and "using NUnit.Framework" at the top. Here's an example. It includes logs for Awake, OnEnable, OnDisable, and OnDestroy to show that all are called. I suppose that means an instance is created and destroyed automatically by the test.

    Code (CSharp):
    1. using System.Collections;
    2. using UnityEngine;
    3. using UnityEngine.TestTools;
    4. using NUnit.Framework;
    5.  
    6. public class UnitTestSO : ScriptableObject
    7. {
    8.     [SerializeField] GameObject prefab;
    9.  
    10.     [Test]
    11.     public void PrefabExists()
    12.     {
    13.         Debug.Log(prefab.name);
    14.         Assert.That(prefab);
    15.     }
    16.  
    17.     [UnityTest]
    18.     public IEnumerator InstantiateAndMovePrefab()
    19.     {
    20.         var go = Instantiate(prefab);
    21.         for (int i = 0; i < 3; i++)
    22.         {
    23.             Assert.That(go);
    24.             yield return null;
    25.         }
    26.         DestroyImmediate(go, false);
    27.         Assert.That(!go);
    28.     }
    29.  
    30.     private void Awake()
    31.     {
    32.         Debug.Log("test awake");
    33.     }
    34.     private void OnEnable()
    35.     {
    36.         Debug.Log("test onenable");
    37.     }
    38.     private void OnDisable()
    39.     {
    40.         Debug.Log("test ondisable");
    41.     }
    42.     private void OnDestroy()
    43.     {
    44.         Debug.Log("test ondestroy");
    45.     }
    46. }
    Edit:
    Actually I did end up having some serialization issues. It was a while ago, but maybe it was that non UnityEngine.Object classes/structs didn't work. I don't quite remember. I ended up just using a singleton scriptable object asset with the fields I needed. Here is the code I used to make that. I know Unity provides a ScriptableSingleton but I believe it is EditorOnly so I made my own.

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public abstract class SingletonSO<T> : ScriptableObject where T : SingletonSO<T>
    4. {
    5.     static T instance;
    6.     public static T Instance
    7.     {
    8.         get
    9.         {
    10.             if (!instance)
    11.             {
    12.                 LoadInstanceEditorOnly();
    13.             }
    14.             return instance;
    15.         }
    16.         set
    17.         {
    18.             if (instance && instance != value)
    19.             {
    20.                 Debug.LogError($"There should not be more than one {typeof(T)} asset in the project.");
    21.                 return;
    22.             }
    23.             instance = value;
    24.         }
    25.     }
    26.  
    27.     public virtual void Awake()
    28.     {
    29.         Instance = this as T;
    30.     }
    31.  
    32.     static void LoadInstanceEditorOnly()
    33.     {
    34. #if UNITY_EDITOR
    35.         if (!Application.isPlaying)
    36.         {
    37.             var guids = UnityEditor.AssetDatabase.FindAssets($"t:{typeof(T)}");
    38.             if (guids == null || guids.Length == 0)
    39.             {
    40.                 Debug.LogError($"Could not find asset of type {nameof(T)} in project");
    41.                 return;
    42.             }
    43.             var path = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[0]);
    44.             instance = UnityEditor.AssetDatabase.LoadAssetAtPath<T>(path);
    45.         }
    46. #endif
    47.     }
    48. }
    49.  
    Example usage:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Assertions;
    3.  
    4. [CreateAssetMenu]
    5. public class UtilitiesTestsAssets : SingletonSO<UtilitiesTestsAssets>
    6. {
    7.     [SerializeField] GameObject rootPrefabExample;
    8.     public GameObject RootPrefabExample => rootPrefabExample;
    9.     private void OnEnable()
    10.     {
    11.         Assert.IsNotNull(rootPrefabExample);
    12.     }
    13. }
    14.  
     
    Last edited: Aug 29, 2022
  5. OwenSignaturize

    OwenSignaturize

    Joined:
    Apr 4, 2019
    Posts:
    1
    This is insanely useful thanks!
     
  6. joshrs926

    joshrs926

    Joined:
    Jan 31, 2021
    Posts:
    115
    I think that ended up not working for me. I'm curious to see if this works for you over a period of time or if you notice issues.