Click here to Skip to main content
Click here to Skip to main content

A Very Lightweight Platform for Testing in Console Mode

, 31 Jan 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
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:

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.

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>.

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:

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:

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.

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; }
}
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:

/// <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();
    }
}
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);
    }
}
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)

Share

About the Author

Mostefai Mohammed Amine
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.
Group type: Organisation (No members)



Comments and Discussions

 
QuestionWhy this? PinmemberMainak Saha31-Jan-10 23:47 
AnswerRe: Why this? PingroupMostefai Mohammed Amine1-Feb-10 0:22 
Hi,
 
Unit tests are used to automatic testing. This library is used for human-driven testing.
 
Some business operations have some effects that can be hardly checked using unit testing : multiple database writing, client server communication, file manipulation,...etc.
 
These checks needs simply human intervention. Also, an error in writing test statements will consequently lead to errors in validation.
 
In the projects that I am implicated in, we use ORM and heavy user interface. The development consists in business operations and integration with ui.
 
To reach an operation in UI mode, there is two problems : the development of the UI has to reach a certain status to permit accomplishing that, and the second, is compiling the whole project to consider these new operations.
 
Using this library was very useful to validate some very complex business operations before integrating them (the business layer).
GeneralRe: Why this? PinmemberMainak Saha1-Feb-10 16:09 
GeneralRe: Why this? PingroupMostefai Mohammed Amine1-Feb-10 22:43 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141223.1 | Last Updated 31 Jan 2010
Article Copyright 2010 by Mostefai Mohammed Amine
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid