// (c) Pedro J. Molina, 2005, Spain. http://pjmolina.com
// Feel free to use it and improve it.
using System;
using System.Data;
using System.Collections;
using System.Reflection;
using OKOKO.Armadillo.Attributes;
using NUnit.Framework;
namespace OKOKO.Armadillo.Framework
{
/// <summary>
/// Armadillo TestEngine
/// </summary>
[TestFixture()]
public class TestEngine
{
#region Private Members
private Assembly Asm;
private IDbInfo DbInfo;
private IDbConnection Cnx;
private int countClass, countMethods, countOK, countFail;
private int countClassMethods, classOK, classFail;
#endregion
#region Constructor
/// <summary>
/// Default Constructor
/// </summary>
/// <param name="asm">Assembly</param>
/// <param name="dbInfo">DB connection info.</param>
public TestEngine(Assembly asm, IDbInfo dbInfo)
{
DbInfo = dbInfo; //DB information for connection & execution
Cnx = dbInfo.Cnx; //Connection
Asm = asm; //Assembly to be tested
TestSql();
}
#endregion
#region Test Driver
/// <summary>
/// TestDriver. Drives all the tests performed by Armadillo using the power of Reflection.
/// </summary>
[Test()]
public void TestSql()
{
countClass = countMethods = countOK = countFail = 0;
//Check code using reflection
foreach(Type exType in Asm.GetExportedTypes())
{
//Check each class of the assembly
if ((exType.IsClass) && (IsADalClass(exType)))
{
countClass++;
countClassMethods = classOK = classFail = 0;
Console.WriteLine("Testing Class: [{0}]", exType.FullName);
foreach(MethodInfo mi in exType.GetMethods(BindingFlags.Public|BindingFlags.Static))
{
//Check each method
if (IsLabelled(mi, typeof(SqlTextQueryAttribute)))
{
countClassMethods++;
Console.Write(" Testing SqlTextQuery Method: ");
CheckSqlTextQuery(mi);
}
if (IsLabelled(mi, typeof(SqlTextExecuteAttribute)))
{
countClassMethods++;
Console.Write(" Testing SqlTextExecute Method: ");
CheckSqlTextExecute(mi);
}
if (IsLabelled(mi, typeof(SqlQueryAttribute)))
{
countClassMethods++;
Console.Write(" Testing SqlQuery Method: ");
CheckSqlQuery(mi);
}
if (IsLabelled(mi, typeof(SqlExecuteAttribute)))
{
countClassMethods++;
Console.Write(" Testing SqlExecute Method: ");
CheckSqlExecute(mi);
}
}
Console.WriteLine("Class [{0}] tested. Results: OK: {1}/{2} Failed:{3}", exType.FullName,
classOK, countClassMethods, classFail);
countMethods += countClassMethods;
countFail += classFail;
countOK += classOK;
}
}
Console.WriteLine("-----------------------------------------------------------------");
Console.WriteLine("Final results: {0} classes & {1} methods tested.", countClass,countMethods);
Console.WriteLine(" Results: OK: {0}/{1} Failed: {2}", countOK, countMethods, countFail);
if ( countFail>0 )
Assert.Fail("{0} methods failed.", countFail); //Fail if needed
}
#endregion
#region Test Methods
/// <summary>
/// Gest an sql query string from the method and let Sql server execute it.
/// </summary>
/// <param name="mi">Method to check</param>
public void CheckSqlTextQuery(MethodInfo mi)
{
try
{
object[] pars = CreateDefaultParams(mi);
string sql = mi.Invoke(null, pars) as string;
IDbCommand cmd = DbInfo.CommandFactory(sql); //Create the command
int res = cmd.ExecuteNonQuery(); //Launch the SQL
LogOK(mi); //Log OK
}
catch (Exception ex)
{
LogException(mi, ex); //Log fail
}
}
/// <summary>
/// Gest an sql string from the method and let Sql server parse it.
/// </summary>
/// <param name="mi">Method to check</param>
public void CheckSqlTextExecute(MethodInfo mi)
{
try
{
DbInfo.DisableSqlExecution(); //Disable SQL Execution
object[] pars = CreateDefaultParams(mi);
string sql = mi.Invoke(null, pars) as string;
IDbCommand cmd = DbInfo.CommandFactory(sql); //Create the command
int res = cmd.ExecuteNonQuery(); //Launch the SQL
LogOK(mi); //Log OK
}
catch (Exception ex)
{
LogException(mi, ex); //Log fail
}
finally
{
DbInfo.EnableSqlExecution(); //Enable SQL Execution
}
}
/// <summary>
/// Launch a query method
/// </summary>
/// <param name="mi">Method to be checked</param>
public void CheckSqlQuery(MethodInfo mi)
{
try
{
object[] pars = CreateDefaultParams(mi);
mi.Invoke(null, pars); //Execute method
LogOK(mi);
}
catch (Exception ex)
{
LogException(mi, ex);
}
}
/// <summary>
/// Executes an method that launches an SQL statement
/// </summary>
/// <param name="mi">Method to be tested</param>
public void CheckSqlExecute(MethodInfo mi)
{
try
{
DbInfo.DisableSqlExecution(); //Disable SQL Execution
object[] pars = CreateDefaultParams(mi);
mi.Invoke(null, pars); //Execute method
LogOK(mi);
}
catch (Exception ex)
{
LogException(mi, ex);
}
finally
{
DbInfo.EnableSqlExecution(); //Enable SQL Execution
}
}
#endregion
#region Auxiliary Reflection Methods
/// <summary>
/// Checks if the current class is part of the DAL or not
/// (has to be tested or not)
/// </summary>
/// <param name="cl">Current class</param>
/// <returns>True if it is marked as a DalLClass</returns>
private bool IsADalClass(Type cl)
{
object[] attrs = cl.GetCustomAttributes(typeof(DalClassAttribute), true);
return (attrs.Length>0);
}
/// <summary>
/// Checks if a method is labelled with an specific attribute.
/// </summary>
/// <param name="mi">Method to inspect</param>
/// <param name="attributeType">Attribute to look for</param>
/// <returns>True if its labelled</returns>
private bool IsLabelled(MethodInfo mi, Type attributeType)
{
object[] attrs = mi.GetCustomAttributes(attributeType, true);
return (attrs.Length>0);
}
/// <summary>
/// Extracts ParamValue attributes for a method.
/// </summary>
/// <param name="mi">Method</param>
/// <returns>A hashtable of paramvalues indexed by param name</returns>
private Hashtable RetrieveParamsValues(MethodInfo mi)
{
Hashtable result = new Hashtable();
foreach(ParamValueAttribute parval in mi.GetCustomAttributes(typeof(ParamValueAttribute), true))
{
result.Add(parval.ParamName, parval);
}
return result;
}
/// <summary>
/// Creates a default value for each parameter
/// </summary>
/// <param name="mi">Method to be invoked</param>
/// <returns>An array with the values created</returns>
private object[] CreateDefaultParams(MethodInfo mi)
{
Hashtable paramsValues = RetrieveParamsValues(mi);
ArrayList parameters = new ArrayList();
foreach(ParameterInfo par in mi.GetParameters())
{
//1. Checks for param-value provided
if (paramsValues.ContainsKey(par.Name))
{
//use it
parameters.Add(((ParamValueAttribute)paramsValues[par.Name]).Value);
}
else
{
//2. if not provided,
//Let's invent values for common simple types
switch (par.ParameterType.FullName)
{
case "System.String":
parameters.Add("Hi!");
break;
case "System.Int16":
parameters.Add(Int16.Parse("0"));
break;
case "System.Int32":
parameters.Add(Int32.Parse("0"));
break;
case "System.Int64":
parameters.Add(Int64.Parse("0"));
break;
case "System.Decimal":
parameters.Add(Decimal.Parse("0"));
break;
case "System.Float":
parameters.Add(System.Single.Parse("0"));
break;
case "System.Double":
parameters.Add(Double.Parse("0"));
break;
case "System.Boolean":
parameters.Add(false);
break;
case "System.DateTime":
parameters.Add(new DateTime(2000,1,2,3,4,5,6));
break;
default:
parameters.Add(null);
break;
}
}
}
return parameters.ToArray();
}
#endregion
#region Log exceptions / OKs
private void LogException(MethodInfo mi, Exception ex)
{
classFail++;
Console.WriteLine(" -FAIL {0}()", mi.Name);
if ((ex.InnerException != null) && (ex.InnerException.GetType()==DbInfo.TypeException))
{
Console.WriteLine(" >> {0}", ex.InnerException.Message); //The especific Exception we where looking for
}
else
{
if (ex.GetType()==DbInfo.TypeException)
{
Console.WriteLine(" >> {0}", ex.Message); //The especific Exception we where looking for
}
else
{
Console.WriteLine(" >> {0}", ex.ToString()); //Other Exception
}
}
}
private void LogOK(MethodInfo mi)
{
classOK++;
Console.WriteLine(" +OK {0}()", mi.Name);
}
#endregion
}
}