Mini NUnit
Tue, 22 Jul 2008 23:53:35 -0400 - Author:
This is a subset of the NUnit framework, which I wrote today in a few hours. I wrote this because of the need to run tests from the program itself, instead of from Sharp Develop.
The code does not rely on or derive from the original NUnit code, and stands alone.
The code does not rely on or derive from the original NUnit code, and stands alone.
/* *(C) 2008 Peter O. * Permission is granted to anyone to copy, modify, * and redistribute this file. Attribution to the * author would be appreciated but is not required. * No warranties are provided and all liabilities * are disclaimed. */ using System; using System.Reflection; using System.Collections.Generic; using System.Globalization; using NUnit.Framework;
namespace NUnit.Framework { [Serializable] public class AssertionException : Exception { public AssertionException() : base(){} public AssertionException(string message) : base(message){} public AssertionException(string message, Exception innerException) : base(message, innerException){} protected AssertionException( System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context){ } }
[Serializable] public class IgnoreException : Exception { public IgnoreException() : base(){} public IgnoreException(string message) : base(message){} public IgnoreException(string message, Exception innerException) : base(message, innerException){} protected IgnoreException( System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context){ } }
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple=false)] public sealed class IgnoreAttribute : Attribute { private string reason; public IgnoreAttribute(string reason){ this.reason=reason; } public string Reason { get { return reason;} set { reason=value;} } }
[AttributeUsage(AttributeTargets.Method, AllowMultiple=false)] public sealed class ExpectedExceptionAttribute : Attribute { private Type exceptionType; public ExpectedExceptionAttribute(Type exception){ this.exceptionType=exception; } public Type ExceptionType { get { return exceptionType;} set { exceptionType=value;} } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple=true)] public sealed class CategoryAttribute : Attribute { private Type name; public CategoryAttribute(Type name){ this.name=name; } public Type Name { get { return name;} set { name=value;} } }
[AttributeUsage(AttributeTargets.Method, AllowMultiple=false)] public sealed class TestAttribute : Attribute { public TestAttribute(){ } }
[AttributeUsage(AttributeTargets.Method, AllowMultiple=false)] public sealed class SetUpAttribute : Attribute { public SetUpAttribute(){ } }
[AttributeUsage(AttributeTargets.Method, AllowMultiple=false)] public sealed class TearDownAttribute : Attribute { public TearDownAttribute(){ } }
[AttributeUsage(AttributeTargets.Method, AllowMultiple=false)] public sealed class TestFixtureSetUpAttribute : Attribute { public TestFixtureSetUpAttribute(){ } }
[AttributeUsage(AttributeTargets.Method, AllowMultiple=false)] public sealed class TestFixtureTearDownAttribute : Attribute { public TestFixtureTearDownAttribute(){ } }
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)] public sealed class TestFixtureAttribute : Attribute { public TestFixtureAttribute(){ } }
public static class Assert { public static void AreEqual(string x, string y, string msg) { if(x == null && y == null) return; if((x == null || y == null) || !x.Equals(y)) ThrowOnUnexpected(x,y,msg); } private static void ThrowOnUnexpected(object x, object y, string msg){ throw new AssertionException(string.Format(CultureInfo.InvariantCulture, "Expected: {0}, but was: {1}. {2}", x == null ? "<null>" : x, y == null ? "<null>" : y, msg)); } public static void AreEqual(int x, int y, string msg){ if(x!=y) ThrowOnUnexpected(x,y,msg); } public static void AreEqual(long x, long y, string msg){ if(x!=y) ThrowOnUnexpected(x,y,msg); } public static void AreEqual(float x, float y, double delta, string msg){ if(Single.IsNaN(x) && Single.IsNaN(y)) return; if(Single.IsNaN(x) || Single.IsNaN(y) || Math.Abs(x-y)>delta) ThrowOnUnexpected(x,y,msg); } public static void AreEqual(double x, double y, double delta, string msg){ if(Double.IsNaN(x) && Double.IsNaN(y)) return; if(Double.IsNaN(x) || Double.IsNaN(y) || Math.Abs(x-y)>delta) ThrowOnUnexpected(x,y,msg); } public static void AreEqual(decimal x, decimal y, string msg){ if(x!=y) ThrowOnUnexpected(x,y,msg); } public static void AreEqual(object x, object y, string msg) { if(x == null && y == null) return; if((x == null || y == null)) ThrowOnUnexpected(x,y,msg); bool isArrayX = x.GetType().IsArray; bool isArrayY = y.GetType().IsArray;
if(isArrayX && isArrayY) { Array arrayX =(Array) x; Array arrayY =(Array) y;
if(arrayX.Length != arrayY.Length) throw new AssertionException(string.Format(CultureInfo.InvariantCulture, "Length of arrays differs. Expected: {0}, but was: {1}. {2}", arrayX.Length, arrayY.Length, msg));
for(int i = 0; i < arrayX.Length; i++) { object itemX = arrayX.GetValue(i); object itemY = arrayY.GetValue(i); if(!itemX.Equals(itemY)) throw new AssertionException(string.Format(CultureInfo.InvariantCulture, "Arrays differ at position {0}. Expected: {1}, but was: {2}. {3}", i, itemX, itemY, msg)); } } else if(!x.Equals(y)) { ThrowOnUnexpected(x,y,msg); } }
public static void Fail(string msg) { throw new AssertionException(msg); } public static void Ignore(string msg) { throw new IgnoreException(msg); }
public static void IsFalse(bool value, string msg) { if(value) throw new AssertionException(msg); }
public static void IsTrue(bool value, string msg) { if(!value) throw new AssertionException(msg); }
public static void AssertionException(object value, string msg) { if(value == null) throw new AssertionException(msg); }
public static void IsNull(object value, string msg) { if(value != null) throw new AssertionException(msg); }
public static void IsNotNull(object value, string msg) { if(value == null) throw new AssertionException(msg); } public static void LessOrEqual(IComparable value1, IComparable value2, string msg) { if(value1.CompareTo(value2)>0) throw new AssertionException(msg); } public static void Less(IComparable value1, IComparable value2, string msg) { if(value1.CompareTo(value2)>=0) throw new AssertionException(msg); } public static void GreaterOrEqual(IComparable value1, IComparable value2, string msg) { if(value1.CompareTo(value2)<0) throw new AssertionException(msg); } public static void Greater(IComparable value1, IComparable value2, string msg) { if(value1.CompareTo(value2)<=0) throw new AssertionException(msg); } public static void AreSame(object x, object y, string msg) { if(!object.ReferenceEquals(x, y)) throw new AssertionException(msg); } } } namespace PeterO.Testing { public enum TestResultType { Unknown, Passed, Failed, Ignored } public sealed class TestResult { TestResultType result; string message; string stackTrace; string name; public TestResult(string name, TestResultType result, string message, string stackTrace){ this.name=name; this.message=message; this.result=result; this.stackTrace=stackTrace; } public string Name { get { return name; } } public TestResultType Result { get { return result; } } public string Message { get { return message; } } public string StackTrace { get { return stackTrace; } } public override string ToString() { System.Text.StringBuilder builder=new System.Text.StringBuilder(); builder.AppendFormat(CultureInfo.InvariantCulture, "Name: {0}\",this.Name); switch(this.Result){ case TestResultType.Failed: builder.AppendFormat("Result: Failure\Message: {0}\StackTrace: {1}\", this.Message,this.StackTrace); break; case TestResultType.Passed: builder.Append("Result: Success\"); break; case TestResultType.Ignored: builder.Append("Result: Ignored\"); break; default: builder.Append("Result: Unknown\"); break; } return builder.ToString(); } } public static class TestRunner { private static bool HasCustomAttribute(Type type, string typeName){ foreach(Object attr in type.GetCustomAttributes(false)){ if(attr.GetType().FullName==typeName) return true; } return false; } private static bool HasCustomAttribute(MethodInfo method, string typeName){ foreach(Object attr in method.GetCustomAttributes(false)){ if(attr.GetType().FullName==typeName) return true; } return false; } private static object FindCustomAttribute(Type method, string typeName){ foreach(Object attr in method.GetCustomAttributes(false)){ if(attr.GetType().FullName==typeName) return attr; } return null; } private static object FindCustomAttribute(MethodInfo method, string typeName){ foreach(Object attr in method.GetCustomAttributes(false)){ if(attr.GetType().FullName==typeName) return attr; } return null; } private static List<MethodInfo> FindMethodsWithAttribute(Type type, string typeName){ List<MethodInfo> methods=new List<MethodInfo>(); foreach(MethodInfo method in type.GetMethods()){ if(HasCustomAttribute(method,typeName)){ methods.Add(method); } } return methods; } /// <summary> /// Runs the tests given in the specified assembly and returns a list of test results. /// Will also write the results to console. /// /// <param name="assembly">Assembly to search for tests. If null, uses the calling assembly. /// <returns>A list of TestResults. public static List<TestResult> RunTests(Assembly assembly){ List<TestResult> results=new List<TestResult>(); if(assembly==null) assembly=Assembly.GetCallingAssembly(); foreach(Type type in assembly.GetExportedTypes()){ if(HasCustomAttribute(type,"NUnit.Framework.TestFixtureAttribute")){ List<MethodInfo> testMethods=FindMethodsWithAttribute(type,"NUnit.Framework.TestAttribute"); IgnoreAttribute ignoreAttributeOnFixture= FindCustomAttribute(type,"NUnit.Framework.IgnoreAttribute") as IgnoreAttribute; bool ignoreMethods=false; bool failMethods=false; string ignoreMessage=""; string failStackTrace=""; object fixture=null; List<MethodInfo> fixtureSetupMethods=FindMethodsWithAttribute(type,"NUnit.Framework.TestFixtureSetUpAttribute"); List<MethodInfo> fixtureTearDownMethods=FindMethodsWithAttribute(type,"NUnit.Framework.TestFixtureTearDownAttribute"); MethodInfo setupMethod=null; MethodInfo tearDownMethod=null; if(ignoreAttributeOnFixture!=null){ ignoreMethods=true; ignoreMessage=ignoreAttributeOnFixture.Reason; } else { List<MethodInfo> setupMethods=FindMethodsWithAttribute(type,"NUnit.Framework.SetUpAttribute"); List<MethodInfo> tearDownMethods=FindMethodsWithAttribute(type,"NUnit.Framework.TearDownAttribute"); setupMethod=(setupMethods.Count==1) ? setupMethods[0] : null; tearDownMethod=(tearDownMethods.Count==1) ? tearDownMethods[0] : null; ConstructorInfo constructor=type.GetConstructor(new Type[0]); if(constructor==null){ ignoreMethods=true; ignoreMessage="Test has no parameterless public constructor."; } else if(setupMethods.Count>1){ ignoreMethods=true; ignoreMessage="More than one SetUp method exists."; } else if(tearDownMethods.Count>1){ ignoreMethods=true; ignoreMessage="More than one TearDown method exists."; } else if(fixtureSetupMethods.Count>1){ ignoreMethods=true; ignoreMessage="More than one TestFixtureSetUp method exists."; } else if(fixtureTearDownMethods.Count>1){ ignoreMethods=true; ignoreMessage="More than one TestFixtureTearDown method exists."; } else { fixture=constructor.Invoke(new object[0]); try { if(fixtureSetupMethods.Count==1) fixtureSetupMethods[0].Invoke(fixture,BindingFlags.Public, null,new object[0],null); } catch(Exception e){ if(e is TargetInvocationException) e=e.InnerException; if(e is IgnoreException){ ignoreMethods=true; ignoreMessage=e.Message; } else { string stackTrace=e.StackTrace; if(e is AssertionException && stackTrace.IndexOf('\')>=0){ stackTrace=stackTrace.Substring(stackTrace.IndexOf('\')+1); } failMethods=true; failStackTrace=stackTrace; ignoreMessage=String.Format("Set up failed: {0}",e.Message); } } } } if(ignoreMethods){ foreach(MethodInfo method in testMethods){ Console.WriteLine("IGNORED: Test {0}: {1}",method.Name,ignoreMessage); results.Add(new TestResult( method.DeclaringType.FullName + "." + method.Name, TestResultType.Ignored,ignoreMessage,"")); } } else if(failMethods){ foreach(MethodInfo method in testMethods){ Console.WriteLine("FAILED: Test {0}: {1}",method.Name,ignoreMessage); results.Add(new TestResult( method.DeclaringType.FullName + "." + method.Name, TestResultType.Failed,ignoreMessage,failStackTrace)); } } else { foreach(MethodInfo method in testMethods){ try { ExpectedExceptionAttribute expectedException= FindCustomAttribute(method,"NUnit.Framework.ExpectedExceptionAttribute") as ExpectedExceptionAttribute; IgnoreAttribute ignoreAttribute= FindCustomAttribute(method,"NUnit.Framework.IgnoreAttribute") as IgnoreAttribute; if(ignoreAttribute!=null){ Assert.Ignore(ignoreAttribute.Reason); continue; } //Setup method if(setupMethod!=null) setupMethod.Invoke(fixture,BindingFlags.Public,null,new object[0],null); //TearDown method if(expectedException!=null){ Type exceptionType=expectedException.ExceptionType; try { method.Invoke(fixture,BindingFlags.Public,null,new object[0],null); } catch(Exception e){ if(e is TargetInvocationException) e=e.InnerException; Assert.AreEqual(exceptionType,e.GetType(),"Wrong exception type was thrown."); } } else { method.Invoke(fixture,BindingFlags.Public,null,new object[0],null); } if(tearDownMethod!=null) tearDownMethod.Invoke(fixture,BindingFlags.Public,null,new object[0],null); Console.WriteLine("PASSED: Test {0}",method.Name); results.Add(new TestResult( method.DeclaringType.FullName + "." + method.Name, TestResultType.Passed,"","")); } catch(Exception e){ if(e is TargetInvocationException) e=e.InnerException; if(e is IgnoreException){ Console.WriteLine("IGNORED: Test {0}: {1}",method.Name,((IgnoreException)e).Message); results.Add(new TestResult( method.DeclaringType.FullName + "." + method.Name, TestResultType.Ignored,e.Message,"")); } else { Console.WriteLine("FAILED: Test {0}",method.Name); string stackTrace=e.StackTrace; if(e is AssertionException && stackTrace.IndexOf('\')>=0){ stackTrace=stackTrace.Substring(stackTrace.IndexOf('\')+1); } results.Add(new TestResult( method.DeclaringType.FullName + "." + method.Name, TestResultType.Failed,e.Message,stackTrace)); Console.WriteLine(e.GetType().FullName); Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); } } } try { if(fixtureTearDownMethods.Count==1) fixtureTearDownMethods[0].Invoke(fixture,BindingFlags.Public, null,new object[0],null); } catch(Exception e){ Console.WriteLine("Tear down failed: Test {0}",fixtureTearDownMethods[0].Name); Console.WriteLine(e.GetType().FullName); Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); } } } } return results; } } }
