Skip to content

Commit

Permalink
Replace ScenesUsingInTestAttribute to LoadSceneAttribute
Browse files Browse the repository at this point in the history
  • Loading branch information
nowsprinting committed Oct 22, 2023
1 parent 521c930 commit ed34b9c
Show file tree
Hide file tree
Showing 24 changed files with 193 additions and 3,170 deletions.
100 changes: 25 additions & 75 deletions Editor/TemporaryBuildScenesUsingInTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,56 +8,54 @@
using TestHelper.Editor;
using UnityEditor;
using UnityEditor.TestTools;
using UnityEditor.TestTools.TestRunner.Api;
using UnityEngine;

[assembly: TestPlayerBuildModifier(typeof(TemporaryBuildScenesUsingInTest.RunOnStandalonePlayer))]
[assembly: TestPlayerBuildModifier(typeof(TemporaryBuildScenesUsingInTest))]

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

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

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

internal static IEnumerable<string> GetScenesUsingInTest()
{
var attributes = FindScenesUsingInTestAttributesOnAssemblies()
.Concat(FindScenesUsingInTestAttributesOnTypes())
.Concat(FindScenesUsingInTestAttributesOnMethods());
var attributes = FindLoadSceneAttributesOnAssemblies()
.Concat(FindLoadSceneAttributesOnTypes())
.Concat(FindLoadSceneAttributesOnMethods());
foreach (var attribute in attributes)
{
if (attribute.ScenePath.ToLower().EndsWith(".unity"))
Expand All @@ -74,74 +72,26 @@ internal static IEnumerable<string> GetScenesUsingInTest()
}
}

/// <summary>
/// Add temporary scenes to build when running play mode tests in editor.
/// </summary>
public class RunInEditor : ICallbacks
{
private EditorBuildSettingsScene[] _originScenesInBuild;

[InitializeOnLoadMethod]
private static void SetupRunningInEditor()
{
var api = ScriptableObject.CreateInstance<TestRunnerApi>();
api.RegisterCallbacks(new RunInEditor());
}

/// <inheritdoc />
public void RunStarted(ITestAdaptor testsToRun)
{
_originScenesInBuild = EditorBuildSettings.scenes;

var scenesInBuild = _originScenesInBuild.ToList();
foreach (var scenePath in GetScenesUsingInTest())
{
if (scenesInBuild.All(scene => scene.path != scenePath))
{
scenesInBuild.Add(new EditorBuildSettingsScene(scenePath, true));
}
}

EditorBuildSettings.scenes = scenesInBuild.ToArray();
}

/// <inheritdoc />
public void RunFinished(ITestResultAdaptor result)
{
EditorBuildSettings.scenes = _originScenesInBuild;
}

/// <inheritdoc />
public void TestStarted(ITestAdaptor test) { }

/// <inheritdoc />
public void TestFinished(ITestResultAdaptor result) { }
}

/// <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 class RunOnStandalonePlayer : ITestPlayerBuildModifier
public BuildPlayerOptions ModifyOptions(BuildPlayerOptions playerOptions)
{
/// <inheritdoc />
public BuildPlayerOptions ModifyOptions(BuildPlayerOptions playerOptions)
var scenesInBuild = new List<string>(playerOptions.scenes);
foreach (var scenePath in GetScenesUsingInTest())
{
var scenesInBuild = new List<string>(playerOptions.scenes);
foreach (var scenePath in GetScenesUsingInTest())
if (!scenesInBuild.Contains(scenePath))
{
if (!scenesInBuild.Contains(scenePath))
{
scenesInBuild.Add(scenePath);
}
scenesInBuild.Add(scenePath);
}

playerOptions.scenes = scenesInBuild.ToArray();
return playerOptions;
}

playerOptions.scenes = scenesInBuild.ToArray();
return playerOptions;
}
}
}
28 changes: 13 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,16 +131,16 @@ public class MyTestClass
}
```

#### ScenesUsingInTest
#### LoadScene

`ScenesUsingInTestAttribute` is an NUnit test attribute class to temporarily add scene files to "Scenes in Build" when running a play mode tests.
`LoadSceneAttribute` is an NUnit test attribute class to load scene before running test.

It has the following benefits:

- Can specify scene by name to `SceneManager.LoadScene()` method
- Can load scenes when running a test on a standalone player
- 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, test class (`TestFixture`) and test assembly.
This attribute can attached to test method only.

Usage:

Expand All @@ -152,23 +152,21 @@ using TestHelper.Attributes;
[TestFixture]
public class MyTestClass
{
[UnityTest]
[ScenesUsingInTest("Assets/MyTests/Scenes")]
public IEnumerator MyTestMethod()
[Test]
[LoadScene("Assets/MyTests/Scenes/Scene.unity")]
public void MyTestMethod()
{
yield return SceneManager.LoadSceneAsync("NotInScenesInBuild");

var cube = GameObject.Find("CubeInNotInScenesInBuild");
var cube = GameObject.Find("Cube");
Assert.That(cube, Is.Not.Null);
}
}
```

> **Note**
> Scene path can specify a file or directory path.
> The path starts with `Assets/` or `Packages/`.
> And use `name` instead of `displayName` of the package, when scenes in the package.
> (e.g., `Packages/com.nowsprinting.test-helper/Tests/Scenes/`)
> - 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
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;
}
}
}
40 changes: 0 additions & 40 deletions Runtime/Attributes/ScenesUsingInTestAttribute.cs

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.

Loading

0 comments on commit ed34b9c

Please sign in to comment.