private void finishLoad(TestScene newTest, Action onCompletion) { if (CurrentTest != newTest) { // There could have been multiple loads fired after us. In such a case we want to silently remove ourselves. testContentContainer.Remove(newTest.Parent); return; } updateButtons(); bool hadTestAttributeTest = false; foreach (var m in newTest.GetType().GetMethods()) { var name = m.Name; if (name == nameof(TestScene.TestConstructor) || m.GetCustomAttribute(typeof(IgnoreAttribute), false) != null) { continue; } if (name.StartsWith("Test", StringComparison.Ordinal)) { name = name.Substring(4); } int runCount = 1; if (m.GetCustomAttribute(typeof(RepeatAttribute), false) != null) { var count = m.GetCustomAttributesData().Single(a => a.AttributeType == typeof(RepeatAttribute)).ConstructorArguments.Single().Value; Debug.Assert(count != null); runCount += (int)count; } for (int i = 0; i < runCount; i++) { string repeatSuffix = i > 0 ? $" ({i + 1})" : string.Empty; var methodWrapper = new MethodWrapper(m.GetType(), m); if (methodWrapper.GetCustomAttributes <TestAttribute>(false).SingleOrDefault() != null) { var parameters = m.GetParameters(); if (parameters.Length > 0) { var valueMatrix = new List <List <object> >(); foreach (var p in methodWrapper.GetParameters()) { var valueAttrib = p.GetCustomAttributes <ValuesAttribute>(false).SingleOrDefault(); if (valueAttrib == null) { throw new ArgumentException($"Parameter is present on a {nameof(TestAttribute)} method without values specification.", p.ParameterInfo.Name); } List <object> choices = new List <object>(); foreach (var choice in valueAttrib.GetData(p)) { choices.Add(choice); } valueMatrix.Add(choices); } foreach (var combination in valueMatrix.CartesianProduct()) { hadTestAttributeTest = true; CurrentTest.AddLabel($"{name}({string.Join(", ", combination)}){repeatSuffix}"); handleTestMethod(m, combination.ToArray()); } } else { hadTestAttributeTest = true; CurrentTest.AddLabel($"{name}{repeatSuffix}"); handleTestMethod(m); } } foreach (var tc in m.GetCustomAttributes(typeof(TestCaseAttribute), false).OfType <TestCaseAttribute>()) { hadTestAttributeTest = true; CurrentTest.AddLabel($"{name}({string.Join(", ", tc.Arguments)}){repeatSuffix}"); handleTestMethod(m, tc.Arguments); } } } // even if no [Test] or [TestCase] methods were found, [SetUp] steps should be added. if (!hadTestAttributeTest) { addSetUpSteps(); } backgroundCompiler?.SetRecompilationTarget(CurrentTest); runTests(onCompletion); updateButtons(); void addSetUpSteps() { var setUpMethods = Reflect.GetMethodsWithAttribute(newTest.GetType(), typeof(SetUpAttribute), true) .Where(m => m.Name != nameof(TestScene.SetUpTestForNUnit)); if (setUpMethods.Any()) { CurrentTest.AddStep(new SingleStepButton(true) { Text = "[SetUp]", LightColour = Color4.Teal, Action = () => setUpMethods.ForEach(s => s.Invoke(CurrentTest, null)) }); } CurrentTest.RunSetUpSteps(); } void handleTestMethod(MethodInfo methodInfo, object[] arguments = null) { addSetUpSteps(); methodInfo.Invoke(CurrentTest, arguments); CurrentTest.RunTearDownSteps(); } }