From 379200bed2fabf0e9802196306d733cb1284a97e Mon Sep 17 00:00:00 2001 From: Stefnotch Date: Mon, 4 Nov 2019 11:56:10 +0100 Subject: [PATCH] Implement test case data source --- Source/Editor/ExampleClass.cs | 15 +++++ Source/Editor/TestAttributes.cs | 41 ++++++++++--- Source/Editor/TestCaseData.cs | 26 ++++++++ Source/Editor/TestRunner.cs | 102 +++++++++++++++++++++++++------- UnitTests.Editor.csproj | 1 + 5 files changed, 154 insertions(+), 31 deletions(-) create mode 100644 Source/Editor/TestCaseData.cs diff --git a/Source/Editor/ExampleClass.cs b/Source/Editor/ExampleClass.cs index a944eda..d4a1c6a 100644 --- a/Source/Editor/ExampleClass.cs +++ b/Source/Editor/ExampleClass.cs @@ -1,5 +1,7 @@ #if !FLAX_PLUGIN +using System.Collections.Generic; using FlaxCommunity.UnitTesting.Editor; +using FlaxEngine; namespace UnitTests.Editor { @@ -75,6 +77,19 @@ public int ExpectedResultsTests(int a, int b) { return a + b; } + + public IEnumerable AddVectorsTestCases() + { + yield return new TestCaseData(new Vector2(0, 0), new Vector2(0, 0)).Returns(new Vector2(0, 0)); + yield return new TestCaseData(new Vector2(3, 5), new Vector2(1, 1)).Returns(new Vector2(4, 6)); + yield return new TestCaseData(new Vector2(1, 2), new Vector2(-1, -3)).Returns(new Vector2(0, -1)); + } + + [TestCaseSource(nameof(AddVectorsTestCases))] + public Vector2 AddVectorsTests(Vector2 a, Vector2 b) + { + return a + b; + } } } #endif \ No newline at end of file diff --git a/Source/Editor/TestAttributes.cs b/Source/Editor/TestAttributes.cs index 3f1e0a7..bb9835e 100644 --- a/Source/Editor/TestAttributes.cs +++ b/Source/Editor/TestAttributes.cs @@ -8,41 +8,64 @@ namespace FlaxCommunity.UnitTesting.Editor [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public sealed class TestCase : Attribute { - public readonly object[] Attributes; - public object ExpectedResult { get; set; } + public TestCaseData TestCaseData { get; } = new TestCaseData(null); + public object ExpectedResult + { + get => TestCaseData.ExpectedResult; + set => TestCaseData.ExpectedResult = value; + } public TestCase(object T1) { - Attributes = new object[] { T1 }; + TestCaseData.Attributes = new object[] { T1 }; } public TestCase(object T1, object T2) { - Attributes = new object[] { T1, T2 }; + TestCaseData.Attributes = new object[] { T1, T2 }; } public TestCase(object T1, object T2, object T3) { - Attributes = new object[] { T1, T2, T3 }; + TestCaseData.Attributes = new object[] { T1, T2, T3 }; } public TestCase(object T1, object T2, object T3, object T4) { - Attributes = new object[] { T1, T2, T3, T4 }; + TestCaseData.Attributes = new object[] { T1, T2, T3, T4 }; } public TestCase(object T1, object T2, object T3, object T4, object T5) { - Attributes = new object[] { T1, T2, T3, T4, T5 }; + TestCaseData.Attributes = new object[] { T1, T2, T3, T4, T5 }; } public TestCase(object T1, object T2, object T3, object T4, object T5, object T6) { - Attributes = new object[] { T1, T2, T3, T4, T5, T6 }; + TestCaseData.Attributes = new object[] { T1, T2, T3, T4, T5, T6 }; } public TestCase(object T1, object T2, object T3, object T4, object T5, object T6, object T7) { - Attributes = new object[] { T1, T2, T3, T4, T5, T6, T7 }; + TestCaseData.Attributes = new object[] { T1, T2, T3, T4, T5, T6, T7 }; + } + } + + /// + /// Specifies a test case source method + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field)] + public sealed class TestCaseSource : Attribute + { + public readonly string MemberName; + public readonly Type SourceClassType; + public TestCaseSource(string memberName) : this(null, memberName) + { + } + + public TestCaseSource(Type sourceClassType, string memberName) + { + SourceClassType = sourceClassType; + MemberName = memberName; } } diff --git a/Source/Editor/TestCaseData.cs b/Source/Editor/TestCaseData.cs new file mode 100644 index 0000000..39081f8 --- /dev/null +++ b/Source/Editor/TestCaseData.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FlaxCommunity.UnitTesting.Editor +{ + public class TestCaseData + { + public TestCaseData(params object[] attributes) + { + Attributes = attributes; + } + + public object[] Attributes { get; set; } + + public object ExpectedResult { get; set; } + + public TestCaseData Returns(object expectedResult) + { + ExpectedResult = expectedResult; + return this; + } + } +} diff --git a/Source/Editor/TestRunner.cs b/Source/Editor/TestRunner.cs index 87aceaf..404af29 100644 --- a/Source/Editor/TestRunner.cs +++ b/Source/Editor/TestRunner.cs @@ -63,6 +63,18 @@ private static void GatherTests() _suites.Add(type); } + private static bool TryGetAttribute(MemberInfo memberInfo, out T attribute) where T : Attribute + { + attribute = memberInfo.GetCustomAttribute(); + return attribute != null; + } + + private static bool TryGetAttributes(MemberInfo memberInfo, out List attributes) where T : Attribute + { + attributes = memberInfo.GetCustomAttributes().ToList(); + return attributes.Count > 0; + } + public static void RunTests() { GatherTests(); @@ -71,7 +83,6 @@ public static void RunTests() { var suiteMethods = suite.GetMethods(); - var tests = suiteMethods.Where(m => m.GetCustomAttributes().Count() > 0 || m.GetCustomAttributes().Count() > 0).ToArray(); var setup = suiteMethods.Where(m => m.GetCustomAttributes().Count() > 0).FirstOrDefault(); var disposer = suiteMethods.Where(m => m.GetCustomAttributes().Count() > 0).FirstOrDefault(); var beforeEach = suiteMethods.Where(m => m.GetCustomAttributes().Count() > 0).FirstOrDefault(); @@ -81,9 +92,12 @@ public static void RunTests() setup?.Invoke(instance, null); - foreach (var testMethod in tests) + foreach (var testMethod in suiteMethods) { - if (testMethod.GetCustomAttributes().Count() > 0) + bool hasTestCases = TryGetAttributes(testMethod, out List testCasesAttribute); + bool hasTestCaseSource = TryGetAttribute(testMethod, out TestCaseSource testCaseSourceAttribute); + + if (TryGetAttribute(testMethod, out Test testAttribute)) { bool failed = false; beforeEach?.Invoke(instance, null); @@ -102,20 +116,45 @@ public static void RunTests() finally { afterEach?.Invoke(instance, null); - string message = $"Test '{suite.Name} {testMethod.Name}' finished with " + (failed ? "Error" : "Success"); - if (failed) + OutputResults(suite, testMethod, failed); + } + } + else if (hasTestCases || hasTestCaseSource) + { + IEnumerable testCases = Enumerable.Empty(); + + if (hasTestCases) + { + testCases = testCases.Concat(testCasesAttribute.Select(a => a.TestCaseData)); + } + if (hasTestCaseSource) + { + var sourceClassType = testCaseSourceAttribute.SourceClassType ?? suite; + object sourceObjectInstance = null; + if (sourceClassType == suite || sourceClassType == null) { - Debug.LogError(message); + sourceObjectInstance = instance; } - else + else if (!sourceClassType.IsAbstract) { - Debug.Log(message); + sourceObjectInstance = Activator.CreateInstance(sourceClassType); } + + var sourceTestCases = (IEnumerable)( + sourceClassType + .GetProperty(testCaseSourceAttribute.MemberName, typeof(IEnumerable)) + ?.GetValue(sourceObjectInstance) ?? + sourceClassType + .GetField(testCaseSourceAttribute.MemberName) + ?.GetValue(sourceObjectInstance) ?? + sourceClassType + .GetMethod(testCaseSourceAttribute.MemberName) + ?.Invoke(sourceObjectInstance, null)); + + testCases = testCases.Concat(sourceTestCases); } - } - else - { - var testCases = testMethod.GetCustomAttributes(); + + int testCaseCount = 0; int successCount = 0; foreach (var testCase in testCases) { @@ -137,27 +176,46 @@ public static void RunTests() finally { afterEach?.Invoke(instance, null); + testCaseCount++; if (!failed) successCount++; } } - int testCount = testCases.Count(); - string message = $"Test '{suite.Name} {testMethod.Name}' finished with {successCount}/{testCount} successfull test cases."; - if (successCount < testCount) - { - Debug.LogError(message); - } - else - { - Debug.Log(message); - } + OutputResults(suite, testMethod, successCount, testCaseCount); + } } disposer?.Invoke(instance, null); } } + + private static void OutputResults(Type suite, MethodInfo testMethod, int successCount, int testCount) + { + string message = $"Test '{suite.Name} {testMethod.Name}' finished with {successCount}/{testCount} successfull test cases."; + if (successCount < testCount) + { + Debug.LogError(message); + } + else + { + Debug.Log(message); + } + } + + private static void OutputResults(Type suite, MethodInfo testMethod, bool failed) + { + string message = $"Test '{suite.Name} {testMethod.Name}' finished with " + (failed ? "Error" : "Success"); + if (failed) + { + Debug.LogError(message); + } + else + { + Debug.Log(message); + } + } } } diff --git a/UnitTests.Editor.csproj b/UnitTests.Editor.csproj index 3b46309..95160aa 100644 --- a/UnitTests.Editor.csproj +++ b/UnitTests.Editor.csproj @@ -62,6 +62,7 @@ +