Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit Testing of a Xml Parser class

I am having a class which uses the XmlReader and XmlReaderSettings classes in C# to validate Xml files against a schema. Since my application involves reading the Xml data from the database, I decide to show an error to the user in a MessageBox. Thus, any validating errors as well as any exceptions thrown would be shown with a string "Error occurred while parsing" appearing in a MessageBox.

I also have a boolean variable which returns whether the parsing was successful or not.

Right now, I am using the boolean value returned by the Parse function in an Assert while having the parse function parse both valid and invalid Xml files.

Thus, while running the suite of test cases, I have these stack of message boxes piled up in the other window.

The real question I had that was it ok to have a number of these message boxes pop up while the unit testing framework in Visual Studio tells us whether all the tests passed or not.

Or is it a case where I just need to return a bool value and then the GUI class displays the appropriate error message.

Q2. Also, say If i do need to check whether a particular string was parsed correctly and stored into an array, can I subclass the main class to add some functionality which could help me in better write Unit tests?

I would really appreciate some suggestions as to how my design and unit testing should be.

Also, I do accept that I am making a big mistake in writing the Unit tests after I have written down the class which I need to test and I know it should be the other way round.

like image 776
chaitanya Avatar asked Feb 25 '23 14:02

chaitanya


1 Answers

The parsing of the Xml and the displaying of error messages are separate concerns, so your parser should not have any knowledge about how the error messages are displayed.

Depending on your needs, there are a few options:

Exceptions

I often live by the rule: "If a method can't do it's job, throw an exception". If you need to stop at the first error, exceptions are the way to go.

From a unit test perspective, if you pass in illegal data, verify that the code throws an exception using the [ExpectedException] attribute.

[TestMethod, ExpectedException(typeof(ParserValidationException))]
public void IllegalDataShouldThrowValidationErrors()
{
    var parser = new MyParser();
    parser.Parse( dataThatContainsErrors );
}

However, if your need to ignore illegal data and report errors, you might want a different approach.

Specialized Return Type

If you need to collect all the errors, it's best to keep the parsed result and the errors together as an object.

public class ParsedResult<T>
{
    public T Result;
    public List<string> Warnings;
}

From a unit test perspective, you should verify that the list of warnings isn't empty if you pass in illegal data.

[TestMethod]
public void ParsedResultsForIllegalDataShouldContainWarnings()
{
    var parsedResult = new MyParser.Parse<Foo>( dataThatContainsErrors );

    Assert.IsNotNull(parsedResult);
    Assert.IsNotNull(parsedResult.Result);
    Assert.AreEqual(1, parsedResult.Warnings.Count);
}

Error Reporter

Pass in a collaborator into the object and have it report it's findings.

public ObjectToReturn Parse(string xml, IProgressReporter progress)
{
     // create xml reader
     // read values from xml
     // if a value is invalid, log it
     progress.AddMessage( "property x was invalid. ")
}

The progress reporter can be a wrapper around your MessageBox, or it could be a Console output, Logger, etc. From a unit test perspective, you can either create a Test Double that captures the messages, or you can use a mock framework and verify that it was called a certain amount of times. Here's an example that uses Moq.

var mockReporter = new Mock<IProgressReporter>();
IProgressReporter reporter = mockReporter.Object;

var parser = new MyParser();
var illegalData = // your illegal data;

var result = parser.Parse( illegalData, parser);

Assert.IsNotNull(result, "The value was not parsed correctly.");
mockReporter.Verify( r => r.AddMessage( It.IsAny<string>() ), Times.AtLeast(1));
like image 70
bryanbcook Avatar answered Mar 07 '23 22:03

bryanbcook