Skip to content

Commit 92a2184

Browse files
committed
Add data test ([TestCase(...)] in NUnit) for Unity tests generator.
1 parent fa80cba commit 92a2184

2 files changed

Lines changed: 162 additions & 6 deletions

File tree

test/DirectUnitTestDriverCore.ttinclude

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,9 +292,32 @@ private void GenerateTestDriver( IEnumerable<TestClass> testClasses )
292292
GenerateActionAssignmentIfNotEmpty( testClass.TestCleanup, "testClassInstance.TestCleanup", "instance.{0}" );
293293
foreach ( var method in testClass.TestMethods )
294294
{
295+
if ( method.ArgumentsList.Count == 0 )
296+
{
295297
#>
296298
testClassInstance.TestMethods.Add( new TestMethod( "<#= method.Name #>", new Action( instance.<#= method.Name #> ) ) );
297299
<#+
300+
}
301+
else
302+
{
303+
#>
304+
testClassInstance.TestMethods.Add(
305+
new TestMethod(
306+
"<#= method.Name #>",
307+
() => {
308+
<#+
309+
foreach( var arguments in method.ArgumentsList )
310+
{
311+
#>
312+
instance.<#= method.Name #>( <#= String.Join( ", ", arguments ) #> );
313+
<#+
314+
}
315+
#>
316+
}
317+
)
318+
);
319+
<#+
320+
}
298321
}
299322
#>
300323
}
@@ -390,7 +413,6 @@ private class TestClass
390413
/// </summary>
391414
private struct TestMethod
392415
{
393-
// TODO: TestCase, TestData etc.
394416
/// <summary>
395417
/// Gets the name of the test method.
396418
/// </summary>
@@ -399,13 +421,24 @@ private struct TestMethod
399421
/// </value>
400422
public string Name { get; private set; }
401423

424+
/// <summary>
425+
/// Gets the arguments for the test method in C# syntax
426+
/// </summary>
427+
/// <value>
428+
/// The arguments for the test method in C# syntax. Each element is arguments for single test method invocation.
429+
/// This value will not be <c>null</c>.
430+
/// </value>
431+
public IList<IList<string>> ArgumentsList { get; private set; }
432+
402433
/// <summary>
403434
/// Initializes a new instance.
404435
/// </summary>
405436
/// <param name="name">The name of the test method.</param>
406-
public TestMethod( string name )
437+
/// <param name="arguments">The arguments for the test method.</param>
438+
public TestMethod( string name, IList<IList<string>> argumentsList )
407439
{
408440
this.Name = name;
441+
this.ArgumentsList = argumentsList;
409442
}
410443
}
411444
#>

test/RoslynAnalyzerUnitTestExplorer.ttinclude

Lines changed: 127 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<#@ import namespace="System.Globalization" #>
1212
<#@ import namespace="System.Linq" #>
1313
<#@ import namespace="System.Text" #>
14+
<#@ import namespace="System.Text.RegularExpressions" #>
1415
<#@ import namespace="System.Threading.Tasks" #>
1516
<#@ import namespace="Microsoft.CodeAnalysis" #>
1617
<#@ import namespace="Microsoft.CodeAnalysis.CSharp" #>
@@ -61,6 +62,11 @@ private class TestClassExplorer
6162
/// ><summary>
6263
private readonly string _testSkippingAttributeFullName;
6364

65+
/// <summary>
66+
/// The type full name of "test case" inidicator attribute type.
67+
/// ><summary>
68+
private readonly string _testCaseAttributeFullName;
69+
6470
/// <summary>
6571
/// The array of type full names of attributes which should mark significant methods including test method, setup method, etc.
6672
/// ><summary>
@@ -76,10 +82,11 @@ private class TestClassExplorer
7682
/// <param name="testSetupAttributeFullName">The type full name of "per test setip routine" inidicator attribute type.</param>
7783
/// <param name="testCleanupAttributeFullName">The type full name of "per test cleanup routine" inidicator attribute type.</param>
7884
/// <param name="testSkippingAttributeFullName">The type full name of "skipping specified test" inidicator attribute type.</param>
85+
/// <param name="testCaseAttributeFullName">The type full name of "test case" inidicator attribute type.</param>
7986
public TestClassExplorer( string testClassAttributeFullName, string testMethodAttributeFullName,
8087
string fixtureSetupAttributeFullName, string fixtureCleanupAttributeFullName,
8188
string testSetupAttributeFullName, string testCleanupAttributeFullName,
82-
string testSkippingAttributeFullName
89+
string testSkippingAttributeFullName, string testCaseAttributeFullName
8390
)
8491
{
8592
this._testClassAttributeFullName = testClassAttributeFullName;
@@ -89,6 +96,7 @@ private class TestClassExplorer
8996
this._testSetupAttributeFullName = testSetupAttributeFullName;
9097
this._testCleanupAttributeFullName = testCleanupAttributeFullName;
9198
this._testSkippingAttributeFullName = testSkippingAttributeFullName;
99+
this._testCaseAttributeFullName = testCaseAttributeFullName;
92100
this._significantMethodAttributeFullNames =
93101
new [] { testMethodAttributeFullName, fixtureSetupAttributeFullName, fixtureCleanupAttributeFullName, testSetupAttributeFullName, testCleanupAttributeFullName };
94102
}
@@ -109,7 +117,8 @@ private class TestClassExplorer
109117
"NUnit.Framework.TestFixtureTearDownAttribute",
110118
"NUnit.Framework.SetUpAttribute",
111119
"NUnit.Framework.TearDownAttribute",
112-
"NUnit.Framework.IgnoreAttribute"
120+
"NUnit.Framework.IgnoreAttribute",
121+
"NUnit.Framework.TestCaseAttribute"
113122
);
114123
}
115124

@@ -166,8 +175,13 @@ private class TestClassExplorer
166175
g.Where( x =>
167176
x.member.GetAttributes().Any( a => GetAttributeName( a ) == this._testMethodAttributeFullName ) // Only test methods
168177
&& x.member.GetAttributes().All( a => GetAttributeName( a ) != this._testSkippingAttributeFullName ) // Excludes "Ignored" method
169-
).Select( x => new TestMethod( x.member.Name ) )
170-
.OrderBy( x => x.Name )
178+
).Select( x =>
179+
new TestMethod(
180+
x.member.Name,
181+
// only test data from attribute constructor arguments are supported.
182+
x.member.GetAttributes().Where( a => GetAttributeName( a ) == this._testCaseAttributeFullName ).Select( a => GetTestData( a ) ).ToArray()
183+
)
184+
).OrderBy( x => x.Name )
171185
)
172186
{
173187
FixtureSetup = GetSpecialMethodName( g.Select( x => x.member ), this._fixtureSetupAttributeFullName ),
@@ -198,6 +212,115 @@ private class TestClassExplorer
198212
return attribute.AttributeClass.ToDisplayString( QualifiedNameOnlyFormat );
199213
}
200214

215+
/// <summary>
216+
/// Gets test data for specified "test case".
217+
/// </summary>
218+
/// <param name="attribute">The attribute.</param>
219+
/// <returns>
220+
/// The test data for the case. The element should be primitive value types, string or null.
221+
/// </returns>
222+
private static string[] GetTestData( AttributeData attribute )
223+
{
224+
TypedConstant[] constructorArguments = attribute.ConstructorArguments.ToArray();
225+
switch ( constructorArguments.Length )
226+
{
227+
case 1:
228+
{
229+
if ( constructorArguments[ 0 ].Kind == TypedConstantKind.Array )
230+
{
231+
// [Attr(new object[]{ a, b, c })]
232+
// Returns array's content (Values is IEnumerable<TypedConstant>)
233+
return constructorArguments[ 0 ].Values.Select( ToCSharpLiteral ).ToArray();
234+
}
235+
else
236+
{
237+
goto default;
238+
}
239+
}
240+
default:
241+
{
242+
// [Attr( a, b, c )]
243+
return constructorArguments.Select( ToCSharpLiteral ).ToArray();
244+
}
245+
}
246+
}
247+
248+
/// <summary>
249+
/// Converts a Roslyn <see cref="TypedConstant" /> to a C# literal representation.
250+
/// </summary>
251+
/// <param name="constant">A Roslyn <see cref="TypedConstant" />.</param>
252+
/// <returns>A C# literal representation.</returns>
253+
private static string ToCSharpLiteral( TypedConstant constant )
254+
{
255+
if ( constant.IsNull )
256+
{
257+
return "null";
258+
}
259+
260+
switch( constant.Kind )
261+
{
262+
case TypedConstantKind.Enum:
263+
{
264+
var value = constant.Value.ToString();
265+
if ( Regex.IsMatch( value, @"^\d+$" ) )
266+
{
267+
return "( " + constant.Type.Name + " )" + constant.Value;
268+
}
269+
else
270+
{
271+
return constant.Type.Name + "." + constant.Value;
272+
}
273+
}
274+
case TypedConstantKind.Type:
275+
{
276+
// TODO: generic type
277+
return "typeof( " + constant.Value + " )";
278+
}
279+
case TypedConstantKind.Primitive:
280+
{
281+
switch( Type.GetTypeCode( constant.Value.GetType() ) )
282+
{
283+
case TypeCode.String:
284+
{
285+
return "@\"" + constant.Value.ToString().Replace( "\"", "\"\"" ) + "\"";
286+
}
287+
case TypeCode.Char:
288+
{
289+
return "'" + constant.Value + "'";
290+
}
291+
case TypeCode.Int64:
292+
{
293+
return constant.Value + "L";
294+
}
295+
case TypeCode.UInt32:
296+
{
297+
return constant.Value + "U";
298+
}
299+
case TypeCode.UInt64:
300+
{
301+
return constant.Value + "UL";
302+
}
303+
case TypeCode.Single:
304+
{
305+
return constant.Value + "F";
306+
}
307+
case TypeCode.Decimal:
308+
{
309+
return constant.Value + "M";
310+
}
311+
default:
312+
{
313+
return constant.Value.ToString();
314+
}
315+
}
316+
}
317+
default:
318+
{
319+
return "__ERROR(" + constant.Kind + ")__";
320+
}
321+
}
322+
}
323+
201324
/// <summary>
202325
/// Gets the name of the special method which marked with specified attribute.
203326
/// </summary>

0 commit comments

Comments
 (0)