一个简单的测试框架(C#版)

端午节有空本来是为整理一下C#调用C/C++库的方法,为了测试用例顺便实现了一直想实现的一个小的测试框架跟同事们分享一下,NUnit搞得已经比较复杂了,这个非常简单理解起来容易,用起来比较方便,谁再想用其它功能再自己加吧,如果功能要求比较多就直接用NUnit好了,不要再自己造轮子了。

此篇献给伟大的屈原,是他给我们创造了这样一个假期!

代码比较简单,注释也写了不少,就不再多说了,直接上代码,时间仓促,不保证没有BUG:

/**
 * A mini test framework: TTest
 * Noock.tian@gmail.com
 * version: 0.1
 * updated: 2012-06-24
 * 
 * Usage:
 *	1) Write a static test case method to do test, which must need no arguments;
 *	2) Tag the test method with TestCaseAttribute attribute;
 *	3) Call Test.Run( ) functions at anytime;
 *	
 *	If a log file is needed, we have at least two methods to get that:
 *	1) Run the test in a console and redirect the output to a file, like this:
 *		.\myapp.exe > test.log
 *	2) Set the TTest.Out, like this
 *			using (FileStream fs = new FileStream(string.Format("Test_{0}.log", DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")),FileMode.CreateNew)) {
				using (StreamWriter sw = new StreamWriter(fs)) {
					Test.Out = sw;
					Test.Run();
					Test.Out = Console.Out;
				}
			}
 *  3) You must know, create it!
 */
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
namespace Noock.TTest
{
	/// <summary>
	/// An attribute which indicate that the method is a test case, it will be run by the test engine.
	/// </summary>
	/// <remarks>
	/// A test case method should:
	/// 1) requires no arguments;
	/// 2) static;
	/// 3) private (recommend)
	/// 4) returns void (recommend);
	/// </remarks>
	public class TestCaseAttribute : System.Attribute
	{
		public string Target { get; set; }
	}

	/// <summary>
	/// Indicates a failed test case
	/// </summary>
	public class TestFailedException : Exception
	{
		public TestFailedException(string msg) : base(msg) { }
	}

	/// <summary>
	/// Test engine
	/// </summary>
	/// <remarks>
	/// <para>Call Equals functions for condition check for every test case method</para>
	/// <para>call a Run( ) function to start test</para>
	/// </remarks>
	public static class TTest
	{

		#region Condition checkers
		/// <summary>
		/// Compare two int values
		/// </summary>
		/// <param name="expect">expected value</param>
		/// <param name="actual">actual value</param>
		/// <param name="msg">a message to display if failed</param>
		public static void AreEqual(int expect, int actual, string msg = null)
		{
			if (expect != actual)
				throw new TestFailedException(msg ?? string.Format("Values not equals: expect {0}, got {1}", expect, actual));
		}

		/// <summary>
		/// Compare two double values
		/// </summary>
		/// <param name="expect">expected value</param>
		/// <param name="actual">actual value</param>
		/// <param name="msg">a message to display if failed</param>
		public static void AreEqual(double expect, double actual, string msg = null)
		{
			if (Math.Abs(expect - actual) > double.Epsilon)
				throw new TestFailedException(msg ?? string.Format("Values not equals: expect {0}, got {1}", expect, actual));
		}

		/// <summary>
		/// Compare two strings
		/// </summary>
		/// <param name="expect">expected value</param>
		/// <param name="actual">actual value</param>
		/// <param name="msg">a message to display if failed</param>
		/// <param name="ignoreCase">true: case insensitive, false: case sensitive</param>
		public static void AreEqual(string expect, string actual, bool ignoreCase = false, string msg = null)
		{
			if( !expect.Equals(actual, ignoreCase ? StringComparison.InvariantCultureIgnoreCase : StringComparison.InvariantCulture) )
				throw new TestFailedException(msg ?? string.Format("Values not equals: expect '{0}', got '{1}'", expect, actual));
		}

		/// <summary>
		/// Compare two bool values
		/// </summary>
		/// <param name="expect">expected value</param>
		/// <param name="actual">actual value</param>
		/// <param name="msg">a message to display if failed</param>
		public static void AreEqual(bool expect, bool actual, string msg = null)
		{
			if (expect != actual)
				throw new TestFailedException(msg ?? string.Format("Values not equals: expect {0}, got {1}", expect, actual));
		}

		/// <summary>
		/// Check if two arrays equal, which meet one of:
		/// 1) the same object
		/// 2) every inner one dimension array equals and values equal
		/// </summary>
		/// <param name="expect"></param>
		/// <param name="actual"></param>
		/// <param name="msg">if null an auto-generated message is used</param>
		public static void AreEqual(byte[][] expect, byte[][] actual, string msg = null)
		{
			if( object.ReferenceEquals(expect, actual) )
				return;
			int mExpect = expect.Length;
			int mActual = actual.Length;
			if (mExpect != mActual)
				throw new TestFailedException(msg ?? string.Format("First dimension no equals: expect {0}, got {1})"
					, mExpect,mActual));
			for (int i = 0; i < mExpect; ++i) {
				if(expect[i] == null &&  actual[i] == null)
					continue;
				else if(object.ReferenceEquals(expect[i],actual[i]))
					continue;
				int nExpect = expect[i].Length;
				int nActual = actual[i].Length;
				if(nExpect != nActual)
					throw new TestFailedException(msg ?? string.Format("Array dimension no equals: expect[{0}] length = {1}, actual[{0}] length = {2}"
						, i, nExpect, nActual));
				for (int j = 0; j < nExpect; ++j) {
					if( expect[i][j] != actual[i][j])
						throw new TestFailedException(msg ?? string.Format("Array values no equals: expect ({0},{1}) == {2}, got {3}"
							, i, j, expect[i][j], actual[i][j]));

				}
			}
		}

		/// <summary>
		/// Compare two 2-dimension arraies
		/// </summary>
		/// <param name="expect"></param>
		/// <param name="actual"></param>
		/// <param name="msg"></param>
		public static void AreEqual(byte[,] expect, byte[,] actual, string msg = null)
		{
			if (object.ReferenceEquals(expect, actual))
				return;
			int mExpect = expect.GetUpperBound(0)+1;
			int mActual = actual.GetUpperBound(0)+1;
			int nExpect = expect.GetUpperBound(1)+1;
			int nActual = actual.GetUpperBound(1)+1;
			if (mExpect != mActual || nExpect != nActual)
				throw new TestFailedException(msg ?? string.Format("Array dimensions no equals: expect ({0},{1}), got ({2},{3})"
					, mExpect, nExpect, mActual, nActual));
			for (int i = 0; i < mExpect; ++i) {
				for (int j = 0; j < nExpect; ++j) {
					if (expect[i,j] != actual[i,j])
						throw new TestFailedException(msg ?? string.Format("Array values no equals: expect ({0},{1}) == {2}, got {3}"
							, i, j, expect[i,j], actual[i,j]));
				}
			}
		}
		#endregion


		#region Test engine control
		/// <summary>
		/// Run tests in an assembly
		/// </summary>
		/// <param name="assembly">assemble to be test</param>
		public static void Run(Assembly assembly)
		{
			lock (_settingLock) {
				// set test options
				if (Out == Console.Out) {
					Console.ForegroundColor = ConsoleColor.White;
				}

				Type[] types = assembly.GetTypes();
				int nTotalOK = 0, nTotalFailed = 0;
				int nType = 0;
				foreach (Type t in types) {
					MethodInfo[] methods = t.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
					bool needTest = false;
					foreach (var m in methods) {
						TestCaseAttribute att = m.GetCustomAttribute(typeof(TestCaseAttribute)) as TestCaseAttribute;
						if (att != null) {
							needTest = true;
							break;
						}
					}
					if (!needTest)
						continue;

					Out.WriteLine("\r\n\r\nTesting {0} ...", t.FullName);
					int nOk = 0, nFailed = 0;
					foreach (var m in methods) {
						TestCaseAttribute att = m.GetCustomAttribute(typeof(TestCaseAttribute)) as TestCaseAttribute;
						if (att == null)
							continue;
						Out.Write("-- [{0}] : {1}\r\n", m.Name, att.Target);
						try {
							m.Invoke(null, null);
							if (Out == Console.Out) {
								Console.ForegroundColor = ConsoleColor.Green;
							}
							Out.WriteLine("[OK]");
							if (Out == Console.Out) {
								Console.ForegroundColor = ConsoleColor.White;
							}
							++nOk;
						} catch (TestFailedException ex) {
							if (Out == Console.Out) {
								Console.ForegroundColor = ConsoleColor.Red;
							}
							Out.Write("[FAILED]");
							if (Out == Console.Out) {
								Console.ForegroundColor = ConsoleColor.White;
							}
							Out.WriteLine(ex.Message);
							++nFailed;
						} catch (Exception ex) {
							if (Out == Console.Out) {
								Console.ForegroundColor = ConsoleColor.Red;
							}
							Exception baseExp = ex.GetBaseException();
							if (baseExp is TestFailedException)
								Out.Write("[FAILED]");
							else
								Out.Write("[ERROR]");
							
							if (Out == Console.Out) {
								Console.ForegroundColor = ConsoleColor.White;
							}
							if (baseExp is TestFailedException)
								Out.WriteLine(baseExp.Message);
							else
								Out.WriteLine(ex.Message);
							++nFailed;
						}
					}
					Out.WriteLine("------------------------------------------------");
					Out.WriteLine("Test finished: {0} OK, {1} Failed", nOk, nFailed);
					nTotalOK += nOk;
					nTotalFailed += nFailed;
					++nType;
				}
				Out.WriteLine("================================================");
				Out.WriteLine("Test finished: {0} OK, {1} Failed in {2} types", nTotalOK, nTotalFailed, nType);
			}
		}

		/// <summary>
		/// Run all test in current AppDomain
		/// </summary>
		public static void Run()
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (var a in assemblies) {
				Run(a);
			}
		}

		#endregion

		#region Test settings

		static TTest()
		{
			Out = Console.Out;
		}

		private static TextWriter _out = null;
		private static object _settingLock = new object();

		/// <summary>
		/// Out stream writer, default to <see cref="Console.Out"/>/>
		/// </summary>
		/// <remarks>
		/// Current thread will be blocked if test has been started.
		/// </remarks>
		public static TextWriter Out
		{
			get { return _out; }
			set
			{
				lock (_settingLock) {
					if (value == null)
						throw new ArgumentNullException();
					_out = value;
				}
			}
		}

		#endregion
	}

}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值