From 259289ad2e6233e06db9ccb97989a31e4150423b Mon Sep 17 00:00:00 2001 From: Thomas Caudal Date: Tue, 4 Oct 2016 10:19:43 +0200 Subject: [PATCH] NUnit3: change the way test name is created so that it doesn't change between discovery and execution --- Src/AutoFixture.NUnit3.UnitTest/Scenario.cs | 86 ++++++++++++++- Src/AutoFixture.NUnit3/AutoDataAttribute.cs | 14 ++- .../AutoFixture.NUnit3.csproj | 3 + .../DefaultTestCaseParameterBuilder.cs | 37 +++++++ .../FixedNameTestCaseParameterBuilder.cs | 100 ++++++++++++++++++ .../ITestCaseParameterBuilder.cs | 24 +++++ .../InlineAutoDataAttribute.cs | 9 +- 7 files changed, 264 insertions(+), 9 deletions(-) create mode 100644 Src/AutoFixture.NUnit3/DefaultTestCaseParameterBuilder.cs create mode 100644 Src/AutoFixture.NUnit3/FixedNameTestCaseParameterBuilder.cs create mode 100644 Src/AutoFixture.NUnit3/ITestCaseParameterBuilder.cs diff --git a/Src/AutoFixture.NUnit3.UnitTest/Scenario.cs b/Src/AutoFixture.NUnit3.UnitTest/Scenario.cs index 4c93f1454..6981f65f3 100644 --- a/Src/AutoFixture.NUnit3.UnitTest/Scenario.cs +++ b/Src/AutoFixture.NUnit3.UnitTest/Scenario.cs @@ -2,6 +2,7 @@ using System.Linq; using NUnit.Framework; using Ploeh.TestTypeFoundation; +using NUnit.Framework.Internal; namespace Ploeh.AutoFixture.NUnit3.UnitTest { @@ -64,6 +65,25 @@ public void FreezeSecondParameterOnlyFreezesSubsequentParameters(Guid g1, [Froze // Teardown } + public class AutoDataFixedNameAttribute : AutoDataAttribute + { + public AutoDataFixedNameAttribute() + { + ParameterBuilder = FixedNameTestCaseParameterBuilder.Instance; + } + } + [Test, AutoDataFixedName] + public void IntroductoryTestFixedName( + int expectedNumber, MyClass sut) + { + // Fixture setup + // Exercise system + int result = sut.Echo(expectedNumber); + // Verify outcome + Assert.AreEqual(expectedNumber, result); + // Teardown + } + [Test, AutoData] public void ModestCreatesParameterWithModestConstructor([Modest]MultiUnorderedConstructorType p) { @@ -358,6 +378,22 @@ public void InlineAutoDataProvidesParameterValuesWhenMissing(string p1, string p Assert.That(p3, Is.Not.Null); } + public class InlineAutoDataFixedNameAttribute : InlineAutoDataAttribute + { + public InlineAutoDataFixedNameAttribute(params object[] arguments) : base(arguments) + { + ParameterBuilder = FixedNameTestCaseParameterBuilder.Instance; + } + } + [Theory] + [InlineAutoDataFixedName("alpha", "beta")] + public void InlineAutoDataProvidesParameterValuesWhenMissingFixedName(string p1, string p2, string p3) + { + Assert.That(p1, Is.EqualTo("alpha")); + Assert.That(p2, Is.EqualTo("beta")); + Assert.That(p3, Is.Not.Null); + } + [Theory] [InlineAutoData] [InlineAutoData] @@ -378,7 +414,7 @@ public void InlineAutoDataCanBeUsedWithFrozen(int p1, int p2, [Frozen]string p3, [Theory, AutoData] public void NoAutoPropertiesAttributeLeavesPropertiesUnset( - [NoAutoProperties]PropertyHolder ph1, + [NoAutoProperties]PropertyHolder ph1, [NoAutoProperties]PropertyHolder ph2, [NoAutoProperties]PropertyHolder ph3 ) @@ -387,5 +423,53 @@ [NoAutoProperties]PropertyHolder ph3 Assert.That(ph2.Property, Is.EqualTo(default(string))); Assert.That(ph3.Property, Is.EqualTo(default(int))); } + + [Test] + public void AutoDataUsesFixedValuesForTestName() + { + var testMethod = GetTestMethod(nameof(IntroductoryTestFixedName)); + Assert.That(testMethod.Name, + Is.EqualTo(nameof(IntroductoryTestFixedName) + "(auto,auto)")); + } + + [Test] + public void AutoDataFixedNameUsesFixedValuesForTestFullName() + { + var testMethod = GetTestMethod(nameof(IntroductoryTestFixedName)); + Assert.That(testMethod.Name, + Is.EqualTo(nameof(IntroductoryTestFixedName) + "(auto,auto)")); + } + + [Test] + public void AutoDataFixedNameGeneratesWorkingFullyQualifiedName() + { + var testMethod = GetTestMethod(nameof(IntroductoryTestFixedName)); + Assert.That(testMethod.FullName, + Is.EqualTo(typeof(Scenario).FullName + "." + nameof(IntroductoryTestFixedName) + "(auto,auto)")); + } + + [Test] + public void InlineAutoDataFixedNameUsesFixedValuesForTestName() + { + var testMethod = GetTestMethod(nameof(InlineAutoDataProvidesParameterValuesWhenMissingFixedName)); + Assert.That(testMethod.Name, + Is.EqualTo(nameof(InlineAutoDataProvidesParameterValuesWhenMissingFixedName) + @"(""alpha"",""beta"",auto)")); + } + + [Test] + public void InlineAutoDataFixedNameUsesFixedValuesForTestFullName() + { + var testMethod = GetTestMethod(nameof(InlineAutoDataProvidesParameterValuesWhenMissingFixedName)); + Assert.That(testMethod.FullName, + Is.EqualTo(typeof(Scenario).FullName + "." + nameof(InlineAutoDataProvidesParameterValuesWhenMissingFixedName) + @"(""alpha"",""beta"",auto)")); + } + + private TestMethod GetTestMethod(string testName) where TAttribute : Attribute, NUnit.Framework.Interfaces.ITestBuilder + { + var method = new MethodWrapper(typeof(Scenario), testName); + var inlineAttribute = (TAttribute)Attribute.GetCustomAttribute(method.MethodInfo, typeof(TAttribute)); + var testMethod = inlineAttribute.BuildFrom(method, null).Single(); + return testMethod; + } } } diff --git a/Src/AutoFixture.NUnit3/AutoDataAttribute.cs b/Src/AutoFixture.NUnit3/AutoDataAttribute.cs index 3b771721e..8c0fcdea6 100644 --- a/Src/AutoFixture.NUnit3/AutoDataAttribute.cs +++ b/Src/AutoFixture.NUnit3/AutoDataAttribute.cs @@ -5,6 +5,7 @@ using NUnit.Framework.Internal; using NUnit.Framework.Internal.Builders; using Ploeh.AutoFixture.Kernel; +using System.Diagnostics.CodeAnalysis; namespace Ploeh.AutoFixture.NUnit3 { @@ -13,11 +14,16 @@ namespace Ploeh.AutoFixture.NUnit3 /// This implementation is based on TestCaseAttribute of NUnit3 /// [AttributeUsage(AttributeTargets.Method)] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "This attribute is the root of a potential attribute hierarchy.")] + [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "This attribute is the root of a potential attribute hierarchy.")] public class AutoDataAttribute : Attribute, ITestBuilder { private readonly IFixture _fixture; + /// + /// Gets or sets the current strategy. + /// + public ITestCaseParameterBuilder ParameterBuilder { get; protected set; } = DefaultTestCaseParameterBuilder.Instance; + /// /// Construct a /// @@ -54,16 +60,14 @@ public IEnumerable BuildFrom(IMethodInfo method, Test suite) yield return test; } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This method is always expected to return an instance of the TestCaseParameters class.")] + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This method is always expected to return an instance of the TestCaseParameters class.")] private TestCaseParameters GetParametersForMethod(IMethodInfo method) { try { var parameters = method.GetParameters(); - var parameterValues = this.GetParameterValues(parameters); - - return new TestCaseParameters(parameterValues.ToArray()); + return ParameterBuilder.Build(method, parameterValues.ToArray(), 0); } catch (Exception ex) { diff --git a/Src/AutoFixture.NUnit3/AutoFixture.NUnit3.csproj b/Src/AutoFixture.NUnit3/AutoFixture.NUnit3.csproj index 22ba43f8a..05e20b242 100644 --- a/Src/AutoFixture.NUnit3/AutoFixture.NUnit3.csproj +++ b/Src/AutoFixture.NUnit3/AutoFixture.NUnit3.csproj @@ -71,6 +71,7 @@ + @@ -84,6 +85,8 @@ + + diff --git a/Src/AutoFixture.NUnit3/DefaultTestCaseParameterBuilder.cs b/Src/AutoFixture.NUnit3/DefaultTestCaseParameterBuilder.cs new file mode 100644 index 000000000..da40d9e0b --- /dev/null +++ b/Src/AutoFixture.NUnit3/DefaultTestCaseParameterBuilder.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework.Interfaces; +using NUnit.Framework.Internal; + +namespace Ploeh.AutoFixture.NUnit3 +{ + /// + /// Default builder used by and to create + /// a instance. + /// + /// + public class DefaultTestCaseParameterBuilder : ITestCaseParameterBuilder + { + /// + /// Gets a singleton instance of . + /// + /// The instance. + public static DefaultTestCaseParameterBuilder Instance { get; } = new DefaultTestCaseParameterBuilder(); + + /// + /// Initializes a new instance of the class. + /// + protected DefaultTestCaseParameterBuilder() { } + + /// + /// Builds a from a method and the argument values. + /// + /// The MethodInfo for which tests are to be constructed. + /// The argument values generated for the test case. + /// Index at which the autodata values have been generated. + public virtual TestCaseParameters Build(IMethodInfo method, object[] args, int autoDataStartIndex) => + new TestCaseParameters(args); + } +} diff --git a/Src/AutoFixture.NUnit3/FixedNameTestCaseParameterBuilder.cs b/Src/AutoFixture.NUnit3/FixedNameTestCaseParameterBuilder.cs new file mode 100644 index 000000000..ef273286a --- /dev/null +++ b/Src/AutoFixture.NUnit3/FixedNameTestCaseParameterBuilder.cs @@ -0,0 +1,100 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NUnit.Framework.Interfaces; +using NUnit.Framework.Internal; +using System.Globalization; + +namespace Ploeh.AutoFixture.NUnit3 +{ + /// + /// Builder that generates fixed values for the .OriginalArguments + /// so that the the test name will have a fixed value.
+ /// This is needed for some test runners such as the Nunit test adaptor for Visual Studio. + ///
+ /// + public class FixedNameTestCaseParameterBuilder : DefaultTestCaseParameterBuilder + { + #region FixedNameArgument + private class FixedNameArgument + { + public Type Type { get; } + + public FixedNameArgument(Type type) + { + this.Type = type ?? throw new ArgumentNullException(nameof(type)); + } + + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, InvariantAutoDataArgumentValue, Type.Name); + } + } + #endregion + + /// + /// Name used for producing the argument value as string for the test method so + /// that it don't vary between discovery and execution. + /// + internal const string InvariantAutoDataArgumentValue = "auto<{0}>"; + + /// + /// Gets a singleton instance of . + /// + /// The instance. + static new public FixedNameTestCaseParameterBuilder Instance { get; } = new FixedNameTestCaseParameterBuilder(); + + /// + /// Initializes a new instance of the class. + /// + protected FixedNameTestCaseParameterBuilder() { } + + /// + /// Builds a from a method and the argument values. + /// + /// The MethodInfo for which tests are to be constructed. + /// The argument values generated for the test case. + /// Index at which the autodata values have been generated. + public override TestCaseParameters Build(IMethodInfo method, object[] args, int autoDataStartIndex) + { + if (method == null) + { + throw new ArgumentNullException(nameof(method)); + } + + var result = base.Build(method, args, autoDataStartIndex); + var methodParameters = method.GetParameters(); + + FixUniqueArgumentReference(result); + + for (int i = autoDataStartIndex; i < result.OriginalArguments.Length; i++) + { + result.OriginalArguments[i] = new FixedNameArgument(methodParameters[i].ParameterType); + } + + return result; + } + + /// + /// In NUnit 3.5+ the Arguments and OriginalArguments properties are containing two different instances, + /// not in earlier versions.
+ /// Unfortunately Arguments has a private setter and there is not way to change the instance other + /// than by using reflection...
+ /// When running in NUnit3.5+ the test below will be false. + ///
+ /// The parameters. + private static void FixUniqueArgumentReference(TestCaseParameters parameters) + { + if (ReferenceEquals(parameters.Arguments, parameters.OriginalArguments)) + { + var clonedArguments = new object[parameters.Arguments.Length]; + Array.Copy((Array)parameters.Arguments, (Array)clonedArguments, parameters.Arguments.Length); + + var property = typeof(TestCaseParameters).GetProperty(nameof(TestCaseParameters.Arguments)); + property.GetSetMethod(true).Invoke(parameters, new object[] { clonedArguments }); + } + } + } +} diff --git a/Src/AutoFixture.NUnit3/ITestCaseParameterBuilder.cs b/Src/AutoFixture.NUnit3/ITestCaseParameterBuilder.cs new file mode 100644 index 000000000..32cf90d87 --- /dev/null +++ b/Src/AutoFixture.NUnit3/ITestCaseParameterBuilder.cs @@ -0,0 +1,24 @@ +using NUnit.Framework.Interfaces; +using NUnit.Framework.Internal; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Ploeh.AutoFixture.NUnit3 +{ + /// + /// Utility used by and to create + /// a instance. + /// + public interface ITestCaseParameterBuilder + { + /// + /// Builds a from a method and the argument values. + /// + /// The MethodInfo for which tests are to be constructed. + /// The argument values generated for the test case. + /// Index at which the autodata values have been generated. + TestCaseParameters Build(IMethodInfo method, object[] args, int autoDataStartIndex); + } +} diff --git a/Src/AutoFixture.NUnit3/InlineAutoDataAttribute.cs b/Src/AutoFixture.NUnit3/InlineAutoDataAttribute.cs index acadf34a8..5a465feac 100644 --- a/Src/AutoFixture.NUnit3/InlineAutoDataAttribute.cs +++ b/Src/AutoFixture.NUnit3/InlineAutoDataAttribute.cs @@ -20,6 +20,11 @@ public class InlineAutoDataAttribute : Attribute, ITestBuilder private readonly object[] _existingParameterValues; private readonly IFixture _fixture; + /// + /// Gets or sets the current strategy. + /// + public ITestCaseParameterBuilder ParameterBuilder { get; protected set; } = DefaultTestCaseParameterBuilder.Instance; + /// /// Construct a /// with parameter values for test method @@ -77,10 +82,8 @@ private TestCaseParameters GetParametersForMethod(IMethodInfo method) try { var parameters = method.GetParameters(); - var parameterValues = this.GetParameterValues(parameters); - - return new TestCaseParameters(parameterValues.ToArray()); + return ParameterBuilder.Build(method, parameterValues.ToArray(), _existingParameterValues.Count()); } catch (Exception ex) {