Click here to Skip to main content
15,867,330 members
Articles / Programming Languages / C#

A Very Lightweight Platform for Testing in Console Mode

Rate me:
Please Sign up or sign in to vote.
4.33/5 (2 votes)
31 Jan 2010CPOL4 min read 20.5K   108   13   4
A library to help a developer to test core business operations in console mode before integration in larger UI project

Motivations

Sometimes, in production environment, testing core business functions needs long and precious time.

Fortunately, there are a lot of platforms that simplify and automate testing. However, this operation cannot always provide the expected results.
Sometimes, operations need simply to be checked by human intervention and evaluated by human appreciation.

Moreover, in large products, when compiling dozens of libraries, every change can be very time consuming and require a lot of effort and automatic testing will not have the expected behavior.
The OBP console testing framework is a very simple and lightweight library that is used to test your business objects, their behaviors and the result of complex operations without being in a graphical environment such as Windows Forms, or another heavy environment, such as web, Silverlight or WPF.

The main goal of the OBP Console Test library is to validate core business functions in console mode before integrating them in the related heavy project.

Structure and Needs

The structure of the test class has to permit the following behavior:

  • Discover test classes and test methods at runtime using custom attributes and reflection
  • Provide the tester a simple user interface based on console menus
  • Allow the usage of overlapping menus
  • Facilitate displaying collections of business objects and selecting individual objects from them

Definitions

  • A test method is a method that performs an action to validate a process, an action or a behavior that is part of a development project.
  • A test class is a class that contains one or more test methods. It could also contain other test classes as members to perform overlapped tests.
  • A menu is a set of actions displayed in console that allow the tester to invoke a given test method using keyboard.

Architecture

The whole library is built on one simple class: CustomConsoleTest. The class accomplishes the following actions:

  • It browses to all the methods of the descendant classes using reflections looking for methods that have the attribute “TestMethod”.
  • Using this attribute, it extracts the display name of the method to use it in the menu.
    It simplifies displaying collections and selecting an item within a collection.
  • To make the test run, you need to instantiate a CustomConsoleTest descendant (this class is abstract) and call the method “Execute”.

The magic in this simple class is that a test class can embed other test classes and the execution of one of the test methods of the parent class could call the Execute method of the child class. By consequence, you'll build a very quick hierarchical console menu dedicated to test core business functions before integration.

Implementation

All the test classes have to derive from CustomConsoleTest. All is in the constructor:

C#
public CustomConsoleTest()
{
    var t = this.GetType();
    int i = 0;
    string name = null;
    // find the methods
    foreach (var m in t.GetMethods(BindingFlags.Public | 
    	BindingFlags.NonPublic | BindingFlags.Instance))
    {
        if (IsValidMethod(m, ref name))
        {
            var item = new MethodItem(name, m);
            _methods.Add(_menuKeys[i], item);
            ++i;
        }
    }
}

When a CustomConsoleTest descendant is instantiated, it performs the following actions:

  1. Using reflection, it looks for methods that have the custom attribute « TestMethod »
  2. When a method has this attribute, it is added to a collection of a class called « MethodItem » that contains the methodInfo instance and the display (user friendly) name of the method.

The test methods have to be parameterless methods.

Once the test method is discovered, the idea behind the test class is to display menus that allow invoking test methods through keyboard keys.

To accomplish that, the tester has to call the method "Execute" that browses the method collection and displays a menu using the friendly name found in the attribute.

To put in overlapped tests and consequently overlapped menu, test classes could embed other test classes and the call of one of the test methods will perform a call to the "Execute" method of the embedded classes.

C#
public void Execute()
{
    char c = '*';
    while ((c != 'q') && (c != 'Q'))
    {
        foreach (var p in _methods)
            Console.WriteLine("
            	{0} - {1}", p.Key, p.Value.DisplayText);
        Console.WriteLine("q - Quit");
        Console.WriteLine();
        c = Console.ReadKey(true).KeyChar;
        if (_methods.ContainsKey(c))
            try
            {
                var m = _methods[c];
                m.Method.Invoke(this, null);
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("An exception has 
                	occurred when invoking the method {0}",
                    ex);
            }
    }
}

For displaying collections and selecting items, I added to generic functions that simplify handling collections. That supposes that the collection implements the generic interface IEnumerable<T>.

C#
protected void DisplayCollection<T>(IEnumerable<T> aCollection)
{
    if (aCollection.Count() == 0)
        Console.WriteLine("No item in the collection");
    int i = 1;
    _elts.Clear();
    foreach (var e in aCollection)
    {
        Console.WriteLine("{0} - {1}", i, GetDescription(e));
        _elts.Add(i, e);
        ++i;                
    }
    Console.WriteLine();
}

The method SelectItem makes selecting items in console mode easier:

C#
protected T SelectItem<T>(IEnumerable<T> aCollection) where T : class
{
    DisplayCollection<T>(aCollection);
    
    while (true)
    {
        Console.WriteLine("Type the element number or q to exit");
        var text = Console.ReadLine();
        if ((text == "q") || (text == "Q"))
            return null;
        try
        {
            int index = Convert.ToInt32(text);
            if (_elts.ContainsKey(index))
                return (T)_elts[index];
            else
                Console.WriteLine("The list does not contain that number, try again !");
        }
        catch
        {
            Console.WriteLine("Wrong value");
        }
    }
}

To display menus, I store the found test methods in a class called MethodItem:

C#
class MethodItem
{
    internal MethodItem(string aDisplayText, MethodInfo aMethod)
    {
        DisplayText = aDisplayText;
        Method = aMethod;
    }
    
    internal string DisplayText { get; private set; }
    
    internal MethodInfo Method { get; private set; }
}

Example

To make all this run in a small example, the application includes two business object types: Machine and SparePart.

C#
class Machine : Asset
{
    internal void AddPart(SparePart aPart)
    {
        Parts.Add(aPart);
        AddWeight(aPart.Weight);
    }
    
    public Machine(string aName)
        : base(aName)
    {
        Parts = new List<SparePart>();
    }
    
    public static void CreateInstances()
    {
        Machines = new List<Machine>();
        for (int i = 0; i < new Random().Next(10) + 1; i++)
            Machines.Add(new Machine(string.Format("Machine{0}", i)));
    }
    
    public static List<Machine> Machines
    {
        get;
        private set;
    }
    
    public List<SparePart> Parts { get; private set; }
}
C#
class SparePart : Asset
{
    public SparePart(Machine aMachine, string aName, double aWeight):
        base(aName)
    {
        Machine = aMachine;
        Weight = aWeight;
        aMachine.AddPart(this);            
    }
    
    public static void CreateInstances()
    {
        Parts = new List<SparePart>();
        var random = new Random();
        foreach(var m in Machine.Machines)
            for (int i = 0; i < random.Next(5); i++)
            {
                var part = new SparePart(m, string.Format
                	("{0}-part{1}", m.Name, i),
                    random.NextDouble());
                Parts.Add(part);
            }
    }
    
    public Machine Machine { get; private set; }
    
    public static List<SparePart> Parts
    { get; private set; }
}

To implement my test operations, I need three classes: MachineTest for testing machine, PartTest for testing spare parts and finally SoftwareTest that embeds the two tests.

The code is as follows:

C#
/// <summary>
/// this class is used to test software
/// </summary>
class SoftwareTest : CustomConsoleTest
{
    /// <summary>
    /// machine test
    /// </summary>
    private MachineTest _machineTest = new MachineTest();
    
    /// <summary>
    /// part test
    /// </summary>
    private PartTest _partTest = new PartTest();
    
    [TestMethod("Machines")]        
    private void ExecuteMachineTest()
    {
        _machineTest.Execute();
    }
    
    [TestMethod("Parts")]
    private void ExecutePartTest()
    {
        _partTest.Execute();
    }
    
    static SoftwareTest()
    {
        Machine.CreateInstances();
        SparePart.CreateInstances();
    }
}
C#
class MachineTest : CustomConsoleTest
{
    [TestMethod("List Machines")]
    private void ListMachines()
    {
        DisplayCollection<Machine>(Machine.Machines);
    }
    
    protected override string GetDescription(object e)
    {
        if(e is Machine)
        {
         var m = e as Machine;
         return string.Format("Machine {0} | {1:0.00} | Parts : {2}",
             m.Name, m.Weight, m.Parts.Count);
        }
        return base.GetDescription(e);
    }
}
C#
class PartTest : CustomConsoleTest
{
    protected override string GetDescription(object e)
    {
        if (e is SparePart)
        {
            var p = e as SparePart;
            return string.Format("Machine : 
            	{0} - {1}", p.Machine.Name, p.Name);
        }
        return base.GetDescription(e);
    }
    
    /// <summary>
    /// selection
    /// </summary>
    [TestMethod("Select A Part")]
    protected void TestSelect()
    {
        var p = SelectItem<SparePart>(SparePart.Parts);
        if (p != null)
            Console.WriteLine("You have selected the part {0}", p.Name);
    }
}

To make displaying objects finer, I override the method GetDescription that is natively based on the ToString method.

Conclusion

In my own experience in developing large and long software systems, automated testing is good but not enough and very often, time-consuming when testing very particular scenarios.

Our approach was to get key non-UI portions of software and test them in console projects to make things go quicker.

For accelerating our approach, we developed this library that allows us in performing tests very quickly by displaying menus and invoking required methods.

The "particular scenario" has to be written in the descending class test methods.

History

  • 31st January, 2010: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect OBP Technologies
Algeria Algeria
OBP Technologies is one of the first .NET and C# consulting companies in Algeria.

OBP delivers high-quality products and services targeting various domains such as education, systems integration and offshore IT services. OBP focuses the offered services on Micrsoft technologies and specially The .NET framework.

We offer valuable and affordable services experience to local and international industrial, educational and governmental institutions.
This is a Organisation (No members)


Comments and Discussions

 
QuestionWhy this? Pin
Mainak Saha31-Jan-10 22:47
Mainak Saha31-Jan-10 22:47 
AnswerRe: Why this? Pin
Mostefai Mohammed Amine31-Jan-10 23:22
Mostefai Mohammed Amine31-Jan-10 23:22 
GeneralRe: Why this? Pin
Mainak Saha1-Feb-10 15:09
Mainak Saha1-Feb-10 15:09 
GeneralRe: Why this? Pin
Mostefai Mohammed Amine1-Feb-10 21:43
Mostefai Mohammed Amine1-Feb-10 21:43 
Hi,

Thank you for sharing your opinion. We are all here to learn and to share.
I have to point some aspects out :
1- The development of UI goes in parallel with the development of the business layer. In another words, we can reach a situation where we need tests on both and accordingly, we cannot test one with another.
2- Because I don’t use UI first, unit tests are not sufficient because I need minimal interactivity. That’s why I added selecting item and displaying collection methods.
3- The business operations results in my case are very difficult to check. We are developing on a N-Tier architecture using WCF so the operations include communication, exception handling, database information writing and reading and file handling. Writing automated routines to check all this is very time consuming.
4- Not all developers master unit testing and this will become an additional learning curve. In this framework, all they have to do is to add an attribute and call Execute method. We have applied this and the results are estonishing, we have accelerated anomaly detection.
5- This class does not intend to be a replacment to unit testing. It is a small complementary utility than could be very useful in huge projects.
Best regards

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.