Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LoadSceneAttribute #13

Merged
merged 7 commits into from
Oct 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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