1
// ***********************************************************************
2
// Copyright (c) 2007 Charlie Poole
4
// Permission is hereby granted, free of charge, to any person obtaining
5
// a copy of this software and associated documentation files (the
6
// "Software"), to deal in the Software without restriction, including
7
// without limitation the rights to use, copy, modify, merge, publish,
8
// distribute, sublicense, and/or sell copies of the Software, and to
9
// permit persons to whom the Software is furnished to do so, subject to
10
// the following conditions:
12
// The above copyright notice and this permission notice shall be
13
// included in all copies or substantial portions of the Software.
15
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
// ***********************************************************************
25
using System.Reflection;
26
using System.Collections;
27
using NUnit.Framework;
32
/// TestCase represents a single executable test
34
public class TestCase : ITest
36
#region Instance Variables
38
private string fullName;
40
private object fixture;
41
private MethodInfo method;
43
private MethodInfo setup;
44
private MethodInfo teardown;
46
private RunState runState = RunState.Runnable;
47
private string ignoreReason;
49
private IDictionary properties;
51
private object[] args;
56
/// Initializes a new instance of the <see cref="TestCase"/> class.
58
/// <param name="name">The name of the test.</param>
59
public TestCase(string name)
61
this.name = this.fullName = name;
65
/// Initializes a new instance of the <see cref="TestCase"/> class.
67
/// <param name="method">The method implementing this test case.</param>
68
public TestCase(MethodInfo method)
70
Initialize(method, null, null);
74
/// Initializes a new instance of the <see cref="TestCase"/> class.
76
/// <param name="name">The name.</param>
77
/// <param name="fixture">The fixture.</param>
78
public TestCase(string name, object fixture)
80
Initialize(fixture.GetType().GetMethod(name), fixture, null);
83
private void Initialize(MethodInfo method, object fixture, object[] args)
88
this.name = MethodHelper.GetDisplayName(method, args);
90
this.fullName = method.ReflectedType.FullName + "." + this.name;
91
this.fixture = fixture;
92
if ( fixture == null )
93
this.fixture = Reflect.Construct(method.ReflectedType, null);
95
if (HasValidSignature(method, args))
97
IgnoreAttribute ignore = (IgnoreAttribute)Reflect.GetAttribute(this.method, typeof(IgnoreAttribute));
101
this.runState = RunState.Ignored;
102
this.ignoreReason = ignore.Reason;
106
foreach (MethodInfo m in method.ReflectedType.GetMethods())
108
if (m.IsDefined(typeof(SetUpAttribute), true))
111
if (m.IsDefined(typeof(TearDownAttribute), true))
117
/// Initializes a new instance of the <see cref="TestCase"/> class.
119
/// <param name="method">The method implementing the test case.</param>
120
/// <param name="args">The args to be used in calling the method.</param>
121
public TestCase(MethodInfo method, object[] args)
123
Initialize(method, null, args);
129
/// Gets the name of the test.
131
/// <value>The name.</value>
138
/// Gets the full name of the test.
140
/// <value>The full name.</value>
141
public string FullName
143
get { return fullName; }
147
/// Gets or sets the run state of the test.
149
/// <value>The state of the run.</value>
150
public RunState RunState
152
get { return runState; }
153
set { runState = value; }
157
/// Gets or sets the ignore reason.
159
/// <value>The ignore reason.</value>
160
public string IgnoreReason
162
get { return ignoreReason; }
163
set { ignoreReason = value; }
167
/// Gets the properties of the test.
169
/// <value>The properties dictionary.</value>
170
public System.Collections.IDictionary Properties
174
if (properties == null)
176
properties = new Hashtable();
178
object[] attrs = this.method.GetCustomAttributes(typeof(PropertyAttribute), true);
179
foreach (PropertyAttribute attr in attrs)
180
foreach( DictionaryEntry entry in attr.Properties )
181
this.Properties[entry.Key] = entry.Value;
189
/// Gets the test case count.
191
/// <value>The test case count.</value>
192
public int TestCaseCount
198
#region Public Methods
202
/// <returns>A TestResult</returns>
203
public TestResult Run()
205
return Run( new NullListener() );
211
/// <param name="listener">A TestListener to handle test events</param>
212
/// <returns>A TestResult</returns>
213
public TestResult Run(TestListener listener)
215
listener.TestStarted(this);
217
TestResult result = new TestResult(this);
218
Run(result, listener);
220
listener.TestFinished(result);
226
#region Protected Methods
228
/// Performs SetUp for the test.
230
protected virtual void SetUp()
234
Assert.That(HasValidSetUpTearDownSignature(setup), "Invalid SetUp method: must return void and have no arguments");
240
/// Performs TearDown for the test.
242
protected virtual void TearDown()
244
if (teardown != null)
246
Assert.That(HasValidSetUpTearDownSignature(teardown), "Invalid TearDown method: must return void and have no arguments");
247
InvokeMethod(teardown);
252
/// Runs the test and handles any exceptions.
254
/// <param name="result">The result.</param>
255
/// <param name="listener">The listener.</param>
256
protected virtual void Run(TestResult result, TestListener listener)
258
IgnoreAttribute ignore = (IgnoreAttribute)Reflect.GetAttribute(method, typeof(IgnoreAttribute));
259
if (this.RunState == RunState.NotRunnable)
260
result.Failure(this.ignoreReason);
261
else if ( ignore != null )
262
result.NotRun(ignore.Reason);
270
catch (NUnitLiteException nex)
272
result.RecordException(nex.InnerException);
275
catch (System.Threading.ThreadAbortException)
282
result.RecordException(ex);
288
/// Runs SetUp, invokes the test and runs TearDown.
290
protected void RunBare()
306
protected virtual void RunTest()
310
InvokeMethod( this.method, this.args );
311
ProcessNoException(this.method);
313
catch (NUnitLiteException ex)
315
ProcessException(this.method, ex.InnerException);
320
/// Invokes a method on the test fixture.
322
/// <param name="method">The method.</param>
323
/// <param name="args">The args.</param>
324
protected void InvokeMethod(MethodInfo method, params object[] args)
326
Reflect.InvokeMethod(method, this.fixture, args);
330
#region Private Methods
332
/// Determines whether the method has a valid signature and sets
333
/// the RunState to NotRunnable if it does not.
335
/// <param name="method">The method.</param>
336
/// <param name="args">The args.</param>
338
/// <c>true</c> if the signature is valid; otherwise, <c>false</c>.
340
private bool HasValidSignature(MethodInfo method, object[] args)
342
if (method.ReturnType != typeof(void))
344
this.runState = RunState.NotRunnable;
345
this.ignoreReason = "A TestMethod must return void";
349
int argsNeeded = method.GetParameters().Length;
350
int argsPassed = args == null ? 0 : args.Length;
352
if (argsNeeded == 0 && argsPassed > 0)
354
this.runState = RunState.NotRunnable;
355
this.ignoreReason = "Arguments may not be specified for a method with no parameters";
359
if (argsNeeded > 0 && argsPassed == 0)
361
this.runState = RunState.NotRunnable;
362
this.ignoreReason = "No arguments provided for a method requiring them";
366
if (argsNeeded != argsPassed)
368
this.runState = RunState.NotRunnable;
369
this.ignoreReason = string.Format("Expected {0} arguments, but received {1}", argsNeeded, argsPassed);
376
private static bool HasValidSetUpTearDownSignature(MethodInfo method)
378
return method.ReturnType == typeof(void)
379
&& method.GetParameters().Length == 0; ;
382
private static void ProcessNoException(MethodInfo method)
384
ExpectedExceptionAttribute exceptionAttribute =
385
(ExpectedExceptionAttribute)Reflect.GetAttribute(method, typeof(ExpectedExceptionAttribute));
387
if (exceptionAttribute != null)
388
Assert.Fail("Expected Exception of type <{0}>, but none was thrown", exceptionAttribute.ExpectedException);
391
private void ProcessException(MethodInfo method, Exception caughtException)
393
ExpectedExceptionAttribute exceptionAttribute =
394
(ExpectedExceptionAttribute)Reflect.GetAttribute(method, typeof(ExpectedExceptionAttribute));
396
if (exceptionAttribute == null)
397
throw new NUnitLiteException("", caughtException);
399
Type expectedType = exceptionAttribute.ExpectedException;
400
if ( expectedType != null && expectedType != caughtException.GetType() )
401
Assert.Fail("Expected Exception of type <{0}>, but was <{1}>", exceptionAttribute.ExpectedException, caughtException.GetType());
403
MethodInfo handler = GetExceptionHandler(method.ReflectedType, exceptionAttribute.Handler);
406
InvokeMethod( handler, caughtException );
409
private MethodInfo GetExceptionHandler(Type type, string handlerName)
411
if (handlerName == null && Reflect.HasInterface( type, typeof(IExpectException) ) )
412
handlerName = "HandleException";
414
if (handlerName == null)
417
MethodInfo handler = Reflect.GetMethod( type, handlerName,
418
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static,
419
new Type[] { typeof(Exception) });
422
Assert.Fail("The specified exception handler {0} was not found", handlerName);