Skip to content

Commit

Permalink
Merge pull request #13 from nowsprinting/feature/scenesusingintest
Browse files Browse the repository at this point in the history
Add CreateScene and LoadScene attribute
  • Loading branch information
nowsprinting authored Oct 22, 2023
2 parents 52b0642 + ed34b9c commit d82dd8e
Show file tree
Hide file tree
Showing 17 changed files with 825 additions and 9 deletions.
97 changes: 97 additions & 0 deletions Editor/TemporaryBuildScenesUsingInTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) 2023 Koji Hasegawa.
// This software is released under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using TestHelper.Attributes;
using TestHelper.Editor;
using UnityEditor;
using UnityEditor.TestTools;

[assembly: TestPlayerBuildModifier(typeof(TemporaryBuildScenesUsingInTest))]

namespace TestHelper.Editor
{
/// <summary>
/// Temporarily build scenes specified by <c>LoadSceneAttribute</c> when running play mode tests on standalone player.
/// </summary>
public class TemporaryBuildScenesUsingInTest : ITestPlayerBuildModifier
{
private static IEnumerable<LoadSceneAttribute> FindLoadSceneAttributesOnAssemblies()
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var attribute in assemblies
.Select(assembly => assembly.GetCustomAttributes(typeof(LoadSceneAttribute), false))
.SelectMany(attributes => attributes))
{
yield return attribute as LoadSceneAttribute;
}
}

private static IEnumerable<LoadSceneAttribute> FindLoadSceneAttributesOnTypes()
{
var symbols = TypeCache.GetTypesWithAttribute<LoadSceneAttribute>();
foreach (var attribute in symbols
.Select(symbol => symbol.GetCustomAttributes(typeof(LoadSceneAttribute), false))
.SelectMany(attributes => attributes))
{
yield return attribute as LoadSceneAttribute;
}
}

private static IEnumerable<LoadSceneAttribute> FindLoadSceneAttributesOnMethods()
{
var symbols = TypeCache.GetMethodsWithAttribute<LoadSceneAttribute>();
foreach (var attribute in symbols
.Select(symbol => symbol.GetCustomAttributes(typeof(LoadSceneAttribute), false))
.SelectMany(attributes => attributes))
{
yield return attribute as LoadSceneAttribute;
}
}

internal static IEnumerable<string> GetScenesUsingInTest()
{
var attributes = FindLoadSceneAttributesOnAssemblies()
.Concat(FindLoadSceneAttributesOnTypes())
.Concat(FindLoadSceneAttributesOnMethods());
foreach (var attribute in attributes)
{
if (attribute.ScenePath.ToLower().EndsWith(".unity"))
{
yield return attribute.ScenePath;
}
else
{
foreach (var guid in AssetDatabase.FindAssets("t:SceneAsset", new[] { attribute.ScenePath }))
{
yield return AssetDatabase.GUIDToAssetPath(guid);
}
}
}
}

/// <summary>
/// Add temporary scenes to build when running play mode tests on standalone player.
/// </summary>
/// <remarks>
/// Required Unity Test Framework package v1.1.13 or higher is to use this script.
/// For details, see the <see href="https://forum.unity.com/threads/testplayerbuildmodifier-not-working.844447/">report in forum</see>.
/// </remarks>
public BuildPlayerOptions ModifyOptions(BuildPlayerOptions playerOptions)
{
var scenesInBuild = new List<string>(playerOptions.scenes);
foreach (var scenePath in GetScenesUsingInTest())
{
if (!scenesInBuild.Contains(scenePath))
{
scenesInBuild.Add(scenePath);
}
}

playerOptions.scenes = scenesInBuild.ToArray();
return playerOptions;
}
}
}
11 changes: 11 additions & 0 deletions Editor/TemporaryBuildScenesUsingInTest.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Required Unity 2019 LTS or later.

## Features


### Attributes

#### FocusGameView
Expand Down Expand Up @@ -130,6 +131,43 @@ public class MyTestClass
}
```

#### LoadScene

`LoadSceneAttribute` is an NUnit test attribute class to load scene before running test.

It has the following benefits:

- Can be used when running play mode tests in-editor and on-player
- Can be specified scenes that are not in "Scenes in Build"

This attribute can attached to test method only.

Usage:

```csharp
using System;
using NUnit.Framework;
using TestHelper.Attributes;

[TestFixture]
public class MyTestClass
{
[Test]
[LoadScene("Assets/MyTests/Scenes/Scene.unity")]
public void MyTestMethod()
{
var cube = GameObject.Find("Cube");
Assert.That(cube, Is.Not.Null);
}
}
```

> **Note**
> - Load scene run after <c>OneTimeSetUp</c> and before <c>SetUp</c>
> - Scene file path is starts with `Assets/` or `Packages/`.
> And package name using `name` instead of `displayName`, when scenes in the package.
> (e.g., `Packages/com.nowsprinting.test-helper/Tests/Scenes/Scene.unity`)

### Constraints

Expand Down
1 change: 1 addition & 0 deletions Runtime/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("TestHelper.Editor")]
[assembly: InternalsVisibleTo("TestHelper.Tests")]
78 changes: 78 additions & 0 deletions Runtime/Attributes/LoadSceneAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) 2023 Koji Hasegawa.
// This software is released under the MIT License.

using System;
using System.Collections;
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
#endif

// ReSharper disable InvalidXmlDocComment

namespace TestHelper.Attributes
{
/// <summary>
/// Load scene before running test.
///
/// It has the following benefits:
/// - Can be used when running play mode tests in-editor and on-player
/// - Can be specified scenes that are not in "Scenes in Build"
///
/// Notes:
/// - Load scene run after <c>OneTimeSetUp</c> and before <c>SetUp</c>
/// - For the process of including a Scene not in "Scenes in Build" to a build for player, see: <see cref="TestHelper.Editor.TemporaryBuildScenesUsingInTest"/>
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class LoadSceneAttribute : NUnitAttribute, IOuterUnityTestAction
{
internal string ScenePath { get; private set; }

/// <summary>
/// Load scene before running test.
/// </summary>
/// <param name="path">Scene file path.
/// The path starts with `Assets/` or `Packages/`.
/// And package name using `name` instead of `displayName`, when scenes in the package.
/// (e.g., `Packages/com.nowsprinting.test-helper/Tests/Scenes/Scene.unity`)
/// </param>
public LoadSceneAttribute(string path)
{
ScenePath = path;
}

/// <inheritdoc />
public IEnumerator BeforeTest(ITest test)
{
AsyncOperation loadSceneAsync = null;
#if UNITY_EDITOR
if (EditorApplication.isPlaying)
{
// Use EditorSceneManager at run on Unity-editor
loadSceneAsync = EditorSceneManager.LoadSceneAsyncInPlayMode(
ScenePath,
new LoadSceneParameters(LoadSceneMode.Single));
}
else
{
EditorSceneManager.OpenScene(ScenePath);
}
#else
// Use ITestPlayerBuildModifier to change the "Scenes in Build" list before run on player
loadSceneAsync = SceneManager.LoadSceneAsync(ScenePath);
#endif
yield return loadSceneAsync;
}

/// <inheritdoc />
public IEnumerator AfterTest(ITest test)
{
yield return null;
}
}
}
3 changes: 3 additions & 0 deletions Runtime/Attributes/LoadSceneAttribute.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 0 additions & 6 deletions Tests/Editor/AssemblyInfo.cs

This file was deleted.

3 changes: 0 additions & 3 deletions Tests/Editor/AssemblyInfo.cs.meta

This file was deleted.

24 changes: 24 additions & 0 deletions Tests/Editor/LoadSceneAttributeTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) 2023 Koji Hasegawa.
// This software is released under the MIT License.

using NUnit.Framework;
using TestHelper.Attributes;
using UnityEngine;

namespace TestHelper.Editor
{
[TestFixture]
public class LoadSceneAttributeTest
{
private const string TestScene = "Packages/com.nowsprinting.test-helper/Tests/Scenes/NotInScenesInBuild.unity";
private const string ObjectName = "CubeInNotInScenesInBuild";

[Test]
[LoadScene(TestScene)]
public void Attach_AlreadyLoadedSceneNotInBuild()
{
var cube = GameObject.Find(ObjectName);
Assert.That(cube, Is.Not.Null);
}
}
}
3 changes: 3 additions & 0 deletions Tests/Editor/LoadSceneAttributeTest.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions Tests/Editor/TemporaryBuildScenesUsingInTestTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) 2023 Koji Hasegawa.
// This software is released under the MIT License.

using NUnit.Framework;

namespace TestHelper.Editor
{
[TestFixture]
public class TemporaryBuildScenesUsingInTestTest
{
[Test]
public void GetScenesUsingInTest_AttachedToMethod_ReturnScenesSpecifiedByAttribute()
{
var actual = TemporaryBuildScenesUsingInTest.GetScenesUsingInTest();
Assert.That(actual,
Does.Contain("Packages/com.nowsprinting.test-helper/Tests/Scenes/NotInScenesInBuild.unity"));
}
}
}
3 changes: 3 additions & 0 deletions Tests/Editor/TemporaryBuildScenesUsingInTestTest.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 50 additions & 0 deletions Tests/Runtime/Attributes/LoadSceneAttributeTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) 2023 Koji Hasegawa.
// This software is released under the MIT License.

using System.Collections;
using System.Threading.Tasks;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;

namespace TestHelper.Attributes
{
[TestFixture]
public class LoadSceneAttributeTest
{
private const string TestScene = "Packages/com.nowsprinting.test-helper/Tests/Scenes/NotInScenesInBuild.unity";
private const string ObjectName = "CubeInNotInScenesInBuild";

[Test]
[LoadScene(TestScene)]
public void Attach_AlreadyLoadedSceneNotInBuild()
{
var cube = GameObject.Find(ObjectName);
Assert.That(cube, Is.Not.Null);

Object.Destroy(cube); // For not giving false negatives in subsequent tests.
}

[Test]
[LoadScene(TestScene)]
public async Task AttachToAsyncTest_AlreadyLoadedSceneNotInBuild()
{
var cube = GameObject.Find(ObjectName);
Assert.That(cube, Is.Not.Null);

Object.Destroy(cube); // For not giving false negatives in subsequent tests.
await Task.Yield();
}

[UnityTest]
[LoadScene(TestScene)]
public IEnumerator AttachToUnityTest_AlreadyLoadedSceneNotInBuild()
{
var cube = GameObject.Find(ObjectName);
Assert.That(cube, Is.Not.Null);

Object.Destroy(cube); // For not giving false negatives in subsequent tests.
yield return null;
}
}
}
3 changes: 3 additions & 0 deletions Tests/Runtime/Attributes/LoadSceneAttributeTest.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Tests/Scenes.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit d82dd8e

Please sign in to comment.