Continuous integration with Unity

This article gives an overview of the test framework in Unity and how to set up a continuous integration system with Unity and Jenkins. This article assumes you have some basic idea about unit testing and bash script.

What is Unity test and continuous integration

Unity test

Unity test is very similar to other unit testing frameworks. From Unity's documentation, Unity actually uses the .Net unit testing NUnit library. The difference is that there are different modes that users can choose to run their tests in. Tests in edit mode are executed in the way as you would expect in other unit testing frameworks, but tests in play mode are executed in a bit more interesting way. Let's take the test in the above documentation as an example:

public class SamplePlayModeTest {
    [UnityTest]
    public IEnumerator TestRigidbodyPhysics()
    {
        var myGameObject = new GameObject();
        myGameObject.AddComponent<Rigidbody>();
        var originalPosition = myGameObject.transform.position.y;

        yield return new WaitForFixedUpdate();

        // y should have changed because of gravity.
        Assert.AreNotEqual(originalPosition, myGameObject.transform.position.y);
    }
}

In this example, a new GameObject is created. After that, A Rigidbody component is added to that GameObject, which makes that GameObject be influenced by Unity physics engine. Because of the gravity, the originalPosition should not be equal to the new position after the FixedUpdate.

Continuous integration

After you have some tests, you may want to have a service that can automatically run those tests after each change you push to your git repository, and build the project for you. This automation process is so called continuous integration. In the rest of the article I will walk you through how to set up a continuous integration server with Jenkins. But first, we need to know a little more about Unity command line interface.

Getting familiar with Unity command line interface

Run Unity tests from command line interface

Being able to run Unity tests from command line is essential to build a continuous integration system. We will first demonstrate how to run Unity tests from command line.

First of all, it would be easier to run Unity from command line if we add an alis in bash profile. Add the following line to your ~/.bash_profile.

alias unity='/Applications/Unity/Unity.app/Contents/MacOS/Unity'

Now we are able to launch Unity from terminal by just typing unity.

However, if we only type unity, it will just start the Unity launcher, and we still need to manualy choose whether to create/open projects, and etc. Therefore, there are some command line augments we need to learn to launch Unity silently and run tests.

  • -batchmode This is almost always needed. It will ensure no pop-up windows and eliminate human intervention.
  • -projectPath <pathname> Specify your project location.
  • -logFile <pathname> Specify where to write Unity Editor logs. We will mostly like need infomation in the log file when we build CI system.
  • -runTests Run Unity tests.
  • -testResults <pathname> Specify where to write Unity tests results. Again, We will mostly like need infomation in the result file when we build CI system.
  • -testPlatform <name> Specify the platform you want to run test on. Currently we will mainly use playmode and editmode.

These are about the minimum command line augments you need to know to run Unity tests from command line. A complete example would be the following:

unity -batchmode \
    -runTests \
    -projectPath $PROJECT_PATH \
    -logFile $LOG_PATH \
    -testResults $RESULTS_PATH \
    -testPlatform $PLATFORM_NAMES

After that, we can check results from log file and test results file.

Build a Unity project from command line interface

Building a Unity project from command line interface is very similar to running Unity tests. Here we assume the project is relative simple and build settings are already configured in the Unity Editor. You can read more about complex builds that involve build script here and the example here.

The only change we need to make from the running test script is to replace all the test related arguments with build arguments. For example:

-buildOSXUniversalPlayer <pathname> This will build the project into macOS executable. Building for other platforms are possible with similar arguments. Users can find the full list here.

A complete example would be the following:

unity -batchmode \
    -projectPath $PROJECT_PATH \
    -logFile $LOG_PATH \
    -buildOSXUniversalPlayer $BUILD_PATH \
    -quit

Set up continuous integration with Jenkins

Finally, it comes to setting up continuous integration with Jenkins server. So far, we've learned all the essential parts about what Unity test and continuous integration are, and how to run Unity tests and build Unity project using Bash from command line interface. Now we need to set up a Jenkins server to automate the entire process.

Install Jenkins

Jenkins is included in most of the package managers, so you should be able to install it easily. You can also download it from their website.

For macOS users who have Homebrew installed, you can simply run:

brew install jenkins

After installation, you can start the Jenkins server by running command jenkins from terminal. Then you can go to localhost:8080 in your browser to initialize the Jenkins server. Make sure you have Github plugin selected and installed because we will connect this server to a Github repository later.

Create a new build item in Jenkins

After the initialization, you should be able to see the Jenkins dashboard. Click the New Item button on the left to create a new build item. This build item is the service that will automatically build our project after each change we make to our Github repository.

After click the New Item button, enter an item name, select Freestyle project and click OK. This should bring you to the configuration page.

In this configuration page, you will need to connect your Jenkins server to your Github repository and specify your build steps. Details on how to connect Jenkins server with Github repository can be found here and here. I will focus more on how to build the project after the Jenkins server successfully get the content from the Github repository.

Write a build script

After Jenkins server successfully pull your project from your Github repository, it needs to know how to build the project. Here we will use what we've learned about Unity command line interface, and write a build bash script to test and build our Unity project.

Suppose your project has a structure like this:

.
+-- build.sh  # This is the build script we will be writing
+-- src/      # This is the your Unity project
+-- build/    # This folder contains your build results
+-- output/   # This folder contains the log files during the building process

We want the Jenkins server to run the build script to run Unity tests and build the Unity project every time after it pulls content from your Github repository. We can use the bash script we created earlier to run the test and build the project.

However, there's something more we need to add in between. We want the Jenkins server to detect whether all tests pass, and stop the building process if there are failures. This can be achieved by appending a few lines of bash script after the scripts for running tests:

result=$?
if [ $result -ne 0 ]; then
    echo "Unity editor mode tests failed."
    exit $result
fi

The above Bash script will get the last return value from the running-test-script, and check whether it equals to 0. If the return value from Unity Test Runner does not equal to 0, that means there are failures, and whole script will stop and exit.

Below is a sample of complete build script:

#!/bin/sh

# Default Unity installation path. Change this if needed.
unity='/Applications/Unity/Unity.app/Contents/MacOS/Unity'

echo "Start running Unity edit mode tests..."
$unity -batchmode \
    -projectPath "$(pwd)/src" \
    -runTests \
    -logFile "$(pwd)/output/editmode_log.xml" \
    -testResults "$(pwd)/output/editmode_results.xml" \
    -testPlatform "editmode"
result=$?
if [ $result -ne 0 ]; then
    echo "Unity editor mode tests failed."
    exit $result
fi

echo "Start running Unity play mode tests..."
$unity -batchmode \
    -projectPath "$(pwd)/src" \
    -runTests \
    -logFile "$(pwd)/output/playmode_log.xml" \
    -testResults "$(pwd)/output/playmode_results.xml" \
    -testPlatform "playmode"
result=$?
if [ $result -ne 0 ]; then
    echo "Unity play mode tests failed."
    exit $result
fi

echo "Start building the project..."
$unity -batchmode \
    -projectPath "$(pwd)/src" \
    -logFile "$(pwd)/output/build_log.xml" \
    -buildOSXUniversalPlayer "$(pwd)/build/build.app" \
    -quit

After we have the build script, we need to tell the Jenkins server to run it. Scroll down to the Build section. Click the Add build step button, and select execute shell. In the text field, type $WORKSPACE/build.sh. $WORKSPACE is the absolute path of the directory assigned to the build. Click Save, and you should be all good.

Next step

The above script will store the build result in the $WORKSPACE/build/ folder. This will get override after each build. Usually people will store the build result on somewhere else like a dedicate server, or integrate with other solutions to deliver directly to AppStore, and etc. You have many options here.

That's it for the overview of Unity test and continuous integration using Jenkins. Hope you get a high level idea of how to set up this kind of system with Unity.