Search code examples
c#visual-studio-2010unit-testing

Best practices for assertions in Unit Testing


I'm trying to test an application that takes three arguments as input (side A, side B, side C) and calculates the triangle is isoscele (two sides are equal), scalene (none of the sides are equal) or equilateral (all sides are equal).

Below is some code from the unit-testing i'm working on. In this case i'm testing that the application tells the user that the triangle is isoscele, and my question here is what I should have after Assert in this case? AreNotSame works for scalene, AreSame works for equilateral, but what works here? Thanks in advance.

public void isIsoscelesTest()
{
    Triangle target = new Triangle(5.0, 5.0, 2.0); // TODO: Initialize to an appropriate value
    bool expected = true; // TODO: Initialize to an appropriate value
    bool actual;
    actual = target.isIsosceles();
    Assert.AreNotSame(expected, actual);
}

From the application...

public bool isIsosceles()
{
    if (uniqueSides() == 2)
        return true;
    return false;
}

Solution

  • Ideally you should create one test per test-case, so in your example, I'd probably have

    [Test]
    public void isIsoscelesTest()     
    { 
        var triangle = new Triangle(5.0, 5.0, 2.0); 
        Assert.That(triangle.isIsoceles(), Is.True);
        Assert.That(triangle.isEquilateral(), Is.False);
        Assert.That(triangle.isScalene(), Is.False);
    }
    

    as well as

    [Test]
    public void isScaleneTest()     
    { 
        var triangle = new Triangle(3.9, 5.0, 2.0); 
        Assert.That(triangle.isIsoceles(), Is.False);
        Assert.That(triangle.isEquilateral(), Is.False);
        Assert.That(triangle.isScalene(), Is.True);
    }
    

    and

    [Test]
    public void isEquilateralTest()     
    { 
        var triangle = new Triangle(3.9, 3.9, 3.9); 
        Assert.That(triangle.isIsoceles(), Is.False);
        Assert.That(triangle.isEquilateral(), Is.True);
        Assert.That(triangle.isScalene(), Is.False);
    }
    

    Personally I would assert in each test that it is a specific type, but not the other two types, however you could also separate these into separate test cases (then it gets verbose though).

    In the above example we have some (nearly) duplicate code which could be factored out into a helper method if so desired. In addition, we're only testing three seperate combinations of variables (side lengths). What if you put any other values in there though? Ok fine we can't test for everything but it gives you a general idea of the shortcomings of unit tests with numerical values.

    Finally, be advised that the == operator when using double or floating precision doesn't always produce true. For instance, try this code:

    [Test]
    public void DoublePrecisionRoundingTest()
    {
       double aValue = 1.2345678;
       Assert.That(aValue + double.Epsilon, Is.EqualTo(aValue)); // passes! Should fail
    }
    

    The reason for this is that in double-precision the rounding error can cause some numerical operations to get annihilated. As a workaround (and this affects your triangle code), you should always test for "near enough" with double or float variables.

    E.g. Instead of this inside your Triangle.uniqueSides method:

    if (aDouble == otherDouble) 
    

    it is advisable (but not always necessary) to use this

    if(Math.Abs(aDouble - otherDouble) < EPSILON)
    

    where EPSILON is a really small value.

    Best regards,