Some time back an article was published on www.gridviewguy.com , “Creating an Online Exam Using LINQ to Classes Part I” which introduced the basic design involved in creating an online exam. In this article we will take one step further and create unit tests to test certain features of the application.

Introduction:

Some time back an article was published on www.gridviewguy.com , “Creating an Online Exam Using LINQ to Classes Part I” which introduced the basic design involved in creating an online exam. In this article we will take one step further and create unit tests to test certain features of the application.

Initializing the Object in Correct State:

Before proceeding with writing the unit test we should take a moment to think about how to setup the Exam object for testing purposes. The Exam object consists of several other objects namely Questions, Choices and UserExams. In order to proceed with unit testing the Exam object must be initialized correctly in a meaningful state.

There are several ways to initialize an Exam object. This involves creating a FakeExamRepository which returns a FakeExam object. Although this approach works fine but requires a lot of code since, you will be creating the dependencies manually for your objects.

Another flavor of the same approach is to generate the Exam object using a resource. This resource can be anything but since; we are generating the exam using an XML file so our resource will be an XML file. The XML file will contain a single Exam. The code will read the XML file and initialize the Exam object. You must add an XML file to your test project and set it up as “Embedded Resource”. 

Unit Testing the Application:

The first test we are going to write will test that if we can load the Unit Test Assembly successfully. This is because later we will need to load the embedded XML file from the assembly. Here is the implementation of the test.

[Test]
        public void test_can_load_assembly_successfully()
        {
            Assembly assem = Assembly.Load("TestSuite");
        }

The strange thing about this test is that it does not involve any Assert statements. This is because we are relaying on exceptions to pass our test. If the “TestSuite” assembly is not found then an exception is thrown and it will cause the test to fail. On the other hand if the “TestSuite” assembly is found then the assembly is loaded and the test will be passed.

Let’s take a look at the second test in which we check that the XML file is attached to the assembly as a resource.

public const string examResourceName = "TestSuite.Exam1.xml";

[Test]
        public void test_can_extract_exam_xml_file_from_assembly()
        {
            Assembly assem = Assembly.Load("TestSuite");

            var obj = (from m in assem.GetManifestResourceNames()
                      where m.Equals(examResourceName)
                      select m).Single();

            Assert.IsNotNull(obj);
            Assert.AreEqual(obj, examResourceName);                     
        }


It is always a good idea to use descriptive names for the tests. The name of the test should reflect the purpose of the test.

Here is another unit test that covers that an Exam object can be constructed from an XML file and saved in the database.

[Test]
        [RollBack]
        public void test_can_create_exam_from_an_xml_file()
        {
            Assembly assem = Assembly.Load("TestSuite");

            XDocument doc = null;

            using (XmlReader reader = XmlReader.Create(assem.GetManifestResourceStream(examResourceName)))
            {
                doc = XDocument.Load(reader);
            }

            Assert.IsNotNull(doc);

            // the following code can be written in a helper class
            XElement e = doc.Element("Exam");

            var exam = new Exam() { Title = e.Attribute("title").Value };

            XElement q = e.Element("Questions");

            foreach (XElement question in q.Elements("Question"))
            {
                Question ques = new Question()
                {
                    Text = question.Attribute("text").Value,
                    Point = Convert.ToDecimal(question.Attribute("point").Value)
                };

                exam.Questions.Add(ques);

                foreach (XElement choice in question.Elements("Choices").Elements("Choice"))
                {

                    ques.Choices.Add(new Choice()
                    {
                        Text = choice.Attribute("text").Value,
                        Correct = Convert.ToBoolean(choice.Attribute("isCorrect").Value)
                    });
                }
            }

            // save the exam

            ExamRepository repository = new ExamRepository();           
            repository.AddAndPersistAll<Exam>(exam);

            Exam eVerify = repository.GetById<Exam>(exam.ExamID);
            Assert.IsTrue(eVerify.ExamID > 0);

            Assert.AreEqual(exam.Title, eVerify.Title);
            Assert.AreEqual(exam.Questions.Count, eVerify.Questions.Count);
        }

You can always move out the XML parsing part into a separate helper class.

Let’s see how we can test the Grader service which is used to grade the user’s exam.

[Test]
        [RollBack]
        public void test_can_grade_an_exam()
        {
            var exam = TestHelper.CreateExamFromXmlFile();
           
            ExamRepository repository = new ExamRepository();           
            repository.AddAndPersistAll<Exam>(exam);

            UserExam userExam = new UserExam() { Exam = exam };

            IGrader grader = new ExamGrader();           
            grader.Grade(userExam);

            Assert.AreEqual(30, userExam.Score);
            Assert.IsNotEmpty(userExam.AnswerSheet);

            Console.WriteLine(userExam.AnswerSheet);
        }


First, we created the exam using the XML file. The exam is later persisted in the database. The IGrader interface Grade method is used to grade the exam. If you like you can change the user’s selections at runtime to get different grades. In this test we assumed that user’s got all the questions correct and made a perfect score.

The ExamGrader Grade method implementation is shown below:

public void Grade(UserExam userExam)
        {
            IExamRepository<Exam> repository = new ExamRepository();
            Exam persistedExam = repository.GetById<Exam>(userExam.ExamID);

            foreach (Question question in userExam.Exam.Questions)
            {
                var q = persistedExam.Questions.Single<Question>(s => s.QuestionID == question.QuestionID);                              
                var c = q.Choices.Single<Choice>(c1 => c1.Correct);
               
                foreach (var choice in question.Choices)
                {
                    if (choice.ChoiceID == c.ChoiceID)
                    {
                        userExam.Score += (double)question.Point;
                    }
                }
            }

            GenerateAnswerSheet(userExam.Exam);
        }

The Grade method compares the user’s submitted Exam to the persisted Exam. The score is incremented when the user’s choice is equal to the actual choice. The GenerateAnswerSheet is used to create the answer sheet for the users. We will be using a string like [questionNo]:[ChoiceNumber]|[questionNo]:[ChoiceNumber] to represent the answer sheet. Here is an actual answer sheet generated.

12:23|13:34|14:45

Just remember that the answer sheet represents the student choices. Here is the implementation of the GenerateAnswerSheet method.

public void GenerateAnswerSheet(Exam exam)
        {
            StringBuilder answerSheet = new StringBuilder();

            foreach (var question in exam.Questions)
            {
                foreach (var choice in question.Choices)
                {
                    answerSheet.Append(String.Format("{0}:{1}|", question.QuestionID, choice.ChoiceID));
                }
            }

            exam.UserExams[0].AnswerSheet = answerSheet.ToString().TrimEnd('|');          
        }

Conclusion:

In this article we discussed the unit tests associated with building an online exam. Unit Tests allows us to better design our application and will serve as pillars for future maintenance. In the next article we will lean how to publish the exam and show it to the user.