Learn the basics of test automation using GameDriver
Getting Started with GameDriver
Be sure to follow the Installation Instructions before this step. We'll wait here...
So you have installed GameDriver and are ready to start testing. Great! Here's what you need to do next. This sample project can also be found in GitHub, here.
Assuming you followed the installation instructions to the end, you should have something similar to the code below added to a test in Visual Studio:
using System;
using System.Diagnostics;
using NUnit.Framework;
using gdio.unity_api;
using gdio.unity_api.v2;
using gdio.unity_api.utilities;
using gdio.common.objects;
namespace DemoTest
{
[TestFixture]
public class UnitTest
{
//These parameters can be used to override settings used to test when running from the NUnit command line
public string testMode = TestContext.Parameters.Get("Mode", "IDE");
public string pathToExe = TestContext.Parameters.Get("pathToExe", null); // replace null with the path to your executable as needed
ApiClient api;
[OneTimeSetUp]
public void Connect()
{
try
{
// First we need to create an instance of the ApiClient
api = new ApiClient();
// If an executable path was supplied, we will launch the standalone game
if (pathToExe != null)
{
ApiClient.Launch(pathToExe);
api.Connect("localhost", 19734, false, 30);
}
// If no executable path was given, we will attempt to connect to the Unity editor and initiate Play mode
else if (testMode == "IDE")
{
api.Connect("localhost", 19734, true, 30);
}
// Otherwise, attempt to connect to an already playing game
else api.Connect("localhost", 19734, false, 30);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
// Enable input hooking
api.EnableHooks(HookingObject.ALL);
//Start the Game - in this example we're waiting for an object called "StartButton" to become active, then clicking it.
api.WaitForObject("//*[@name='StartButton']");
api.ClickObject(MouseButtons.LEFT, "//*[@name='StartButton']", 30);
api.Wait(3000);
}
[Test]
public void Test1()
{
// Do something
}
[Test]
public void Test2()
{
// Do something else. Tests should be able to run independently after the steps in [OneTimeSetup] and should use try/catch blocks to avoid exiting prematurely on failure
}
[OneTimeTearDown]
public void Disconnect()
{
// Disconnect the GameDriver client from the agent
api.DisableHooks(HookingObject.ALL);
api.Wait(2000);
api.Disconnect();
api.Wait(2000);
}
}
}
For this article, we will implement a single [Test]. As a best practice these should be self-contained in the sense that they can run whether or not other tests have passed; Meaning the test should include whatever steps are necessary to navigate to that point. We will use a simple example here.
First, we will load the scene, and then check the status of an object to verify the scene is loaded. We do this using the WaitForObject function, and can optionally wrap that in an Assert.IsTrue function which will automatically fail the test if the scene isn't loaded:
[Test]
public void ObjectMovement() // This will be our main test method, where we want to achieve some expected result. Be sure to give it a useful name
{
// If we're not in the correct scene, load it using the object name
if (api.GetSceneName() == "Menu")
{
api.ClickObject(MouseButtons.LEFT, "//*[@name='Load_MouseMovement']", 30);
}
else api.LoadScene("MouseMoveObject");
Next, we will want to perform some specific actions to test the behavior of the game. This could be anything from moving the character around the scene, collecting items, locating and shooting enemies, or simply drag-and-drop objects around the UI. It all depends on your project and the goal of your tests. Keep in mind the goal, to test features or functionality that is critical to the game, and provide immediate feedback to developers if something isn't working. In this example, we will move an object around the scene, and test the final location.
// Wait for the object we want to test to become active in the scene. This could be the result of some other action or just the general loading of the scene
api.WaitForObject("//*[@name='Cylinder']");
// Store the initial position of the object on screen for use in later steps
Vector3 cyl = api.GetObjectPosition("//*[@name='Cylinder']",CoordinateConversion.WorldToScreenPoint);
// Move the mouse to the object and wait for it to get there
api.MouseMoveToObject("//*[@name='Cylinder']", 30, true, true);
//Rotate the object in various ways
api.RotateObject("//*[@name='Cylinder']", new Vector3(0, 30, 0), Space.World, true); // Rotates 30-degrees on the Y axis
api.Wait(1000);
api.RotateObject("//*[@name='Cylinder']", 0, 0, 20); // Rotates 20-degrees on the Z axis
api.Wait(1000);
api.RotateObject("//*[@name='Cylinder']", cyl, 45); // All 3 axis will rotate 45-degrees
api.Wait(1000);
// Get the initial position of the object
Vector3 pos = api.GetObjectPosition("//*[@name='Cylinder']", CoordinateConversion.WorldToScreenPoint);
Vector2 start = new Vector2(pos.x, pos.y);
int frames = (int)api.GetLastFPS(); // set to GetLastFPS for regular
// MouseDrag the Cylinder in a spiral, checking the location at each step to use as the next starting position.
api.MouseDrag(MouseButtons.LEFT, new Vector2(start.x + 180, start.y), (ulong)frames, start, true);
api.Wait(frames);
pos = api.GetObjectPosition("//*[@name='Cylinder']", CoordinateConversion.WorldToScreenPoint);
api.MouseDrag(MouseButtons.LEFT, new Vector2(pos.x, pos.y + 180), (ulong)frames, null, true);
api.Wait(frames);
pos = api.GetObjectPosition("//*[@name='Cylinder']", CoordinateConversion.WorldToScreenPoint);
api.MouseDrag(MouseButtons.LEFT, new Vector2(pos.x - 360, pos.y), (ulong)frames, null, true);
api.Wait(frames);
pos = api.GetObjectPosition("//*[@name='Cylinder']", CoordinateConversion.WorldToScreenPoint);
api.MouseDrag(MouseButtons.LEFT, new Vector2(pos.x, pos.y - 360), (ulong)frames, null, true);
api.Wait(frames);
pos = api.GetObjectPosition("//*[@name='Cylinder']", CoordinateConversion.WorldToScreenPoint);
api.MouseDrag(MouseButtons.LEFT, new Vector2(pos.x + 360, pos.y), (ulong)frames, null, true);
api.Wait(frames);
pos = api.GetObjectPosition("//*[@name='Cylinder']", CoordinateConversion.WorldToScreenPoint);
api.MouseDrag(MouseButtons.LEFT, new Vector2(pos.x, pos.y + 180), (ulong)frames, null, true);
api.Wait(frames);
pos = api.GetObjectPosition("//*[@name='Cylinder']", CoordinateConversion.WorldToScreenPoint);
api.MouseDrag(MouseButtons.LEFT, new Vector2(pos.x - 180, pos.y), (ulong)frames, null, true);
api.Wait(frames);
// update the value of the object position again for comparison
pos = api.GetObjectPosition("//*[@name='Cylinder']", CoordinateConversion.WorldToScreenPoint);
//Simple output messaging to validate component locations
Console.WriteLine("Starting Position: x=" + start.x.ToString() + ", y=" + start.y.ToString());
Console.WriteLine("Ending Position: x=" + pos.x.ToString() + ", y=" + pos.y.ToString());
//Using test assertions to validate object positions are within a specified tolerance.
Assert.AreEqual(start.x, pos.x, 2f, "X coordinates aren't within 2 degrees of expected value");
Assert.AreEqual(start.y, pos.y, 2f, "Y coordinates aren't within 2 degrees of expected value");
Finally, exit the scene to bring the test back to the starting point, which will allow other tests to run.
//Return to the Menu scene
api.ClickObject(MouseButtons.LEFT, "//*[@name='ReturnButton']", 30);
api.Wait(2000);
Assert.IsTrue(api.GetSceneName().Equals("Menu"), "Menu not loaded!");
There you have it! We loaded a scene, moved an object in a pre-defined pattern using the previous location as a reference point, and verified the result.
Happy Testing!