Skip to content

Commit

Permalink
NUnit3: change the way test name is created so that it doesn't change…
Browse files Browse the repository at this point in the history
… between discovery and execution
  • Loading branch information
frblondin authored and Thomas Caudal committed Sep 24, 2017
1 parent 06ca10b commit 259289a
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 9 deletions.
86 changes: 85 additions & 1 deletion Src/AutoFixture.NUnit3.UnitTest/Scenario.cs
Expand Up @@ -2,6 +2,7 @@
using System.Linq;
using NUnit.Framework;
using Ploeh.TestTypeFoundation;
using NUnit.Framework.Internal;

namespace Ploeh.AutoFixture.NUnit3.UnitTest
{
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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]
Expand All @@ -378,7 +414,7 @@ public void InlineAutoDataCanBeUsedWithFrozen(int p1, int p2, [Frozen]string p3,

[Theory, AutoData]
public void NoAutoPropertiesAttributeLeavesPropertiesUnset(
[NoAutoProperties]PropertyHolder<object> ph1,
[NoAutoProperties]PropertyHolder<object> ph1,
[NoAutoProperties]PropertyHolder<string> ph2,
[NoAutoProperties]PropertyHolder<int> ph3
)
Expand All @@ -387,5 +423,53 @@ [NoAutoProperties]PropertyHolder<int> ph3
Assert.That(ph2.Property, Is.EqualTo(default(string)));
Assert.That(ph3.Property, Is.EqualTo(default(int)));
}

[Test]
public void AutoDataUsesFixedValuesForTestName()
{
var testMethod = GetTestMethod<AutoDataFixedNameAttribute>(nameof(IntroductoryTestFixedName));
Assert.That(testMethod.Name,
Is.EqualTo(nameof(IntroductoryTestFixedName) + "(auto<Int32>,auto<MyClass>)"));
}

[Test]
public void AutoDataFixedNameUsesFixedValuesForTestFullName()
{
var testMethod = GetTestMethod<AutoDataAttribute>(nameof(IntroductoryTestFixedName));
Assert.That(testMethod.Name,
Is.EqualTo(nameof(IntroductoryTestFixedName) + "(auto<Int32>,auto<MyClass>)"));
}

[Test]
public void AutoDataFixedNameGeneratesWorkingFullyQualifiedName()
{
var testMethod = GetTestMethod<AutoDataAttribute>(nameof(IntroductoryTestFixedName));
Assert.That(testMethod.FullName,
Is.EqualTo(typeof(Scenario).FullName + "." + nameof(IntroductoryTestFixedName) + "(auto<Int32>,auto<MyClass>)"));
}

[Test]
public void InlineAutoDataFixedNameUsesFixedValuesForTestName()
{
var testMethod = GetTestMethod<InlineAutoDataFixedNameAttribute>(nameof(InlineAutoDataProvidesParameterValuesWhenMissingFixedName));
Assert.That(testMethod.Name,
Is.EqualTo(nameof(InlineAutoDataProvidesParameterValuesWhenMissingFixedName) + @"(""alpha"",""beta"",auto<String>)"));
}

[Test]
public void InlineAutoDataFixedNameUsesFixedValuesForTestFullName()
{
var testMethod = GetTestMethod<InlineAutoDataFixedNameAttribute>(nameof(InlineAutoDataProvidesParameterValuesWhenMissingFixedName));
Assert.That(testMethod.FullName,
Is.EqualTo(typeof(Scenario).FullName + "." + nameof(InlineAutoDataProvidesParameterValuesWhenMissingFixedName) + @"(""alpha"",""beta"",auto<String>)"));
}

private TestMethod GetTestMethod<TAttribute>(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;
}
}
}
14 changes: 9 additions & 5 deletions Src/AutoFixture.NUnit3/AutoDataAttribute.cs
Expand Up @@ -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
{
Expand All @@ -13,11 +14,16 @@ namespace Ploeh.AutoFixture.NUnit3
/// This implementation is based on TestCaseAttribute of NUnit3
/// </summary>
[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;

/// <summary>
/// Gets or sets the current <see cref="ITestCaseParameterBuilder"/> strategy.
/// </summary>
public ITestCaseParameterBuilder ParameterBuilder { get; protected set; } = DefaultTestCaseParameterBuilder.Instance;

/// <summary>
/// Construct a <see cref="AutoDataAttribute"/>
/// </summary>
Expand Down Expand Up @@ -54,16 +60,14 @@ public IEnumerable<TestMethod> 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)
{
Expand Down
3 changes: 3 additions & 0 deletions Src/AutoFixture.NUnit3/AutoFixture.NUnit3.csproj
Expand Up @@ -71,6 +71,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="FixedNameTestCaseParameterBuilder.cs" />
<Compile Include="InlineAutoDataAttribute.cs" />
<Compile Include="AutoDataAttribute.cs" />
<Compile Include="CustomizeAttribute.cs" />
Expand All @@ -84,6 +85,8 @@
<Compile Include="ModestAttribute.cs" />
<Compile Include="NoAutoPropertiesAttribute.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="DefaultTestCaseParameterBuilder.cs" />
<Compile Include="ITestCaseParameterBuilder.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AutoFixture\AutoFixture.csproj">
Expand Down
37 changes: 37 additions & 0 deletions 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
{
/// <summary>
/// Default builder used by <see cref="AutoDataAttribute"/> and <see cref="InlineAutoDataAttribute"/> to create
/// a <see cref="TestCaseParameters"/> instance.
/// </summary>
/// <seealso cref="ITestCaseParameterBuilder" />
public class DefaultTestCaseParameterBuilder : ITestCaseParameterBuilder
{
/// <summary>
/// Gets a singleton instance of <see cref="DefaultTestCaseParameterBuilder"/>.
/// </summary>
/// <value>The instance.</value>
public static DefaultTestCaseParameterBuilder Instance { get; } = new DefaultTestCaseParameterBuilder();

/// <summary>
/// Initializes a new instance of the <see cref="DefaultTestCaseParameterBuilder"/> class.
/// </summary>
protected DefaultTestCaseParameterBuilder() { }

/// <summary>
/// Builds a <see cref="TestCaseParameters" /> from a method and the argument values.
/// </summary>
/// <param name="method">The MethodInfo for which tests are to be constructed.</param>
/// <param name="args">The argument values generated for the test case.</param>
/// <param name="autoDataStartIndex">Index at which the autodata values have been generated.</param>
public virtual TestCaseParameters Build(IMethodInfo method, object[] args, int autoDataStartIndex) =>
new TestCaseParameters(args);
}
}
100 changes: 100 additions & 0 deletions 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
{
/// <summary>
/// Builder that generates fixed values for the <see cref="TestCaseParameters"/>.OriginalArguments
/// so that the the test name will have a fixed value.<BR/>
/// This is needed for some test runners such as the Nunit test adaptor for Visual Studio.
/// </summary>
/// <seealso cref="DefaultTestCaseParameterBuilder" />
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

/// <summary>
/// Name used for producing the argument value as string for the test method so
/// that it don't vary between discovery and execution.
/// </summary>
internal const string InvariantAutoDataArgumentValue = "auto<{0}>";

/// <summary>
/// Gets a singleton instance of <see cref="FixedNameTestCaseParameterBuilder"/>.
/// </summary>
/// <value>The instance.</value>
static new public FixedNameTestCaseParameterBuilder Instance { get; } = new FixedNameTestCaseParameterBuilder();

/// <summary>
/// Initializes a new instance of the <see cref="FixedNameTestCaseParameterBuilder"/> class.
/// </summary>
protected FixedNameTestCaseParameterBuilder() { }

/// <summary>
/// Builds a <see cref="TestCaseParameters" /> from a method and the argument values.
/// </summary>
/// <param name="method">The MethodInfo for which tests are to be constructed.</param>
/// <param name="args">The argument values generated for the test case.</param>
/// <param name="autoDataStartIndex">Index at which the autodata values have been generated.</param>
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;
}

/// <summary>
/// In NUnit 3.5+ the Arguments and OriginalArguments properties are containing two different instances,
/// not in earlier versions.<BR/>
/// Unfortunately Arguments has a private setter and there is not way to change the instance other
/// than by using reflection...<BR/>
/// When running in NUnit3.5+ the test below will be false.
/// </summary>
/// <param name="parameters">The parameters.</param>
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 });
}
}
}
}
24 changes: 24 additions & 0 deletions 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
{
/// <summary>
/// Utility used by <see cref="AutoDataAttribute"/> and <see cref="InlineAutoDataAttribute"/> to create
/// a <see cref="TestCaseParameters"/> instance.
/// </summary>
public interface ITestCaseParameterBuilder
{
/// <summary>
/// Builds a <see cref="TestCaseParameters"/> from a method and the argument values.
/// </summary>
/// <param name="method">The MethodInfo for which tests are to be constructed.</param>
/// <param name="args">The argument values generated for the test case.</param>
/// <param name="autoDataStartIndex">Index at which the autodata values have been generated.</param>
TestCaseParameters Build(IMethodInfo method, object[] args, int autoDataStartIndex);
}
}
9 changes: 6 additions & 3 deletions Src/AutoFixture.NUnit3/InlineAutoDataAttribute.cs
Expand Up @@ -20,6 +20,11 @@ public class InlineAutoDataAttribute : Attribute, ITestBuilder
private readonly object[] _existingParameterValues;
private readonly IFixture _fixture;

/// <summary>
/// Gets or sets the current <see cref="ITestCaseParameterBuilder"/> strategy.
/// </summary>
public ITestCaseParameterBuilder ParameterBuilder { get; protected set; } = DefaultTestCaseParameterBuilder.Instance;

/// <summary>
/// Construct a <see cref="InlineAutoDataAttribute"/>
/// with parameter values for test method
Expand Down Expand Up @@ -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)
{
Expand Down

0 comments on commit 259289a

Please sign in to comment.