Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

NCover Basics: Improving Json.NET's Test Suite with NUnit and NCoverExplorer

28 Feb 2008 1  
One of the easiest ways to master NCover is simply to dive into some code and improve tests, so in this article we'll be looking at improving the test quality for an open source project, Json.NET.

This article is in the Product Showcase section for our sponsors at CodeProject. These articles are intended to provide you with information on products and services that we consider useful and of value to developers.

NCover Basics: Improving Json.NET's Test Suite with NUnit and NCoverExplorer

One of the easiest ways to master NCover is simply to dive into some code and improve tests, so this week we'll be looking at improving the test quality for an open source project, Json.NET, which is available on CodePlex here. Json.NET is a tool for reading and writing JSON from .NET, and includes a JsonSerializer for easily serializing .NET objects into Json.

Json stands for JavaScript Object Notation, and is a way that AJAX developers communicate between the browser and the web server, and increasingly a way that web services developers communicate between various web services clients and the web server. You can learn more about Json at Introducing JSON.

We'll start off by compiling and running the tests for Json.NET through NCoverExplorer in order to visualize code coverage, and then proceed to actually improve the quality of the test suite for Json.NET by increasing the project's code coverage.

During this article we'll be utilizing Visual Studio 2005, NUnit, and of course, NCover. If you use a tool other than Visual Studio to build your projects, you should be fine, as long as you can compile the Json.NET project. If you do not already have NUnit installed, you can simply download the latest MSI installer from the Nunit home page, install it, and add the NUnit bin directory to your path. NCover can be downloaded from here, and you'll need either a paid or trial license to complete the steps outlined in this article.

Build Json.NET and Test in NUnit

Building Json.NET is fairly straightforward. Download the project's source code from the Json.NET website. We'll be working with version 1.3.1 in this article. The source is included with the 1.3.1 release, which you can download here. Once you have downloaded the code, unzip it to your hard drive and open the solution file located at Src\Newtonsoft.Json.sln in Visual Studio 2005.

Building the project is as easy as clicking on Build -> Build Solution within Visual Studio. After a moment you'll see some warnings show up in the Error List in Visual Studio, but the build should complete successfully.

Now that the solution is finished building, let's test it in NUnit to make sure that all of the tests succeed. Open up a console window and cd into the location where you unzipped Json.NET. Next, cd into the Src\Newtonsoft.Json.Tests\bin\Debug folder, which is where your build output should be. Listing the files in the directory should reveal several DLL and PDB files, as well as a couple of XML documents.

To test Json.NET with NUnit, execute the following command at the command line:

  nunit-console Newtonsoft.Json.Tests.dll

You'll see some output scroll by as the tests are being executed and should see that… wait a minute! One of the tests failed. Let's go ahead and fix that before we continue.

You'll notice that the output of the test failure should look something like this:

json_net_test_failed.png

We can tell right off that the failure most likely happened because the developer of Json.NET coded the test against his time zone, rather than against whatever time zone the test is being run from, so a couple of changes around line 339 of the XmlNodeConverterTest.cs file should fix the issue.

So, let's add a couple of lines before line 337 to get a DateTime in our own time zone:

  // Get a DateTime set to our current locale to put into our expected XML
  DateTime dt = new DateTime(1970, 1, 1);
  dt = dt.AddMilliseconds(954374400000);

And then update what was line 337 to serialize the DateTime that we created where the old serialized DateTime was:

  string expected = @"<?xml version=""1.0"" standalone=""no""?>" +
      @"<root><person id=""1""><Float>2.5</Float><Integer>99</Integer></person>" +
      @"<person id=""2""><Boolean>true</Boolean><date>" + 
            XmlConvert.ToString(dt, "yyyy-MM-ddTHH:mm:ss.fffffffzzzzzz") +
            @"</date></person></root>";

Now rebuild the project and run it through NUnit on the console again, and you should see that it completes successfully.

Collecting Coverage Data with NCoverExplorer

Installing NCover should have added a shortcut to NCoverExplorer in your Start Menu, so go ahead and open it up. Now, go ahead and click on the Run NCover button on the toolbar or select Run NCover... from the File menu. You'll be presented with the NCoverExplorer Run dialogue.

nce_run_dialog.png

The run dialogue presents you with all of the options available from the NCover.Console command line. We'll set the following options on the Application to Profile page:

  • Choose the nunit-console.exe executable in the NUnit\bin folder on your computer (it's usually installed in Program Files).
  • In the working directory field, enter the path to the Src\Newtonsoft.Json.Tests\bin\Debug folder in the Json.NET project.
  • In the application arguments field, enter the arguments for nunit-console. In our case this will be "Newtonsoft.Json.Tests.dll /noshadow." The /noshadow flag simply tells NUnit to not shadow copy the assemblies that it is testing. Shadow copying gives NUnit problems at times, so we recommend you always test with it off.

Additionally, ensure that the "Close window & load coverage file" checkbox in the bottom left corner of the Run dialog is unchecked, so that we can review the output of NCover after it completes.

Now click the Run button in the bottom right corner of the dialog, and you should see output begin to scroll in the Run dialog. The first few lines of the output show the NCover settings, and then you should notice the output of NUnit, just as it was displayed when we ran the NUnit tests before. Finally, you should see a summary of the coverage data and the return code from NUnit (which should be 0).

Reviewing Coverage and Improving the Test Suite

Now that you've collected coverage data on the application, click the "Close" button in the Run dialog to expose the main NCoverExplorer interface. You'll notice that you can see the two project namespaces, Newtonsoft.Json and Newtonsoft.Json.Tests in the far left window. You can also see percentages next to the namespaces. The percentages represent the percentage of lines of code in the namespaces that were visited during the execution of the tests.

nce_main_window.png

Expanding the Newtonsoft.Json namespace will reveal the namespaces within the namespace Expanding those namespaces will reveal classes, and expanding the classes will reveal methods. Clicking on a class or method will take us to the source file where the class or method is declared. You'll see within the source view that lines of code that were not visited during the course of the tests are highlighted in red, and lines that were visited are highlighted in green.

We're going to target a few of the easier to cover methods in the namespace, and attempt to improve their coverage to 100%. Let's start with the ParseProperty method in the Newtonsoft.Json.JsonReader class. It's got a single line of code that isn't covered — line 380. The code surrounding line 380 is presented below, with line 380 highlighted for emphasis:

  if (ValidIdentifierChar(_currentChar))
  {
    ParseUnquotedProperty();
  }
  else if (_currentChar == '"' || _currentChar == '\'')
  {
    ParseQuotedProperty(_currentChar);
  }
  else
  {
    throw new JsonReaderException("Invalid property identifier character: " + 
        _currentChar);

  }

We can tell fairly easily that the line will only be reached if a Json property doesn't return true from the ValidIdentifierChar method (which says that a property should begin with a letter, a digit, an underscore, or a dollar sign), or if it doesn't match a " or a '. So, let's write a quick test that creates just such an invalid property and attempts to parse it through a JsonReader. Our test should check that a JsonReaderException is thrown.

We'll add a test to the bottom of JsonReaderTest.cs with the following code to cover that exception being thrown:

  [Test]
  [ExpectedException(typeof(JsonReaderException))]
  public void TestInvalidPropertyName()
  {
      string input = @"{@CPU: 'Intel'}";
    
      StringReader sr = new StringReader(input);

      using (JsonReader jsonReader = new JsonReader(sr))
      {
          // just read until the reader is exhausted
          while (jsonReader.Read()) { };
      }
  }

The test basically just creates some Json with an invalid identifier (@CPU) and then reads the Json out with a JsonReader until it gets an exception. How do we know that the exception is thrown at the correct place, though?

Well, build your project and go back to NCover Explorer. Open the Run dialog again, and hit the run button to run the tests through NCover. Once the tests are finished executing and you have confirmed that they all succeeded close the run dialog and click on File -> Reload to load the new coverage data from the execution. You should see that line 380 of JsonReader.cs is now covered, so we know that our changes caused that line to be visited and that the exception was thrown, since our test passed.

A More Difficult Example

We'll go ahead and improve the coverage for one more method in JsonReader before we wrap this article up. Let's focus our attention on a method that is completely uncovered. A little digging around in NCoverExplorer reveals that the private method ParseComment (which starts on line 724) is completely uncovered. Let's focus on that method.

Let's start with a baseline test that includes a Json comment in it. That should cause some lines of ParseComment to be covered, and will let us know that we're on the right track. I added the following test, which causes some Json with a comment in it to be parsed:

  // Tests a comment
  [Test]
  public void TestParseComment()
  {
      string input = @"{CPU: 'Intel', /* Some Comment */ Manufacturer: 'Dell'}";
      StringReader sr = new StringReader(input);

      using (JsonReader jsonReader = new JsonReader(sr))
      {
          // just read until the reader is exhausted
          while (jsonReader.Read()) { };
      }
  }

Run that through NCoverExplorer, and you should see that most of the lines of ParseComment are now covered. It looks like we're on the right track. Now, let's get some of those uncovered conditions in the code covered.

The first bit of uncovered code in the method, lines 744 and 745, is the handling of asterisks in the middle of a comment. As Json.NET parses a comment, it looks for an asterisk, and then if the asterisk is followed by a forward slash it finishes handling the comment. But, if the asterisk is not followed by a forward slash, it simply adds the forward slash to the buffer and continues. So, let's add an asterisk without a trailing forward slash to the middle of our comment, and that should cover the condition. We'll just change the first line of the test we just wrote:

  string input = @"{CPU: 'Intel', /* Some * Comment */ Manufacturer: 'Dell'}";

Run that change through NCoverExplorer and you'll see that we only have one more line of code (line 757) in the method to cover and we'll be finished. I think that next line requires its own test. That line looks to be throwing an exception whenever someone has a forward slash in their code without a following asterisk, which basically means that they entered half of the start comment token.

The following test should do the trick:

  // Tests a comment with a missing opening asterisk
  [Test]
  [ExpectedException(typeof(JsonReaderException))]
  public void TestParseCommentMissingOpeningAsterisk()
  {
      string input = 
          @"{CPU: 'Intel', / Some Comment With Missing Asterisk */ Manufacturer: 'Dell'}";
      StringReader sr = new StringReader(input);

      using (JsonReader jsonReader = new JsonReader(sr))
      {
          // just read until the reader is exhausted
          while (jsonReader.Read()) { };
      }
  }

Run that through NCoverExplorer and you'll see that it does in fact cover the last uncovered line of the method.

Conclusion

In this article we saw just how easy it is to improve the quality of your NUnit tests using NCoverExplorer. We can now feel confident that the ParseComment and ParseProperty methods in the JsonReader class have been thoroughly tested. A great exercise would be getting the JsonReader class to have 100% coverage.

There's no better way to hone your skills as a programmer than going out and reading someone else's code, and even better, improving their code. Make it a habit to find some open source projects that you would like to be involved in and help them reach 100% coverage. By the time you improve a few classes in a project, you should see that you have a solid understanding of their project and that you are ready to add new features.

All code from Json.NET is licensed under the following:

Copyright (c) 2007 James Newton-King Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here