dotKn0ck dotKn0ck - 17 days ago 5
C# Question

Test case design for Max() function

C# Newbie here.

I understand that test cases should be:


  • Simple and Transparent

  • Have minimal repetitions

  • Ensure 100% code coverage



I also understand the basics of Boundary Value Analysis and Equivalence Partitioning but with the function below, what would be basic test cases?

static public int Max(int a, int b, int c)
{ // Lines of code: 8, Maintainability Index: 70, Cyclomatic Complexity: 4, Class Coupling: 0
if (a > b)
if (a > c)
return a;
else
return c;
else
if (b > c)
return b;
else
return c;
}


This is what I have so far..

using Microsoft.VisualStudio.TestTools.UnitTesting;
using ConsoleApplication10;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication10.Tests
{
[TestClass()]
public class ProgramTests
{
[TestMethod()]
public void MaxTestNulls(int a, int b, int c)
{
Assert.IsNotNull(a, "The first parameter must be present.");
Assert.IsNotNull(b, "The second parameter must be present.");
Assert.IsNotNull(c, "The third parameter must be present.");
}
[TestMethod()]
public void MaxTestTypes(int a, int b, int c)
{
Assert.IsInstanceOfType(a, typeof(int));
Assert.IsInstanceOfType(b, typeof(int));
Assert.IsInstanceOfType(c, typeof(int));
}
[TestMethod()]
public void MaxTestBasics(int a, int b, int c)
{
if (a > int.MaxValue || b > int.MaxValue || c > int.MaxValue)
{
Assert.Fail();
}

if (a < int.MinValue || b < int.MinValue || c < int.MinValue)
{
Assert.Fail();
}
}
}
}


Am I totally off base here? My teacher won't bulge and give me any hints.. What other test cases could I use that would be useful?

Answer

One viable testing strategy is called branch coverage. There, you try to write unit tests that are covering each execution branch in the system under test.

In your case, distinct execution branches are the return instructions: return a, return c, return b and again return c at the bottom.

You can cover entire function with these use cases (values for a, b and c, respectively):

4, 2, 3
4, 2, 5
4, 2, 4 <-- boundary condition
2, 4, 3
2, 2, 3 <-- boundary condition
2, 4, 5
2, 4, 4 <-- boundary condition

There are exactly four test cases that are ensuring that all branches are covered. Additional three cases are there to cover boundary conditions that still end up in specific branches.

Below is the implementation of these test cases in xUnit. I have chosen xUnit because it supports multiple test cases for one test method through use of the InlineData attribute:

[Theory]
[InlineData(4, 2, 3, 4)]
[InlineData(4, 2, 5, 5)]
[InlineData(4, 2, 4, 4)]
[InlineData(2, 4, 3, 4)]
[InlineData(2, 2, 3, 3)]
[InlineData(2, 4, 5, 5)]
[InlineData(2, 4, 4, 4)]
public void Max_ReturnsExpectedValue(int a, int b, int c, int expectedMax)
{
    int actualMax = Program.Max(a, b, c);
    Assert.Equal(expectedMax, actualMax);
}

Just for the record, all seven test cases are passing for your implementation above.

On a related note, this kind of testing is called white-box testing. You have access to concrete implementation and then you are writing tests for that implementation. If you wanted to implement black-box testing, then your test cases would only be driven by the expectations you have from the function. For example, these could be the legitimate test cases for black box tests:

1, 1, 1 -> 1
1, 1, 2 -> 2
1, 2, 1 -> 2
1, 2, 2 -> 2
1, 2, 3 -> 3
1, 3, 1 -> 3
1, 3, 2 -> 3
1, 3, 3 -> 3
2, 1, 1 -> 2
...

The problem here is that we don't know which boundary conditions are relevant and therefore we have to add quite a lot of them to cover all the options. If you keep going in the same direction, total number of tests would be 3^3=27.

Comments