I'm new in testing for Unity3D, and I wanted to write some tests for our game. I managed to write this tests which work fine:
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Game.Characters;
using Game.Inventory;
namespace Tests
{
public class ItemTests
{
[UnityTest]
public IEnumerator ItemIsOnHand()
{
// Setup
Player player = MonoBehaviour.Instantiate(Resources.Load<GameObject>("Tests/Player"));
yield return null; // Wait for prefab to load
Character character = player.GetComponent<Character>();
WorldItem worldItem = WorldItem.Create(Resources.Load<Item>("Items/item"), 1);
yield return null; // Wait for prefab to load
character.Inventory.Give(worldItem);
ItemScript itemScript = character.HandItemContainer.gameObject.GetComponentInChildren<ItemScript>();
// Test
Assert.IsNotNull(itemScript);
// Teardown
Object.Destroy(player);
Object.Destroy(character);
Object.Destroy(worldItem);
Object.Destroy(itemScript);
}
[UnityTest]
public IEnumerator ItemIsDisabledAtStart()
{
// Setup
Player player = MonoBehaviour.Instantiate(Resources.Load<GameObject>("Tests/Player"));
yield return null; // Wait for prefab to load
Character character = player.GetComponent<Character>();
WorldItem worldItem = WorldItem.Create(Resources.Load<Item>("Items/item"), 1);
yield return null; // Wait for prefab to load
character.Inventory.Give(worldItem);
ItemScript itemScript = character.HandItemContainer.gameObject.GetComponentInChildren<ItemScript>();
// Test
Assert.IsFalse(itemScript.IsActive);
// Teardown
Object.Destroy(player);
Object.Destroy(character);
Object.Destroy(worldItem);
Object.Destroy(itemScript);
}
}
}
As you can see, both tests (and other tests too) use the same setup and teardown. I declare the variables outside the methods (to be available for every test). Teardown method is working fine, as it doesn't need to wait for anything to load.
private Player player;
private Character character;
private WorldItem worldItem;
private ItemScript itemScript;
[TearDown]
public void Teardown()
{
Debug.Log("Teardown called");
Object.Destroy(player);
Object.Destroy(character);
Object.Destroy(ectomodulatorWorldItem);
}
But I can't manage to make the SetUp method work. If I make it a coroutine ([SetUp] public IEnumerator SetUp(){...}) it never runs (I checked with the debugger). And if I make it a simple void method and delete the yield return nulls, it fails at character.Inventory.Give(worldItem); because worldItem is still null.
Is there a way I can make a SetUp method work?
Thanks
I found what I needed. Just like [Test] attribute only works on void methods and you need to use [UnityTest] to run a coroutine, there is also a [UnitySetUp] attribute that allows to use coroutine setup instead of a void one.
Link to the docs for more info: https://docs.unity3d.com/Packages/com.unity.test-framework#1.1/manual/reference-actions-outside-tests.html#unitysetup-and-unityteardown
In case you're interested, my tests file is like this:
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Game.Characters;
using Game.Inventory;
namespace Tests
{
public class ItemTests
{
Player player;
Character character;
WorldItem worldItem;
ItemScript itemScript;
[UnitySetUp]
public IEnumerator SetUp()
{
player = MonoBehaviour.Instantiate(Resources.Load<GameObject>("Tests/Player"));
yield return null; // Wait for prefab to load
character = player.GetComponent<Character>();
worldItem = WorldItem.Create(Resources.Load<Item>("Items/item"), 1);
yield return null; // Wait for prefab to load
character.Inventory.Give(worldItem);
itemScript = character.HandItemContainer.gameObject.GetComponentInChildren<ItemScript>();
}
[TearDown]
public void Teardown()
{
Object.Destroy(player);
Object.Destroy(character);
Object.Destroy(ectomodulatorWorldItem);
}
[Test]
public void ItemIsOnHand()
{
// Test
Assert.IsNotNull(itemScript);
}
[Test]
public void ItemIsDisabledAtStart()
{
// Test
Assert.IsFalse(itemScript.IsActive);
}
}
}
Related
I want the user to be able to change the ID of the link, which is why I used InputField to get their input. Unfortunately, I can't seem to figure out, how to make it work smoothly with a Coroutine.
When I try to update the link, it just ends in an endless loop like with the implementation below. When I don't update the link the Coroutine seems to start without getting the input and gets stuck.
I would really appreciate any help.
public class CSVDownloader : MonoBehaviour
{
public static string googleSheetDocID;
public static string url;
public InputField InputField;
public static string csvDownloaded;
public static CSVParametersData downloadedData;
public void Start()
{
googleSheetDocID = InputField.text;
Debug.Log(googleSheetDocID);
StartCoroutine(DownloadData());
}
public void Update()
{
googleSheetDocID = InputField.text;
Debug.Log(googleSheetDocID);
}
public static IEnumerator DownloadData()
{
Debug.Log(("In Coroutine"));
//url = "https://docs.google.com/spreadsheets/d/1ufpl1MsxXU2bk0Kt5YT_BkM1uI2WMLxr/export? format=csv";
url = "https://docs.google.com/spreadsheets/d/" + googleSheetDocID + "/export?format=csv";
Debug.Log(url);
UnityWebRequest webRequest = UnityWebRequest.Get(url);
yield return new WaitForEndOfFrame();
Debug.Log("Starting Download...");
yield return webRequest.SendWebRequest();
Debug.Log("Webrequestsend");
}
}
How about doing it in a few more steps?
In the code presented above, the user interaction ends with keyboard input. So trying to do everything in real time is a problem.
If the only user interaction is the keyboard, it is complex and difficult to achieve the desired task.
If it were me, I would improve the code to make it easier to achieve what you want by proceeding like this:
Add that script to your Button GameObject
using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
public class CsvDownloader : MonoBehaviour
{
[SerializeField] private InputField inputField;
private Button changeUrlButotn;
private string googleSheetDocID;
private string url;
private void Awake()
{
changeUrlButotn = GetComponent<Button>();
changeUrlButotn.onClick.AddListener(OnChangeUrl);
}
private void OnChangeUrl()
{
googleSheetDocID = inputField.text;
StartCoroutine(DownloadData());
}
private IEnumerator DownloadData()
{
url = Path.Combine(
"https://docs.google.com/spreadsheets/d",
googleSheetDocID,
"export?format=csv");
Debug.Log(url);
using var unityWebRequest = UnityWebRequest.Get(url);
unityWebRequest.SendWebRequest();
while (!unityWebRequest.isDone)
{
yield return null;
// Things to process in units of frames
// when unityWebRequest has not finished downloading ex) progress
// ...
}
Debug.Log("Success DownloadData()");
}
}
Separate the part that inputs the InputField with the keyboard and the part that updates the googleSheetDocID.
FYI, as #Max Play said, infinite loop doesn't happen.
Hope your problem is solved :)
I have an EventSystem for managing my turn-based game in Unity.
public class EventSystem : MonoBehaviour
{
private static List<Action> _commandsQueue = new List<Action>();
private bool _canExecuteCommand = true;
public void AddToQueue(Action command)
{
_commandsQueue.Add(command);
}
private void StartCommandExecution()
{
_commandsQueue[0]();
_canExecuteCommand = false;
}
public void CommandExecutionComplete()
{
_canExecuteCommand = true;
}
public void PlayFirstCommandFromQueue()
{
if (_commandsQueue.Any() && _canExecuteCommand)
{
StartCommandExecution();
}
else
{
Debug.LogError("No Command In Queue");
}
}
}
How do I put a method in Update() until _canExecuteCommand is true again but only for some methods?
It is quite broad what you are trying to do but in general you would use an endless loop within a Coroutine.
You can create a generic routine which invokes any Action you pass in as parameter once a frame like e.g.
private IEnumerator InvokeEveryFrame(Action action)
{
// This looks strange but is okey in a Coroutine as long as you yield somewhere within
while(true)
{
action?.Invoke();
// This tells Unity to "pause" this routine here
// render the current frame and continue from here in the next frame
yield return null;
}
}
So all that's left is starting the routine using MonoBehaviour.StartCoroutine like e.g.
Coroutine routine = StartCoroutine(SomeParameterlessMethod);
or if you need parameters
Coroutine routine = StartCoroutine(() => SomeMethod(x, y, z));
and then at some point later stop it using MonoBehaviour.StopCoroutine and the stored Coroutine reference like e.g.
StopCoroutine(routine);
how exactly you store that reference is up to you of course up to you.
I have the following simple class extending Networkbehaviour. I need to wait for a connection to server in order to call a [Command] funtcion.
Any UNET or multiplayer experts able to tell me what I am doing wrong?
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
public class KeepClientConnected : NetworkBehaviour {
public int pingInterval = 30;
// Use this for initialization
void Awake () {
DontDestroyOnLoad(transform.gameObject);
}
void Start(){
}
void OnDisconnectedFromServer(NetworkDisconnection info)
{
if (Network.isServer)
Debug.Log("Local server connection disconnected");
else
if (info == NetworkDisconnection.LostConnection)
Debug.Log("Lost connection to the server");
else
Debug.Log("Successfully diconnected from the server");
}
private void OnConnectedToServer()
{
Debug.Log("0 ASSERT OnConnectedToServer");
StartCoroutine(myCoRoutine());
}
[Command]
void Cmd_KeepConnection()
{
Debug.Log("2 ASSERT calling KeepConnection on Server");
}
IEnumerator myCoRoutine()
{
while (true)
{
Cmd_KeepConnection();
Debug.Log("1 ASSERT calling KeepConnection on Server");
yield return new WaitForSeconds(2);
}
}
}
I haven't used OnConnectedToServer but I can give you a couple of alternatives.
Make a Player. Have this network behavior exist on that player. The player will only get created once the client is connected.
Extend NetworkManager and override OnStartClient while making sure to still call base.OnStartClient. In this method you can be sure that the client is started and ready as it passes a reference to the client to you.
As a side note I used the following code in my player's Update method:
if (KeepAlive)
{
LastPing += Time.deltaTime;
if (LastPing > 10)
{
LastPing = 0;
CmdPing();
}
}
I have various methods that work fine, but I want to call them only after a delay. To avoid writing a different method for all of them I figured it's more beneficial to Invoke them somehow. I made the methods so that they exclude Unity's timeScale, so they always wait for Real seconds, using a custom built short function.
The WaitForRealSeconds:
public class WaitForRealSecondsClass
{
#region Wait for real seconds
public Coroutine WaitForRealSeconds(float aTime, MonoBehaviour mono)
{
return mono.StartCoroutine(_WaitForRealSeconds(aTime));
}
private IEnumerator _WaitForRealSeconds(float aTime)
{
while (aTime > 0.0f)
{
aTime -= Mathf.Clamp(Time.unscaledDeltaTime, 0, 0.2f);
yield return null;
}
}
#endregion
}
The way I wish to Invoke a Move function of mine:
public void InvokeAnim(float timeBeforeStart, Action<MonoBehaviour> MoveFunction, MonoBehaviour mono)
{
if (moveRoutine != null)
{
mono.StopCoroutine(moveRoutine);
}
moveRoutine = _InvokeAnim(timeBeforeStart, MoveFunction, mono);
}
IEnumerator _InvokeAnim(float timeBeforeStart, Action<MonoBehaviour> MoveFunction, MonoBehaviour mono)
{
yield return new WaitForRealSecondsClass().WaitForRealSeconds(timeBeforeStart, mono);
MoveFunction(mono);
}
And the Move(MonoBehaviour mono) itself:
public void Move(MonoBehaviour mono)
{
if (moveRoutine != null)
{
mono.StopCoroutine(moveRoutine);
}
moveRoutine = _Move(from, to, overTime, mono);
mono.StartCoroutine(moveRoutine);
}
What I tested and worked is the Move itself, the WaitForRealSeconds I used in another project for UI waiting when the game was stopped, it was fine then.
As I said I have many methods to invoke, all of them return void and have a parameter MonoBehaviour. Currently it doesn't do anything and I have no idea why.
Sod it, I was dump enough to forget actually Starting that coroutine.
In my Invoke:
public void InvokeAnim(float timeBeforeStart, Action<MonoBehaviour> MoveFunction,
MonoBehaviour mono)
{
if (moveRoutine != null)
{
mono.StopCoroutine(moveRoutine);
}
moveRoutine = _InvokeAnim(timeBeforeStart, MoveFunction, mono);
mono.StartCoroutine(moveRoutine); //one line was missing
}
IEnumerator _InvokeAnim(float timeBeforeStart, Action<MonoBehaviour> MoveFunction,
MonoBehaviour mono)
{
yield return new WaitForRealSecondsClass().WaitForRealSeconds(timeBeforeStart, mono);
MoveFunction(mono);
}
I'm making an event that does the failLevel stuff when it fires off. For that I have made a delegate
public delegate Coroutine FailGame(IEnumerator function);
public static event FailGame gameFailedEvent;
like so and I subscribed the appropriate function to it
void Start ()
{
gameFailedEvent += StartCoroutine;
}
It works when it is called from the same script like so:
gameFailedEvent(WaitThenFailLevel());
when this WaitThenFailLevel() looks like this:
IEnumerator WaitThenFailLevel()
{
CharacterController2D.playerDied = true;
if (CharacterController2D.animState != CharacterController2D.CharAnimStates.fall)
{
CharacterController2D.currentImageIndex = 0;
CharacterController2D.animState = CharacterController2D.CharAnimStates.fall;
}
CharacterController2D.movementDisabled = true;
yield return new WaitForSeconds(0.2f);
StartCoroutine(ScaleTime(1.0f, 0.0f, 1.2f));
}
It works fine here. Now, I have another object that can kill the player (dangerous times I know) and I don't want to copy paste everything again, I just want it to fire off the static event made in the script above.
I DID try making the WaitThenFailGame function
public static
and make static all my other ienumerators but I got an error named "An object reference is required for non-static field..."
Hence I tried the event stuff.
All well and fine, but I can't call the event from the other script because I can't pass it the function from the script mentioned.
What to do now?
Here is the example code:
EventContainor.cs
public class EventContainer : MonoBehaviour
{
public static event Action<string> OnGameFailedEvent;
void Update()
{
if (Input.GetKey(KeyCode.R))
{
// fire the game failed event when user press R.
if(OnGameFailedEvent = null)
OnGameFailedEvent("some value");
}
}
}
Listener.cs
public class Listener : MonoBehaviour
{
void Awake()
{
EventContainer.OnGameFailedEvent += EventContainer_OnGameFailedEvent;
}
void EventContainer_OnGameFailedEvent (string value)
{
StartCoroutine(MyCoroutine(value));
}
IEnumerator MyCoroutine(string someParam)
{
yield return new WaitForSeconds(5);
Debug.Log(someParam);
}
}
using UnityEngine;
public class ScriptA : MonoBehaviour
{
public ScriptB anyName;
void Update()
{
anyName.DoSomething();
}
}
using UnityEngine;
public class ScriptB : MonoBehaviour
{
public void DoSomething()
{
Debug.Log("Hi there");
}
}
This is linking functions between scripts , Copied from Here, maybe coroutines are the same, Then you need to start the coroutine in void Start() {} , You may find this useful as well.