Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it bad practice to have unit tests run in a loop?

I am writing some unit tests in C# using nunit. Consider the following code:

    [Test ()]
    public void TestCarCost () {
        for (int i = 0; i < Examples.exampleCount; i++) {
            Car car = new Car(Examples[i]);
            Assert.AreEqual (car.getCost (), Examples[i].cost, "Test " + (i + 1) + " failed");
        }
    }

Let's say Examples is a class with some static data for testing different possible input for car types, as you can see I am trying to test for any bugs in the car.getCost() function. Now having this in a loop feels wrong somehow, since for instance when any of the asserts fails it always sends you to the same line of code. Moreover, as far as I can tell whenever an assert fails in a [Test ()] nunit immediately terminates the rest of the test code. Which means if I have everything inside a loop and assert nr 1 fails, I don't get to see whether the other asserts failed. Writing explicitly all of the tests is essentially writing copypasted code, so it also doesn't feel right. What would be considered good practice in this situation? Is it ok to have a ton of similar code in unit testing? Is there some elegant solution that I am missing?

like image 846
TheMountainThatCodes Avatar asked Dec 11 '22 12:12

TheMountainThatCodes


2 Answers

NUnit has built-in support for parameterized tests. If your example data is simple (i.e. values can appear in attributes, like a string), your test could use TestCase attributes and look something like this:

[TestCase("Example Value 1", ExpectedResult=123.4)]
[TestCase("Example Value 2", ExpectedResult=567.8)]
[TestCase("Example Value 3", ExpectedResult=901.2)]
public decimal TestCarCost (string exampleValue) {
    Car car = new Car(exampleValue);
    return car.GetCost();        
}

If your example values (really, test input) cannot appear in attributes, you can use the TestCaseSource attribute to indicate a member that will supply the values for you:

[TestCaseSource(nameof(Examples))]
public void TestCarCost (ExampleInput exampleValue) {
    Car car = new Car(exampleValue);
    return car.GetCost();        
}

public IEnumerable<ITestCaseData> Examples {
    get {
        yield return new TestCaseData(new ExampleInput(1,2)).Returns(123.4);
        yield return new TestCaseData(new ExampleInput(3,4)).Returns(567.8);
        yield return new TestCaseData(new ExampleInput(5,6)).Returns(901.2);
    }
}
like image 153
Patrick Quirk Avatar answered Dec 28 '22 00:12

Patrick Quirk


It is better to use TestCaseAttribute instead loop. Please read more about in following link.

In your case should be:

[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
public void TestCarCost (int id) 
{           
    Car car = new Car(Examples[id]);
    Assert.AreEqual (car.getCost (), Examples[id].cost, "Test " + (i + 1) + " failed");

}

Here you can create multiple test cases for the single test method. For each test case, you can supply parameter in TestCase() attribute and pass as id to your test method.

Based on id you can access to your resources which are in your case `Examples[id].'

I will rather use this way because I can monitor each case and it doesn't require additional logic.

Also if you put additional logic in a unit test, you will increase chance to fail, and you will rely on the accuracy of that logic.

like image 38
kat1330 Avatar answered Dec 27 '22 23:12

kat1330