DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Testing, Tools, and Frameworks
  4. Parameterized Test Example in .NET Core Using NUnit

Parameterized Test Example in .NET Core Using NUnit

A lot of times when writing unit tests we end up with a lot test methods that look the same and actually do the same thing. Read on for a better way

Avraam Piperidis user avatar by
Avraam Piperidis
CORE ·
Jun. 25, 19 · Tutorial
Like (3)
Save
Tweet
Share
29.63K Views

Join the DZone community and get the full member experience.

Join For Free

A lot of times when writing unit tests we end up with a lot test methods that look the same and actually do the same thing. Also, there are special cases where we want to have high test coverage and in-depth test access for our crucial and very important core functionality methods.

For example, when creating a framework or a library, usually we want to write many tests and cover all possible aspects and outcomes, which may lead to a large amount of certain behavior test methods.

Very often, we end up with these test methods with the same logic and behavior but with different input and data values. We are going to create parameterized tests that will test the same method but with different values.

The Scenario

Let's start with a simple method that calculates the total price of a product * quantity and then it applies the discount on the total price. 

public static double calculate(double price,int quantity,double discount) 
{
    double totalPrice = price * quantity;
    double totalPriceWithDiscount = System.Math.Round(totalPrice - (totalPrice * discount/100),2);
    return totalPriceWithDiscount;
}

As simple as it looks, there is a lot of important other stuff to test here, like:

  • nulls

  • negative and zero inputs

  • exceptions/input validation handling

  • rounding

But we are not going to cover these here.

We are going to focus on the parameterized test and validating the mathematical correctness of the calculation method.

Without a parameterized test, we have this plain test.

[Test]
public void testCalculate() 
{
    Assert.AreEqual(100,MyClass.calculate(10,10,0));
}

This passes and, indeed, if the price of a product is 10, the quantity is 10, and we have zero discounts then the total price is 100.

The problem is that with this setup if we want to test different values and results we have to write a different test method for every different input.

The TestCase Attribute

We start by first converting the above test to a parameterized test using the TestCase attribute.

[TestCase(10,10,10,90)]
[TestCase(10,10,0,100)]
public void testCalculate(double price,int quantity,double discount,double expectedFinalAmount) 
{
    Assert.AreEqual(expectedFinalAmount,MyClass.calculate(price,quantity,discount)); 
}

Now, this method will run for every TestCase annotation it has. A mapping will occur at runtime to the values we provided at the annotations and copied down to the method parameters. In our example, this test will run two times. We can pass reference types and value types.

Usually, the order of parameters goes by first providing the values and the last one is the expected result.

The TestCaseSource Attribute

For every different input, we have to add a TestCase attribute at the top of the test method. To organize the code, and for reusability reasons, we are going to use the TestCaseSource attribute. We're going to create a provider method and centralize the input data.

First, we create a provider method and then move and fill it with the data we want.

public static IEnumerable<TestCaseData> priceProvider() 
{
    yield return new TestCaseData(10,10,10,90);
    yield return new TestCaseData(10,10,0,100);
}

And also refactor the testCalculate method to use the priceProvider method.

[Test,TestCaseSource("priceProvider")]
public void testCalculate(double price,int quantity,double discount,double expectedFinalAmount) 
{
    Assert.AreEqual(expectedFinalAmount,MyClass.calculate(price,quantity,discount)); 
}

This is the same as having the TestCase attributes on top of the method.

We can also provide a different class for the provider methods to isolate and centralize the code in class/file level.

[Test,TestCaseSource(typeof(MyProviderClass),"priceProvider")]
public void testCalculate(double price,int quantity,double discount,double expectedFinalAmount) 
{
    Assert.AreEqual(expectedFinalAmount,MyClass.calculate(price,quantity,discount)); 
}

priceProvider is a static method inside MyProviderClass.

Extra Parameterization With the Help of the TestFixture Attribute

Let's add one more step of parameterization with the help of TestFixture Attribute. Usually,TestFixture is a class attribute to mark a class that contains tests, on the other hand, one of the biggest features is that TestFixture can take constructor arguments. NUnit will create and test a separate instance for every input set.

Let's assume that except for the final amount we test above, there is an extra amount applied depending on what category the product is, which could be category 1 or 2. 

[TestFixture(typeof(int),typeof(double),1,5)]
[TestFixture(typeof(int),typeof(double),2,6.5)]
public class TestCharge<T,X>
{
    T categoryType;
    X extraValue;
    public TestCharge(T t,X x) 
    {
        this.categoryType = t;
        this.extraValue = x;
    }
}

We know, in fact, that in category one the extra amount is 5 and in category two it's 6.5.

We can now run all the tests again but also for every TestFixture we provided.

For example, we test the calculation also depending on the category.

[Test,TestCaseSource(typeof(MyProviderClass),"priceProvider")]
public void testCalculateCategory(double price,int quantity,double discount,double expectedFinalAmount) 
{
    Assert.AreEqual(expectedFinalAmount+(double)(object)this.extraValue,
                    MyClass.calculateCategory(price,quantity,discount,(int)(object)this.categoryType)); 
}

Final Words

Remember, what makes a good unit test is its simplicity, the ease of reading and writing, the reliability, not to be treated as an integration test, and it has to be fast. 

The original and complete repository of code samples can be found here.

unit test NUnit

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Introduction to Container Orchestration
  • Test Execution Tutorial: A Comprehensive Guide With Examples and Best Practices
  • Public Key and Private Key Pairs: Know the Technical Difference
  • OpenVPN With Radius and Multi-Factor Authentication

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: