Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

xUnit and multiple data records for a test

I'm fairly new to Unit Testing and have the following code:

public class PowerOf
{
    public int CalcPowerOf(int @base, int exponent) {
        if (@base == 0) { return 0; }
        if (exponent == 0) { return 1; }
        return @base * CalcPowerOf(@base, exponent - 1);
    }
}

The unit test (with xUnit) I wrote for it first was this one, but I'm not quite sure if it's the right approach, or if I should use another pattern? What I wanted to know is whether this is the correct usage for passing multiple sets of data into a "unit test" - as I didn't see any docs or reference examples on xUnit's docs?

    [Fact]
    public void PowerOfTest() {
        foreach(var td in PowerOfTestData()) {
           Assert.Equal(expected, CalcPowerOf(@base, exponent));
        }
    }

    public class TestData {
      int Base {get;set;}
      int Exponent {get;set;}
      int ExpectedResult {get;set;}
    }

    public List<TestData> PowerOfTestData() {
        yield return new TestData { Base = 0, Exponent = 0, TestData = 0 };
        yield return new TestData { Base = 0, Exponent = 1, TestData = 0 };
        yield return new TestData { Base = 2, Exponent = 0, TestData = 1 };
        yield return new TestData { Base = 2, Exponent = 1, TestData = 2 };
        yield return new TestData { Base = 5, Exponent = 2, TestData = 25 };
    }
like image 258
RecursEve Avatar asked Feb 26 '17 10:02

RecursEve


People also ask

Does xUnit create a new instance for each test?

xUnit.net creates a new instance of the test class for every test that is run, so any code which is placed into the constructor of the test class will be run for every single test.

Should I use MSTest or xUnit?

As far as NUnit vs. XUnit vs. MSTest is concerned, the biggest difference between xUnit and the other two test frameworks (NUnit and MSTest) is that xUnit is much more extensible when compared to NUnit and MSTest. The [Fact] attribute is used instead of the [Test] attribute.

What is difference between Fact and Theory in xUnit?

Fact vs Theory In an Xunit test class or fixture, there are two kinds of tests: Fact tests and Theory tests. The small, but very important, difference is that Theory tests are parameterized and can take outside input. Fact tests, however, are not parameterized and cannot take outside input.

Does xUnit run tests in parallel?

Running unit tests in parallel is a new feature in xUnit.net version 2. There are two essential motivations that drove us to not only enable parallelization, but also for it to be a feature that's enabled by default: As unit testing has become more prevalent, so too have the number of unit tests.


3 Answers

You're using a class member to define your data which is wrong in your case. You use this approach when the values are specified at run-time (maybe looping through values from 1 to MAX) which is not your case (You have hard-coded data). I think this approach is better:

[Theory]
[InlineData(0, 0, 0)]
[InlineData(0, 1, 0)]
[InlineData(2, 0, 1)]
[InlineData(2, 1, 2)]
[InlineData(2, 2, 4)]
[InlineData(5, 2, 25)]
[InlineData(5, 3, 125)]
[InlineData(5, 4, 625)]
public void PowerOfTest(int @base, int exponent, int expected) 
{
   var result = CalcPowerOf(@base,exponent);
   Assert.Equal(expected, result);
}

This way you have a more readable test in a large class.

like image 50
Zein Makki Avatar answered Oct 28 '22 09:10

Zein Makki


For a strongly-typed parameter list to the test method without using object[], you can also use TheoryData. It defines several generic overloads for up to 10 parameters. Since your method has 3 integer input values, @base, exponent and expected, you can use a property of type TheoryData<int, int, int>. Then, annotate your PowerOfTest method with the Theory and MemberData(nameof(PropertyName) attribute:

class PowerOfTests
{
    TheoryData<int, int, int> PowerOfTestData => new TheoryData<int, int, int>
    {
       { 0, 0, 0 },
       { 0, 1, 0 },
       { 2, 0, 1 },
       { 2, 1, 2 },
       { 5, 2, 25 }
    };
     
    [Theory]
    [MemberData(nameof(PowerOfTestData)] 
    public void PowerOfTest(int @base, int exponent, int expected) 
    {
        Assert.Equal(expected, CalcPowerOf(@base, exponent));
    }
}

The reason I can initialize TheoryData<int, int, int> with the:

{ 
    { param1, param2, param3 }, 
    ... 
}

syntax(called a collection initializer) is because it implements IEnumerable and defines an Add<int, int, int>(int, int, int) method that takes in three integer parameters( the <int, int, int> generic overload of TheoryData).

This also makes it possible to have the test data in a separate class by inheriting from TheoryData:

class PowerOfTestDataClass : TheoryData<int, int, int>
{
    public PowerOfTestDataClass()
    {
       Add(0, 0, 0);
       Add(0, 1, 0);
       Add(2, 0, 1);
       Add(2, 1, 2);
       Add(5, 2, 25);
    }
}

Now instead of MemberData, annotate the PowerOfTest() method with the ClassData attribute and its parameter as PowerOfTestDataClass's type:

[Theory]
[ClassData(typeof(PowerOfTestDataClass)] 
public void PowerOfTest(int @base, int exponent, int expected) 
{
    Assert.Equal(expected, CalcPowerOf(@base, exponent));
}

The advantage of having a strongly-typed typed parameter list is you can always ensure that the arguments will have the right type and the right length. While the object array in IEnumerable<object[]> also works, it will allow any type and any length.

Reference: https://andrewlock.net/creating-strongly-typed-xunit-theory-test-data-with-theorydata/

like image 28
Amal K Avatar answered Oct 28 '22 07:10

Amal K


You'd be better of using a specialised construct in xUnit, called a Theory, that handles so called "Data Driven Tests". Decorate your testmethod with the Theory attribute and then make sure to return a static "member" with input parameters and the expected result as you already kind of did with the TestData class. See the example below, and ref to the xUnit documentation: "Writing your first theory".

I would thus refactor your code like below. Firstly decorating the test with the attributes Theory and MemberData and adding parameters to your test "@base", "exponent" and "expectedResult" - as you had in your TestData class. xUnit won't allow you to use the TestData class, it only accepts an IEnumerable<object> and requires it to be static, but the benefit to a foreach loop construct is that all the tests are ran seperately. And for each run with a specific data set you'll get a green or red flag!

public class PowerOfTests
{
    [Theory]
    [MemberData(nameof(PowerOfTestData))]
    public void PowerOfTest(int @base, int exponent, int expected) {
        Assert.Equal(expected, CalcPowerOf(@base, exponent));
    }

    public static IEnumerable<object[]> PowerOfTestData() {
        yield return new object[] { 0, 0, 0 };
        yield return new object[] { 0, 1, 0 };
        yield return new object[] { 2, 0, 1 };
        yield return new object[] { 2, 1, 2 };
        yield return new object[] { 2, 2, 4 };
        yield return new object[] { 5, 2, 25 };
        yield return new object[] { 5, 3, 125 };
        yield return new object[] { 5, 4, 625 };
    }
}
like image 45
Yves Schelpe Avatar answered Oct 28 '22 08:10

Yves Schelpe