Exercise index of this lecture   Alphabetic index   Course home   

Exercises and solutions
Test of Object-oriented Programs


14.1   Cyclomatic complexity of GCD  

Find the cyclomatic complexity of Euclid's, gcd, function in this C program.

If possible, find test cases that allow you to test each independent path of the gcd function.

Independent paths are defined here in course material.

How many test cases do you actually need to cover all source lines of the gcd function?

Solution

The cyclomatic complexity of the gcd function is four.

In general, the cyclomatic complexity is an upper limit of the number of necessary test cases. In the gcd function, it is possible to cover all source lines with two test cases.

Notice in gcd that the control of the branches of the two if control structures are coupled. This is actually poor programming! This observation may imply that it is impossible to find input data that exercises all independent paths.


14.2   Install Nunit  

Download and install the latest stable version Nunit from www.nunit.org on your computer. If you have not already done so, consult the basic information about NUnit on the accompanying slide.

More specifically, for Windows users, goto the NUnit download page and download the most recent stable version of NUNIT for .NET 2.0. As of February 2010 this is the NUnit-2.5.3.9345.msi file (a Windows installer file). You can also get this file directly from here.

Mono users on Linux probably already have an installation of NUnit. According to the NUnit documentation, Mono 1.0 through Mono 1.9 include NUnit 2.2. Try calling the NUnit console runner named nunit-console from your shell. Please be aware of the version you are running. The newest stable version of NUnit is 2.5.3 (as of February 2010). Mono users are recommended to use version 2.4.8, however. See also the release notes.

Next, consult the NUnit documentation. Pay attention to the menu to the right. In particular the CORE FEATURES documentation of Assertions and Attributes.


14.3   Give Nunit a Try  

This exercise is guided tour in using NUnit (version 2.5.3) together with Visual C# 2008 Express on Windows. The exercise will help set up the BankAccount test, which we have seen on an earlier slide page in this material. If you use C# via another IDE, or on a non-Windows platform, you should not follow this guide.

We will assume that you already have installed Nunit and that you use Visual C# 2008 Express.

Start Visual C# 2008 Express. Use File > New Project... and make a Class Library project.

In the Solution Explorer (usually at the far right of you screen) right click References, and do Add Reference.... Select and add nunit.framework (version 2.5.3) from the list. (The list may be long, so be careful to select the right entry).

Use Project > Add Class... to add the BankAccount class. (Do copy and paste from the BankAccount class of this material).

Paste the BankAccountTest class instead of Class1, which was made automatically by Visual C# 2008 Express.

Now build your library application: Use F6 as usual. "Build succeeded" is expected.

Do File > Save All. Notice the Location of you project. On a piece of paper (or in a text editor) notice the full file path to your Visual C# 2008 project. You can also get information about your project locations via the C# 2008 Express menu entry Tools > Options..., 'Projects and Solutions'.

Start NUnit 2.5.3 - for instance via the Icon on your desktop or via the Windows start menu.

In NUnit use Tools -> Settings... and check the 'Enable Visual Studio Support' in the window which appears. You find the check box in IDE Support > Visual Studio. (This is most likely the default setting in NUNIT 2.5.3).

In NUnit use File > Open Project, and select the dll named after your project. The file is most likely located in the Bin/Debug branch of your project directory (as you should have noticed above).

Activate the Run button in NUnit. You should see 'green light'.

Introduce an run-time error in BankAccount class. Is the error revealed in unit test?

If you managed to get to here you should be able to use NUnit on your own stuff. Congratulations.


14.4   Test of class Set  

In a previous exercise we have implemented the operations intersection, union, and set difference in class Set<T>.

In continuation of class SetTest, perform unit tests of the intersection, union, and set difference operations.

The natural starting point is your solution to the previous exercise. You can also chose to test my solution.


14.5   Unit test of struct Interval  

You are given a version of struct Interval, which we have worked with in an earlier exercise. You are also given a partial test suite of struct Interval. In the given Interval test suite all tests should be successful. (You may have to adjust the full qualified names of exceptions in the ExpectedException attributes, however). Please check for yourself that all tests pass successfully.

Add a test of the indexer and a test of the OverlapWith method. Both are members of type Interval, as given above.

In the version of struct Interval from above there is at least one error in either the indexer or the OverlapWith method. You should be able to reveal the error from your tests!

Please correct the error when you have found it. Carry out a regression test.

There are several overloaded operators in struct Interval. I have only made a test of the + operator. If time allows, add some additional tests of the non-tested Interval operators.

Finally, discuss the expected coverage of my Interval test cases together with your additional Interval test cases.

Solution

There is an error in the OverlapWith method of struct Interval. The two cases other inside this and this inside other (see the trailing comment lines) have been switched.

The following version of Interval-test.cs reveals the error:

using System;
using NUnit.Framework;

[TestFixture]  
public class IntervalTestExtra{

  Interval iv_1, iv_2, iv_3, iv_4;

  [SetUp]   
  public void Init(){
     iv_1 = new Interval(3,8);
     iv_2 = new Interval(8,3);
     iv_3 = new Interval(3,3);
     iv_4 = Interval.MakeEmptyInterval(); 
  }

  // All tests proposed initially should be here.

  [Test]  
  public void IndexerTest1(){
    Assert.AreEqual(iv_1[0], 3, "Indexer of iv_1 - at index 0");
  }

  [Test]  
  public void IndexerTest2(){
    Assert.AreEqual(iv_1[iv_1.Length-1], 8, "Indexer of iv_1 - at index Length-1");
  }

  [Test]  
  public void IndexerTest3(){
    Assert.AreEqual(iv_2[0], 8, "Indexer of iv_2 - at index 0");
  }

  [Test]  
  public void IndexerTest4(){
    Assert.AreEqual(iv_2[iv_2.Length-1], 3, "Indexer of iv_2 - at index Length-1");
  }

  [Test]  
  public void IndexerTest5(){
    Assert.AreEqual(iv_3[0], 3, "Indexer of iv_3 - at index 0");
  }

  [Test]  
  public void IndexerTest6(){
    Assert.AreEqual(iv_3[iv_3.Length-1], 3, "Indexer of iv_3 - at index Length-1");
  }

  [Test, ExpectedException("System.IndexOutOfRangeException")]  
  public void IndexerTestEmpty(){
    int res = iv_4[0];
  }

  [Test, ExpectedException("System.IndexOutOfRangeException")]  
  public void IndexerRangeProblem_1(){
    int res = iv_1[6];
  }

  [Test, ExpectedException("System.IndexOutOfRangeException")]  
  public void IndexerRangeProblem_2(){
    int res = iv_1[-1];
  }

  // I have NOT included tests of <<, >>, *, -, !, and the conversion to an array.
  // Do it yourself!

  [Test]  
  public void OverlapTest1(){
    Interval iv_1 = new Interval(3,8),
             iv_2 = new Interval(3,8);

    Assert.AreEqual(iv_1.OverlapWith(iv_2).From, 3, "part 1", "Overlap: Indentical");
    Assert.AreEqual(iv_1.OverlapWith(iv_2).To, 8, "part 2", "Overlap: Indentical");
  }

  [Test]  
  public void OverlapTestIdentical(){
    Interval iv_1 = Interval.MakeEmptyInterval(), 
             iv_2 = Interval.MakeEmptyInterval();

    Assert.IsTrue(iv_1.OverlapWith(iv_2).Empty);
  }

  [Test]  
  public void OverlapTest_Empty_NonEmpty(){
    Interval iv_1 = Interval.MakeEmptyInterval(), 
             iv_2 = new Interval(3,8);

    Assert.IsTrue(iv_1.OverlapWith(iv_2).Empty);
  }

  [Test]  
  public void OverlapTest_NonEmpty_Empty(){
    Interval iv_1 = new Interval(3,8),
             iv_2 = Interval.MakeEmptyInterval();

    Assert.IsTrue(iv_1.OverlapWith(iv_2).Empty);
  }


  [Test]  
  public void OverlapTest_Disjoint_Left(){
    Interval iv_1 = new Interval(3,8),
             iv_2 = new Interval(9,13);

    Assert.IsTrue(iv_1.OverlapWith(iv_2).Empty);
  }

  [Test]  
  public void OverlapTest_Disjoint_Right(){
    Interval iv_1 = new Interval(3,8),
             iv_2 = new Interval(-3,0);

    Assert.IsTrue(iv_1.OverlapWith(iv_2).Empty);
  }

  [Test]  
  public void OverlapTest_Other_ContainedIn_This(){
    Interval iv_1 = new Interval(3,8),
             iv_2 = new Interval(4,7);

    Assert.AreEqual(iv_1.OverlapWith(iv_2).From, 4, "To");
    Assert.AreEqual(iv_1.OverlapWith(iv_2).To, 7, "From" );
  }

  [Test]  
  public void OverlapTest_This_ContainedIn_Other(){
    Interval iv_1 = new Interval(4,7),
             iv_2 = new Interval(3,8);

    Assert.AreEqual(iv_1.OverlapWith(iv_2).From, 4, "To");
    Assert.AreEqual(iv_1.OverlapWith(iv_2).To, 7, "From");
  }

  [Test]  
  public void OverlapTest_OverlapsAtLeft(){
    Interval iv_1 = new Interval(4,7),
             iv_2 = new Interval(2,5);

    Assert.AreEqual(iv_1.OverlapWith(iv_2).From, 4);
    Assert.AreEqual(iv_1.OverlapWith(iv_2).To, 5);
  }

  [Test]  
  public void OverlapTest_OverlapsAtRight(){
    Interval iv_1 = new Interval(4,7),
             iv_2 = new Interval(6,10);

    Assert.AreEqual(iv_1.OverlapWith(iv_2).From, 6);
    Assert.AreEqual(iv_1.OverlapWith(iv_2).To, 7);
  }

}

In the systematic tests shown above the test methods OverlapTest_This_ContainedIn_Other and OverlapTest_Other_ContainedIn_This both fail.

The above version of Interval-test.cs also shows the test of the indexer.

Here is the correct version of the method OverlapWith:

public Interval OverlapWith (Interval other){
    // Positively oriented intervals:
    Interval thisPI = (this.Orientation < 0) ? !this : this,
             otherPI = (other.Orientation < 0) ? !other : other,
             res;

    if (thisPI.Empty || otherPI.Empty)                                  // both empty
         res = MakeEmptyInterval();
    else if (thisPI.From > otherPI.To || thisPI.To < otherPI.From)      // disjoint
         res = MakeEmptyInterval();
    else if (thisPI.From < otherPI.From && otherPI.To < thisPI.To)      // other inside this
         res = otherPI;
    else if (otherPI.From <= thisPI.From && thisPI.To <= otherPI.To)    // this inside other
         res = thisPI;
    else if (thisPI.From <= otherPI.From && otherPI.From <= thisPI.To)  // this overlaps left border of other
         res = new Interval(otherPI.From, thisPI.To);
    else if (otherPI.From <= thisPI.From && thisPI.From <= otherPI.To)  // other overlaps left border of this
         res = new Interval(thisPI.From, otherPI.To);
    else throw new Exception("Should not happen");

    return (this.Orientation < 0) ? !res : res;
  }

With this version of OverlapWith all test are executed successfully.


Generated: Monday February 7, 2011, 12:23:19