Pages

Saturday, December 8, 2012

Composite pattern for testing applications

It is very common task to write some sort of testing application. It can be both an educational task and a commectial product. And the use of the composite pattern can dramatically simplify this task (of course if you are not making one-level tests). Composition pattern allows you to work with case studie as with any other kind of questions. You can read about this pattern here.
Here is my simple example of this pattern:

Classes for questions:
public abstract class TestTask
{
    public TestTask(string text)
    {
        answers = new List<TaskAnswer>();
        TaskText = text;
    }

    //abstract methods, common for every existing task
    public abstract void AddSubElement(TestTask element);
    public abstract void RemoveSubElement(TestTask element);
    public abstract bool CheckTask();
        
    //methods and properties for answers
    private List<TaskAnswer> answers;
    public string TaskText { get; set; }

    public void AddAnswer(params KeyValuePair<string, bool>[] newAnswers)
    {
        foreach (var answer in newAnswers)
        {
            answers.Add(new TaskAnswer(answer.Key, answer.Value));    
        }
    }
    public void RemoveAnswer(int number)
    {
        answers.RemoveAt(number);
    }
}

public class SingleChoiseTask: TestTask
{
    public SingleChoiseTask(string text) : base(text) {}

    //property for selected answer
    public TaskAnswer SelectedAnswer { get; set; }

    public override bool CheckTask()
    { //for single choise question just check if selected answer is correct
        return SelectedAnswer != null && SelectedAnswer.IsCorrect;
    }

    //single choise question cannot have any children
    public override void AddSubElement(TestTask element)
    {
        throw new NotImplementedException();
    }

    public override void RemoveSubElement(TestTask element)
    {
        throw new NotImplementedException();
    }
}

public class MultipleChoiseTask : TestTask
{
    public MultipleChoiseTask(string text): base(text)
    {
        SelectedAnswers = new List<TaskAnswer>();
    }

    //List for selected answers
    public List<TaskAnswer> SelectedAnswers;
    public override bool CheckTask()
    { //for multiple choise question we have to check that every selected answer is correct
        return SelectedAnswers.Count > 0 && SelectedAnswers.TrueForAll(answer => answer.IsCorrect);
    }

    // multiple choise question cannot have any children
    public override void AddSubElement(TestTask element)
    {
        throw new NotImplementedException();
    }

    public override void RemoveSubElement(TestTask element)
    {
        throw new NotImplementedException();
    }
}

public class  CaseStudieTask: TestTask
{
    public CaseStudieTask(string text): base(text)
    {
        SubTasks = new List<TestTask>();
    }

    //List of childrens
    private List<TestTask> SubTasks;

    public override void AddSubElement(TestTask element)
    {
        SubTasks.Add(element);
    }
    public override void RemoveSubElement(TestTask element)
    {
        SubTasks.Remove(element);
    }

    public override bool CheckTask()
    { // for case studie we have to check that every sub question is correct
        return SubTasks.TrueForAll(el => el.CheckTask());
    }
}
Class for answers:
public class TaskAnswer
{
    public string AnswerText { get; set; }
    public bool IsCorrect { get; set; }

    public TaskAnswer(string text, bool isCorrect)
    {
        AnswerText = text;
        IsCorrect = isCorrect;
    }
}
And example of usage:
public void InitializeTest()
{
    //Create container
    TestTask test = new CaseStudieTask("First test");
            
    //Initialise sample questions
    var firstQuestion = new SingleChoiseTask("First question");
    firstQuestion.AddAnswer(new KeyValuePair<string, bool>("correct answer", true),
                            new KeyValuePair<string, bool>("incorrect answer", false));

    var secondQuestion = new MultipleChoiseTask("Second question");
    secondQuestion.AddAnswer(new KeyValuePair<string, bool>("correct answer 1", true),
                                new KeyValuePair<string, bool>("correct answer 2", true));

    var thirdQuestion = new CaseStudieTask("Case studie");
    var firstSubQuestion = new SingleChoiseTask("First question in case studie");
    firstSubQuestion.AddAnswer(new KeyValuePair<string, bool>("correct answer", true),
                            new KeyValuePair<string, bool>("incorrect answer", false));
    var secondSubQuestion = new SingleChoiseTask("Second question in case studie");
    secondSubQuestion.AddAnswer(new KeyValuePair<string, bool>("correct answer", true),
                            new KeyValuePair<string, bool>("incorrect answer", false));
    thirdQuestion.AddSubElement(firstSubQuestion);
    thirdQuestion.AddSubElement(secondSubQuestion);

    // And populate test
    test.AddSubElement(firstQuestion);
    test.AddSubElement(secondQuestion);
    test.AddSubElement(thirdQuestion);
}
Of course this example is not full. There is no way to navigate throught test, no binding for the answers and no scores (user can pass the test only if he scores 100%).
And I would add singleton pattern for test (and this class would calculate current points, watch for current question etc.). If you are interested I can show more complex example.

No comments:

Post a Comment