Entitas-Redux icon indicating copy to clipboard operation
Entitas-Redux copied to clipboard

[Docs] Create a document describing ways to test an Entitas Redux project using Unity Test Runner

Open jeffcampbellmakesgames opened this issue 3 years ago • 4 comments

How to test your project using the Unity Test Runner (currently with NUnit) | no need to do it like this anymore https://www.youtube.com/watch?v=DZpvUnj2dGI

jeffcampbellmakesgames avatar Jan 11 '21 17:01 jeffcampbellmakesgames

@jeffcampbellmakesgames I would like to work on this.

Since I'm not an expert on this I would like to provide the wiki content here as a comment so you can review it first

matthiashermsen avatar Jan 12 '21 20:01 matthiashermsen

@jeffcampbellmakesgames I would like to work on this.

Since I'm not an expert on this I would like to provide the wiki content here as a comment so you can review it first

That sounds good; I can also help to supply content for this based on some of the knowledge gained from unit testing the framework itself and different approaches used there.

jeffcampbellmakesgames avatar Jan 12 '21 21:01 jeffcampbellmakesgames

@jeffcampbellmakesgames this is a first prototype. For the assembly definition part I'm not sure what needs to be added so for now I just added everything. I would put this wiki page to the getting started section.

Please take care for the move system test example, this is not done yet :D

Setting up tests

Requirements

Entitas-Redux and Genesis are installed and Genesis is configured.

Unity test framework

You can find more about it here.

Setting up the tests environment

First you need to create a tests assembly folder. This can be done via the Asset folder context menu

Create => Testing => Tests Assembly Folder

After creating it you should see the following warning in the editor console

Assembly for Assembly Definition File 'Assets/Tests/Tests.asmdef' will not be compiled, because it has no scripts associated with it.

Unity needs to know the location of your code, the tests assembly definition needs a reference to the assembly definition of your code. At the root of your code directory create a new assembly definition

Create => Assembly Definition

Now head over to the tests assembly definition and add your code assembly definition to the list. After that, don't forget to save your changes by clicking on apply at the bottom.

image

Now Unity knows about your code. Head over to the code assembly definition and add Entitas-Redux and Genesis as a reference to that assembly definition. Do the same for the tests assembly definition. Otherwise the tests environment doesn't know about external packages like Entitas-Redux or Genesis. After that, don't forget to save your changes by clicking on apply at the bottom.

image

Writing tests

Make sure all tests live in the tests assembly folder. Create a new test script template with

Create => Testing => C# Test Script

Running the tests

You can run the tests with the Test Runner which can be found at

Window => General => Test Runner

Test examples

The following shows some examples for different scenarios. It should give a rough overview about testing in its simplest form.

Testing an init system creating a grid of x * y entities with unique field indices

Given the example components and init system

[Game, Unique]
public sealed class BoardSizeComponent : IComponent
{
    public Vector2Int value;
}

[Game]
public sealed class FieldIndexComponent : IComponent
{
    [PrimaryEntityIndex]
    public Vector2Int value;
}

public sealed class FieldConstructionSystem : IInitializeSystem
{
    private readonly Contexts _contexts;

    public FieldConstructionSystem(Contexts contexts)
    {
        _contexts = contexts;
    }

    public void Initialize()
    {
        Vector2Int boardSize = _contexts.Game.BoardSize.value;
        
        for (int x = 0; x < boardSize.x; x++)
        {
            for (int y = 0; y < boardSize.y; y++)
            {
                Vector2Int newFieldIndex = new Vector2Int(x, y);
                
                GameEntity newField = _contexts.Game.CreateEntity();
                newField.AddFieldIndex(newFieldIndex);
            }
        }
    }
}

You can test the FieldConstructionSystem by creating a FieldConstructionSystemTests script. Things you might want to test:

  • It should create an amount of entities equal to the board size
  • Each field index exists only once

The first test runs multiple times with different mock data but you can run the second test with different data too. You can even use the TestCaseSource attribute to make your test data reusable.

public sealed class FieldConstructionSystemTests
{
    private Contexts _contexts;
    private Systems _systems;

    [SetUp]
    public void Setup()
    {
        _contexts = new Contexts();
        _systems = new Systems();
        
        FieldConstructionSystem fieldConstructionSystem = new FieldConstructionSystem(_contexts);
        _systems.Add(fieldConstructionSystem);
    }
    
    [TestCase(0, 0)]
    [TestCase(100, 100)]
    [TestCase(8, 53)]
    [TestCase(27, 4)]
    [TestCase(10, 10)]
    [TestCase(71, 3)]
    [TestCase(39, 8)]
    public void ItCreatesFieldIndexEntitiesBasedOnBoardSize(int horizontalBoardSize, int verticalBoardSize)
    {
        Vector2Int boardSize = new Vector2Int(horizontalBoardSize, verticalBoardSize);
        
        _contexts.Game.ReplaceBoardSize(boardSize);
        
        _systems.Initialize();
        
        int expectedEntityAmount = boardSize.x * boardSize.y;
        int actualEntityAmount = _contexts.Game
            .GetGroup(GameMatcher.FieldIndex)
            .AsEnumerable()
            .Count();

        Assert.AreEqual(expectedEntityAmount, actualEntityAmount);
    }
    
    [Test]
    public void EachAreaIndexExists()
    {
        Vector2Int boardSize = new Vector2Int(10, 10);
        
        _contexts.Game.ReplaceBoardSize(boardSize);
        
        _systems.Initialize();
        
        for (int x = 0; x < boardSize.x; x++)
        {
            for (int y = 0; y < boardSize.y; y++)
            {
                Vector2Int currentFieldIndex = new Vector2Int(x, y);
                GameEntity fieldEntity = _contexts.Game.GetEntityWithFieldIndex(currentFieldIndex);
                
                Assert.NotNull(fieldEntity);
            }
        }
    }
}

Testing an update system moving an entity horizontally by one once per frame

Given the example component and update system


!!!

@jeffcampbellmakesgames since #43 is not done yet I have to check how to refactor and improve this system x)

!!!


[Game]
public sealed class PositionComponent : IComponent
{
    public Vector2Int value;
}

public sealed class MoveSystem : IUpdateSystem
{
    public void Update()
    {
        IEnumerable<GameEntity> entities = Contexts.SharedInstance.Game
            .GetGroup(GameMatcher.Position)
            .AsEnumerable();

        foreach (GameEntity entity in entities)
        {
            Vector2Int oldPosition = entity.Position.value;
            Vector2Int newPosition = new Vector2Int(oldPosition.x + 1, oldPosition.y);
            
            entity.ReplacePosition(newPosition);
        }
    }
}

You can test the MoveSystem by creating a MoveSystemTests script. You want to test that the horizontal position increases incrementally by 1 once per frame.

Unfortunately the TestCase attribute is not supported, but you can use ValueSource instead. You can find more information here.

For the sake of simplicity this test only runs once


!!!

@jeffcampbellmakesgames the test is not passing yet x) Have to check how to write Unity tests

!!!


public sealed class MoveSystemTests
{
    private Contexts _contexts;
    private Systems _systems;

    [SetUp]
    public void Setup()
    {
        _contexts = new Contexts();
        _systems = new Systems();
        
        MoveSystem moveSystem = new MoveSystem();
        _systems.Add(moveSystem);
    }

    [UnityTest]
    public IEnumerator TheEntityMovesHorizontallyOncePerFrame()
    {
        Vector2Int initialEntityPosition = new Vector2Int(8, 53);
        
        GameEntity entity = _contexts.Game.CreateEntity();
        entity.AddPosition(initialEntityPosition);
        
        _systems.Update();
        
        yield return null;
    
        int expectedHorizontalTargetPosition = initialEntityPosition.x + 1;
        
        Assert.AreEqual(expectedHorizontalTargetPosition, entity.Position.value.x);
    }
}

matthiashermsen avatar Jan 13 '21 18:01 matthiashermsen

@jeffcampbellmakesgames I'm not sure if my suggestions are still up to date but for now I would like to unassign myself and wait for a review :)

matthiashermsen avatar May 07 '22 19:05 matthiashermsen