Edit: Solution below.
After my first excitement about the TestFixtureSource and your example, I ran into a strange issue. My tests all used to pass, then I refactored them to use the TestFixtureSource following your Unite talk. I really copied the same approach, but for some reason the tests now randomly seem to fail without error messages and most of the tests actually appear as skipped.

Any idea what could be going wrong here?
I’m having troubles finding an issue. If I remove some of the tests, all others pass again, but some of them are still skipped. If I remove other tests, again other tests randomly fail or are skipped.
namespace Nementic.X.SoloMode
{
using Nementic.X.Worldmap;
using NUnit.Framework;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
public class ZodiacScenesProvider : IEnumerable<string>
{
IEnumerator<string> IEnumerable<string>.GetEnumerator()
{
var sceneGUIDs = AssetDatabase.FindAssets("t: Scene", new[] { "Assets/Runtime/SoloMode/LevelSelectionScenes" });
for (int i = 0; i < sceneGUIDs.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(sceneGUIDs[i]);
yield return path;
}
}
public IEnumerator GetEnumerator() => ((IEnumerable<string>)this).GetEnumerator();
}
[TestFixture]
[TestFixtureSource(typeof(ZodiacScenesProvider))]
public class SaveKeyTests
{
private readonly string scenePath;
private Scene scene;
public SaveKeyTests(string scenePath)
{
this.scenePath = scenePath;
}
[OneTimeSetUp]
public void LoadScene()
{
this.scene = EditorSceneManager.OpenScene(this.scenePath, OpenSceneMode.Single);
}
[Test]
public void ValidateMultipleBoardsFilePathCorrect()
{
var multipleBoardsVariant = AssetDatabase.LoadAssetAtPath<LevelVariant>(
"Assets/Runtime/SoloMode/SoloManagement/LV_ScriptableObjects/LV-02_MultipleBoards.asset");
multipleBoardsVariant.hideFlags = HideFlags.DontUnloadUnusedAsset;
Assert.That(multipleBoardsVariant != null, "No level variant source found.");
foreach (var soloLevel in Object.FindObjectsOfType<SoloLevel>())
{
// Ignore tutorials (indicated by presence of a guide).
if (string.IsNullOrEmpty(soloLevel.guideCharacterKey) &&
soloLevel.LevelVariant == multipleBoardsVariant)
{
Assert.That(soloLevel.FilePaths.Length == 1);
Assert.That(soloLevel.FilePaths[0].Contains("Multiple_Boards"),
AssertMessage("Incorrect file path for multiple boards.", scene, soloLevel));
}
}
Resources.UnloadAsset(multipleBoardsVariant);
}
private static string AssertMessage(string message, Scene scene, Object context)
{
return $"{message} Context: '{context.name}'. Scene: {scene.name}.";
}
[Test]
public void ValidateMultipleBoardsCorrectSettings()
{
var multipleBoardsVariant = AssetDatabase.LoadAssetAtPath<LevelVariant>(
"Assets/Runtime/SoloMode/SoloManagement/LV_ScriptableObjects/LV-02_MultipleBoards.asset");
Assert.NotNull(multipleBoardsVariant);
foreach (var soloLevel in Object.FindObjectsOfType<SoloLevel>())
{
if (soloLevel.FilePaths.Length == 1)
{
string ubongoPath = soloLevel.FilePaths[0];
if (ubongoPath.Contains("Multiple_Boards"))
{
Assert.That(soloLevel.LevelVariant == multipleBoardsVariant, "Solo Level with incorrect level variant: " + soloLevel.name + " - " + scene.name);
Assert.That(soloLevel.RandomizeBoardRotation == false, "Solo Level with incorrect RandomizeBoardRotation: " + soloLevel.name + " - " + scene.name);
Assert.That(soloLevel.RandomizeBoardFlip == true, "Solo Level with incorrect RandomizeBoardFlip: " + soloLevel.name + " - " + scene.name);
Assert.That(soloLevel.HasTimer == false, "Solo Level with incorrect HasTimer: " + soloLevel.name + " - " + scene.name);
}
}
}
}
[Test]
public void ValidateZodiacTotalCrystalCount()
{
int expectedSum = Object.FindObjectsOfType<SoloLevel>().Length * 3;
var label = Object.FindObjectOfType<WorldmapCrystalCount>();
Assert.AreEqual(expectedSum, label.maxCount, "Mismatched crystal count for zodiac HUD: " + scene.name);
}
[Test]
public void ValidateZodiacFinalLevelCrystalCount()
{
int totalSum = Object.FindObjectsOfType<SoloLevel>().Length * 3;
foreach (var gate in Object.FindObjectsOfType<CrystalGateLock>())
{
Assert.Less(gate.crystalUnlockCount, totalSum - 3, "Gate unlock count must be less than total crystal count of all level minus the last one: " + scene.name + " - " + gate.name);
}
}
[Test]
public void ValidateZodiacHUDCrystalCount()
{
var crystalCount = Object.FindObjectOfType<WorldmapCrystalCount>();
var nodeKey = Object.FindObjectOfType<SoloLevel>().saveKey;
string animalName = nodeKey.Split('/')[1];
Assert.That(crystalCount.saveKey.Contains(animalName), "Zodiac HUD crystal count save key should contain the animal name as specified in the nodes save keys. Scene: " + scene.name);
}
}
}
After quite some investigation I found my issue. My example code only shows a couple of the tests, there are a few more methods which are very similar. The entire class is not refactored well, since I was only now starting to reorganize the code. In one of the tests, I opened another unrelated scene to test something, which used to work in the old structure, but now with the TestFixtureSource it breaks, since the scene reference suddenly becomes null. However, the issue was hidden quite well since it only appeared for specific ordering of the tests and sometimes not at all.
Additionally, I was experiencing a visual glitch in the test runner, where some of the tests showed as failed, but without error message. When I toggled the display state of failed/passing tests in the top right corner of the runner window, the visuals were updated and showed the correct symbols again.