NUnit Tutorial: Parameterized Tests With Examples
In this guide, we will showcase NUnit parameterized test cases along with the commonly used attributes like the TestFixture NUnit attribute.
Join the DZone community and get the full member experience.
Join For FreeCross-browser testing has become an integral part of the test process to ensure the product experience and behavior remain consistent across different combinations of web browsers, devices, and operating systems. As testing has to be performed on varied combinations, it can lead to code duplication as a lot of test methods will be doing the same thing but on different input combinations. I have come across many such situations during the code optimization process when I felt that a part of the code is either duplicated or redundant.
One important lesson I learned from these situations is that you should never leave such activities for the future as it becomes more challenging to optimize with the increase in LOC (Lines of Code). This is where a parameterized test can be beneficial as it enables testing the code/methods against different input values. Test parameterization should be explored in cross-browser testing as the same tests need to be executed on different web browsers and different versions of the same web browser. In this blog, we learn how to execute NUnit parameterized tests with examples.
What Is Parameterization In NUnit?
NUnit is one of the widely used C# test frameworks for cross-browser testing as it is compatible with the Selenium test suite. NUnit supports parameterized tests since the release of NUnit 2.5. Test methods can have parameters, and various attributes are available that indicate what arguments should be supplied by the NUnit framework.
Some NUnit attributes enable specifying arguments inline, while other attributes use a separate method or field to hold the arguments.
We will use Visual Studio 2019 (Community Edition) for development, which can be downloaded from here.
Note: This blog will only focus on creating NUnit parameterized test examples that will aid you in the process of cross-browser testing or automated browser testing.
NUnit Parameterized Tests (Or Data-Driven Tests)
Parameterization of NUnit tests was introduced with version 2.5 (as mentioned above) and is considered extremely useful when used with the Selenium WebDriver. Using special attributes in NUnit, you can develop foolproof tests by verifying them on different browsers, browser versions, and platforms, which can be passed as parameters to the test.
To demonstrate an NUnit parameterized test example, we perform the test mentioned below:
Open DuckDuckGo in the intended web browser.
Locate the search box.
Enter search query i.e., LambdaTest.
Execute the search operation.
Free up the resources.
You can refer to our detailed article on NUnit, which walks you through the implementation of executing the above-mentioned test without parameterization.
Cross-browser testing on the local Selenium grid can hit a roadblock as it is not feasible to have an in-house setup with different combinations of browsers, platforms, and devices.
Using a local Selenium grid for cross-browser testing can lead to a reduction of test coverage. Instead, cross-browser testing should be performed on cloud-based cross-browser testing platforms like LambdaTest, where testing can be performed on 2000+ browsers, thereby providing wider test coverage.
To get started, you should create an account on LambdaTest and note the user-name & access-key from the Profile Page. Desired capabilities can be generated using LambdaTest Capabilities Generator, and these capabilities enable to execute tests using different browser + OS combinations on remote Selenium grid. Along with parameterization, the prerequisite is that the tests have to be executed in parallel to complete test execution within a shorter time. With my current plan, I can execute five tests in parallel on the remote Selenium Grid on LambdaTest.
Let’s explore the different attributes in NUnit using which we can come up with an NUnit parameterized test:
TestCase Attribute
The TestCase attribute in NUnit marks a method with parameters as a test method. It also provides the inline data that needs to be used when that particular method is invoked. It can appear one or more times on the test method, with each appearance carrying values for the test case. Make more copies of the attribute if you want multiple cases. The data type of the values provided to the TestCase attribute should match with that of the arguments used in the actual test case.
This attribute that helps in coming up with an NUnit parameterized test also supports several additional named parameters like Author, Category, Description, ExpectedResult, TestName, etc. The execution order of the TestCase attribute can vary when used in combination with other data-providing attributes.
Demonstration – [TestCase] Attribute
The search for ‘LambdaTest’ on DuckDuckGo is carried out on the following browser + OS combinations.
BROWSER | BROWSER VERSION | PLATFORM/OPERATING SYSTEM |
---|---|---|
Chrome | 72.0 | Windows 10 |
Internet Explorer | 11.0 | Windows 10 |
Safari | 11.0 | macOS High Sierra |
Microsoft Edge | 18.0 | Windows 10 |
The complete implementation is below:
using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using NUnit.Framework;
using System.Threading;
using System.Collections.Generic;
namespace ParallelLTSelenium
{
[TestFixture]
public class ParallelLTTests
{
ThreadLocal<IWebDriver> driver = new ThreadLocal<IWebDriver>();
private String browser;
private String version;
private String os;
[Test]
[TestCase("chrome", "72.0", "Windows 10")]
[TestCase("internet explorer", "11.0", "Windows 10")]
[TestCase("Safari", "11.0", "macOS High Sierra")]
[TestCase("MicrosoftEdge", "18.0", "Windows 10")]
[Parallelizable(ParallelScope.All)]
public void DuckDuckGo_TestCase_Demo(String browser, String version, String os)
{
String username = "user-name";
String accesskey = "access-key";
String gridURL = "@hub.lambdatest.com/wd/hub";
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.SetCapability("user", username);
capabilities.SetCapability("accessKey", accesskey);
capabilities.SetCapability("browserName", browser);
capabilities.SetCapability("version", version);
capabilities.SetCapability("platform", os);
driver.Value = new RemoteWebDriver(new Uri("https://" + username + ":" + accesskey + gridURL), capabilities, TimeSpan.FromSeconds(600));
System.Threading.Thread.Sleep(2000);
driver.Value.Url = "https://www.duckduckgo.com";
IWebElement element = driver.Value.FindElement(By.XPath("//*[@id='search_form_input_homepage']"));
element.SendKeys("LambdaTest");
/* Submit the Search */
element.Submit();
/* Perform wait to check the output */
System.Threading.Thread.Sleep(2000);
}
[TearDown]
public void Cleanup()
{
bool passed = TestContext.CurrentContext.Result.Outcome.Status == NUnit.Framework.Interfaces.TestStatus.Passed;
try
{
// Logs the result to Lambdatest
((IJavaScriptExecutor)driver.Value).ExecuteScript("lambda-status=" + (passed ? "passed" : "failed"));
}
finally
{
// Terminates the remote webdriver session
driver.Value.Quit();
}
}
}
}
Code WalkThrough
Step 1 – The data of IWebDriver is stored per-thread basis, and that is the reason for using the ThreadLocal class.
public class ParallelLTTests { ThreadLocal driver = new ThreadLocal();
Step 2 – The input combinations of browser, version, and platform constitute the parameters for the test case. These are supplied via the TestCase attribute. As the test has to be performed on four combinations, there are four occurrences of the attribute. The TestCase attribute is under the Test attribute, which defines the start of a test case.
As we want the test and its descendants to execute in parallel with other tests at the same level, ParallelScope is set to All using the Parallelizable attribute.
xxxxxxxxxx
[Test]
[TestCase("chrome", "72.0", "Windows 10")]
[TestCase("internet explorer", "11.0", "Windows 10")]
[TestCase("Safari", "11.0", "macOS High Sierra")]
[TestCase("MicrosoftEdge", "18.0", "Windows 10")]
[Parallelizable(ParallelScope.All)]
Step 3 – We have not used the SetUp attribute as the steps being performed as a part of that attribute, i.e., creating instances of a remote Selenium WebDriver, setting browser capabilities, etc., are shifted to the TestCase attribute.
xxxxxxxxxx
public void DuckDuckGo_TestCase_Demo(String browser, String version, String os)
{
String username = "user-name";
String accesskey = "access-key";
String gridURL = "@hub.lambdatest.com/wd/hub";
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.SetCapability("user", username);
capabilities.SetCapability("accessKey", accesskey);
capabilities.SetCapability("browserName", browser);
capabilities.SetCapability("version", version);
capabilities.SetCapability("platform", os);
Step 4 – The search box on DuckDuckGo is located using the XPath locator, for which we made use of the browser’s Inspect Tool. A search term is entered in the search box to perform the search operation.
xxxxxxxxxx
driver.Value.Url = "https://www.duckduckgo.com";
IWebElement element = driver.Value.FindElement(By.XPath("//*[@id='search_form_input_homepage']"));
element.SendKeys("LambdaTest");
/* Submit the Search */
element.Submit();
Step 5 – The resources used by the Selenium WebDriver instance is released as part of the TearDown attribute.
xxxxxxxxxx
[TearDown]
public void Cleanup()
{
...................
...................
// Terminates the remote webdriver session
driver.Value.Quit();
}
As seen in the execution snapshot, the data passed in the TestCase attribute is used as parameters for executing tests in DuckDuckGo_TestCase_Demo(String browser, String version, String os).
As parallelism is enabled, the DuckDuckGo_TestCase_Demo test is executed on four different input combinations (supplied via the TestCase attribute) in one shot.
TestCaseSource Attribute
The TestCaseSource attribute can be applied to any test method, just like the TestCase attribute. The property, methods, or fields specified by the TestCaseSource attribute provide the arguments to the parameterized method. Unlike the TestCase attribute that is used to provide simple compile-time constants as parameters to the parameterized function, the TestCaseSource attribute can be used to provide more complicated parameter types.
The other major advantage of this attribute is that the source method is reusable across different tests. The object/data that is a part of the method using the TestCaseSource attribute can also be reused for multiple tests.
The source specified by the attribute can either return IEnumerable or a type that implements IEnumerable. For simple tests, object[] can be returned from the source. For more complicated tests, IEnumerable is used as the TestCaseData class provides additional test case information for a parameterized test, e.g., TestName, Result, ExpectedException, Properties, Result, etc.
For demonstrating the usage of TestCaseSource to create an NUnit parameterized test, the source method uses IEnumerable to provide values to the parameterized function.
Demonstration – [TestCaseSource] Attribute
We use the same test case that was used to showcase the usage of the TestCase attribute, i.e., a search for ‘LambdaTest’ is performed on the following browser and OS combinations.
BROWSER | BROWSER VERSION | PLATFORM/OPERATING SYSTEM |
---|---|---|
Chrome | 72.0 | Windows 10 |
Internet Explorer | 11.0 | Windows 10 |
Safari | 11.0 | macOS High Sierra |
Microsoft Edge | 18.0 | Windows 10 |
The complete implementation is below:
xxxxxxxxxx
using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using NUnit.Framework;
using System.Threading;
using System.Collections.Generic;
namespace ParallelLTSelenium
{
[TestFixture]
[Parallelizable(ParallelScope.All)]
public class ParallelLTTests
{
ThreadLocal<IWebDriver> driver = new ThreadLocal<IWebDriver>();
private String browser;
private String version;
private String os;
private static IEnumerable<TestCaseData> AddBrowserConfs()
{
yield return new TestCaseData("chrome", "72.0", "Windows 10");
yield return new TestCaseData("internet explorer", "11.0", "Windows 10");
yield return new TestCaseData("Safari", "11.0", "macOS High Sierra");
yield return new TestCaseData("MicrosoftEdge", "18.0", "Windows 10");
}
[Test, TestCaseSource("AddBrowserConfs")]
public void DuckDuckGo_TestCaseSource_Demo(String browser, String version, String os)
{
String username = "user-name";
String accesskey = "access-key";
String gridURL = "@hub.lambdatest.com/wd/hub";
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.SetCapability("user", username);
capabilities.SetCapability("accessKey", accesskey);
capabilities.SetCapability("browserName", browser);
capabilities.SetCapability("version", version);
capabilities.SetCapability("platform", os);
driver.Value = new RemoteWebDriver(new Uri("https://" + username + ":" + accesskey + gridURL), capabilities, TimeSpan.FromSeconds(600));
System.Threading.Thread.Sleep(2000);
driver.Value.Url = "https://www.duckduckgo.com";
IWebElement element = driver.Value.FindElement(By.XPath("//*[@id='search_form_input_homepage']"));
element.SendKeys("LambdaTest");
/* Submit the Search */
element.Submit();
/* Perform wait to check the output */
System.Threading.Thread.Sleep(2000);
}
[TearDown]
public void Cleanup()
{
bool passed = TestContext.CurrentContext.Result.Outcome.Status == NUnit.Framework.Interfaces.TestStatus.Passed;
try
{
// Logs the result to Lambdatest
((IJavaScriptExecutor)driver.Value).ExecuteScript("lambda-status=" + (passed ? "passed" : "failed"));
}
finally
{
// Terminates the remote webdriver session
driver.Value.Quit();
}
}
}
}
The core implementation that involves the following remains the same as the [TestCase] attribute:
Invocation of Selenium WebDriver in the target web browser.
Generation of browser capabilities.
Performing a search on DuckDuckGo, and
Releasing the resources used by the WebDriver instance as a part of the TearDown attribute.
You can refer to steps 1, 3, 4, and 5 from the ‘Code WalkThrough’ section of [TestCase] attribute for more detailed information on how the above requirements are implemented in the code.
As shown in the snippet below, AddBrowserConfs() is the source method that returns IEnumerable. The browser capabilities are passed to the parameterized function, i.e., DuckDuckGo_TestCaseSource_Demo(String browser, String version, String os) through the TestCaseData attribute.
The test case information provided via TestCaseData matches the argument type being used in DuckDuckGo_TestCaseSource_Demo, i.e., the arguments should be of type String.
The TestCaseSource attribute uses the AddBrowserConfs method to supply parameters to the test case DuckDuckGo_TestCaseSource_Demo.
xxxxxxxxxx
private static IEnumerable AddBrowserConfs()
{
yield return new TestCaseData("chrome", "72.0", "Windows 10");
yield return new TestCaseData("internet explorer", "11.0", "Windows 10");
yield return new TestCaseData("Safari", "11.0", "macOS High Sierra");
yield return new TestCaseData("MicrosoftEdge", "18.0", "Windows 10"); }
...................
...................
[Test, TestCaseSource("AddBrowserConfs")]
public void DuckDuckGo_TestCaseSource_Demo(String browser, String version, String os)
{
...................
...................
}
As shown in the execution snapshot, the four tests are executed in parallel on LambdaTest’s remote Selenium grid. The browser capabilities are passed to DuckDuckGo_TestCaseSource_Demo using the TestCaseSource attribute that is used on AddBrowserConfs, a parameterized test method.
ValueSource Attribute
The ValueSource attribute functions similarly like TestCaseSource, except that it is used as a Method parameter.
Using the ValueSource attribute for creating parameterized tests in NUnit for cross-browser testing does not sound convincing as a list of tests is prepared based on the values supplied via the ValueSource attribute. For example, the input values shown below generate four test cases, i.e., chrome 70.0, chrome 71.0, Firefox 70.0, and Firefox 71.0
xxxxxxxxxx
private static string[] AddBrowserConfs = new string[] {
"chrome",
"Firefox"
};
private static string[] AddVerConfs = new string[] {
"70.0",
"71.0"
};
Demonstration – [ValueSource] Attribute
For demonstrating the usage of ValueSource attribute, a DuckDuckGo search for LambdaTest is performed on the following browser + OS combinations.
BROWSER | BROWSER VERSION | PLATFORM/OPERATING SYSTEM |
---|---|---|
Chrome | 70.0, 71.0 | Windows 10, macOS Mojave |
Firefox | 70.0, 71.0 | Windows 10, macOS Mojave |
The complete implementation is shown below:
xxxxxxxxxx
using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using NUnit.Framework;
using System.Threading;
using System.Collections.Generic;
namespace ParallelLTSelenium
{
[TestFixture]
[Parallelizable(ParallelScope.All)]
public class ParallelLTTests
{
ThreadLocal<IWebDriver> driver = new ThreadLocal<IWebDriver>();
private String browser;
private String version;
private String os;
private static string[] AddBrowserConfs = new string[] {
"chrome",
"Firefox"
};
private static string[] AddVerConfs = new string[] {
"70.0",
"71.0"
};
private static string[] AddOsConfs = new string[] {
"Windows 10",
"macOS Mojave"
};
[Test]
public void DuckDuckGo_ValueSource_Demo([ValueSourceAttribute("AddBrowserConfs")] String browser,
[ValueSourceAttribute("AddVerConfs")] String version,
[ValueSourceAttribute("AddOsConfs")] String os)
{
String username = "user-name";
String accesskey = "access-key";
String gridURL = "@hub.lambdatest.com/wd/hub";
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.SetCapability("user", username);
capabilities.SetCapability("accessKey", accesskey);
capabilities.SetCapability("browserName", browser);
capabilities.SetCapability("version", version);
capabilities.SetCapability("platform", os);
driver.Value = new RemoteWebDriver(new Uri("https://" + username + ":" + accesskey + gridURL), capabilities, TimeSpan.FromSeconds(600));
System.Threading.Thread.Sleep(2000);
driver.Value.Url = "https://www.duckduckgo.com";
IWebElement element = driver.Value.FindElement(By.XPath("//*[@id='search_form_input_homepage']"));
element.SendKeys("LambdaTest");
/* Submit the Search */
element.Submit();
/* Perform wait to check the output */
System.Threading.Thread.Sleep(2000);
}
[TearDown]
public void Cleanup()
{
bool passed = TestContext.CurrentContext.Result.Outcome.Status == NUnit.Framework.Interfaces.TestStatus.Passed;
try
{
// Logs the result to Lambdatest
((IJavaScriptExecutor)driver.Value).ExecuteScript("lambda-status=" + (passed ? "passed" : "failed"));
}
finally
{
// Terminates the remote webdriver session
driver.Value.Quit();
}
}
}
}
Code WalkThrough
There are no changes in the core implementation for invoking the WebDriver instance, generating the browser capabilities, performing DuckDuckGo search, and performing de-initialization. You can refer to steps 1, 3, 4, and 5 from the ‘Code WalkThrough’ section of the TestCase attribute for more information.
Three string arrays consisting of browser type, browser versions, and platforms are created. These arrays are then passed as individual parameters to the test method (DuckDuckGo_ValueSource_Demo) using the ValueSource attribute.
A total of eight test combinations are generated from the input values passed to the test method i.e. (chrome + 70.0 + Windows 10), (chrome + 71.0 + macOS Mojave), (Firefox + 70.0 + Windows 10), (Firefox + 71.0 + macOS Mojave), etc.
xxxxxxxxxx
private static string[] AddBrowserConfs = new string[] {
"chrome",
"Firefox"
};
private static string[] AddVerConfs = new string[] {
"70.0",
"71.0"
};
private static string[] AddOsConfs = new string[] {
"Windows 10",
"macOS Mojave"
};
[Test]
public void DuckDuckGo_ValueSource_Demo(
[ValueSource("AddBrowserConfs")]String browser, [ValueSource("AddVerConfs")] String version, [ValueSource("AddOsConfs")] String os
)
{
...................
...................
};
The execution snapshot below shows that eight test combinations are created from the test parameters supplied through the ValueSource attribute.
TestFixture Attribute
The TestFixture NUnit attribute marks a class that contains tests. Parameterized and generic test fixtures were introduced in NUnit 2.5. For an NUnit parameterized test, argument values are passed to the TestFixture NUnit attribute. The NUnit framework constructs a separate instance of TestFixture for each set of arguments.
From NUnit 2.5, test fixtures can take constructor arguments. In the example shown below, the test fixture would be instantiated by the NUnit framework three times, passing each set of arguments to the appropriate constructor.
xxxxxxxxxx
[TestFixture("chrome", "72.0", "Windows 10")]
[TestFixture("internet explorer", "11.0")]
[TestFixture("Safari", 11)]
Demonstration – [TestFixture] Attribute
A search for ‘LambdaTest’ is performed on the following browser and OS combinations.
BROWSER | BROWSER VERSION | PLATFORM/OPERATING SYSTEM |
---|---|---|
Chrome | 72.0 | Windows 10 |
Internet Explorer | 11.0 | Windows 10 |
Safari | 11.0 | macOS High Sierra |
Microsoft Edge | 18.0 | Windows 10 |
The complete implementation is below:
xxxxxxxxxx
using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using NUnit.Framework;
using System.Threading;
using System.Collections.Generic;
namespace ParallelLTSelenium
{
[TestFixture("chrome", "72.0", "Windows 10")]
[TestFixture("internet explorer", "11.0", "Windows 10")]
[TestFixture("Safari", "11.0", "macOS High Sierra")]
[TestFixture("MicrosoftEdge", "18.0", "Windows 10")]
[Parallelizable(ParallelScope.All)]
public class ParallelLTTests
{
ThreadLocal<IWebDriver> driver = new ThreadLocal<IWebDriver>();
private String browser;
private String version;
private String os;
public ParallelLTTests(String browser, String version, String os)
{
this.browser = browser;
this.version = version;
this.os = os;
}
[SetUp]
public void Init()
{
String username = "user-name";
String accesskey = "access-key";
String gridURL = "@hub.lambdatest.com/wd/hub";
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.SetCapability("user", username);
capabilities.SetCapability("accessKey", accesskey);
capabilities.SetCapability("browserName", browser);
capabilities.SetCapability("version", version);
capabilities.SetCapability("platform", os);
driver.Value = new RemoteWebDriver(new Uri("https://" + username + ":" + accesskey + gridURL), capabilities, TimeSpan.FromSeconds(600));
System.Threading.Thread.Sleep(2000);
}
[Test]
public void DuckDuckGo_TestFixtures_Demo()
{
{
driver.Value.Url = "https://www.duckduckgo.com";
IWebElement element = driver.Value.FindElement(By.XPath("//*[@id='search_form_input_homepage']"));
element.SendKeys("LambdaTest");
/* Submit the Search */
element.Submit();
/* Perform wait to check the output */
System.Threading.Thread.Sleep(2000);
}
}
[TearDown]
public void Cleanup()
{
bool passed = TestContext.CurrentContext.Result.Outcome.Status == NUnit.Framework.Interfaces.TestStatus.Passed;
try
{
// Logs the result to Lambdatest
((IJavaScriptExecutor)driver.Value).ExecuteScript("lambda-status=" + (passed ? "passed" : "failed"));
}
finally
{
// Terminates the remote webdriver session
driver.Value.Quit();
}
}
}
}
Code WalkThrough
Step 1 – The browser and platform combinations are specified as arguments to the TestFixture attribute.
xxxxxxxxxx
[TestFixture("chrome", "72.0", "Windows 10")]
[TestFixture("internet explorer", "11.0", "Windows 10")]
[TestFixture("Safari", "11.0", "macOS High Sierra")]
[TestFixture("MicrosoftEdge", "18.0", "Windows 10")]
[Parallelizable(ParallelScope.All)]
Step 2 – The arguments supplied via the TestFixture NUnit attribute are passed to the constructor that has three parameters of type String.
xxxxxxxxxx
public ParallelLTTests(String browser, String version, String os)
{
this.browser = browser;
this.version = version;
this.os = os;
}
Step 3 – The implementation related to the instantiation of WebDriver and setting up the capabilities for testing on the remote Selenium grid is added to the SetUp attribute.
xxxxxxxxxx
[SetUp]
public void Init()
{
String username = "user-name";
String accesskey = "access-key";
String gridURL = "@hub.lambdatest.com/wd/hub";
DesiredCapabilities capabilities = new DesiredCapabilities();
...................
...................
}
Step 4 – The implementation of de-initialization remains unchanged and is included as a part of the TearDown attribute.
Shown below is the execution snapshot where it is observed that ParallelLTTests constructor is called four times, i.e., the number of times the TestFixture was instantiated by the NUnit framework.
Summary
In this blog, we had a look at some of the widely used attributes in the NUnit framework that are used for test parameterization, including TestFixture NUnit. Apart from the attributes that we covered in the blog, there are other attributes that aid in creating parameterized tests in NUnit framework. However, many of those NUnit attributes are not useful for test scenarios related to cross-browser testing or automated browser testing.
Cross-browser testing on the local Selenium grid is not scalable; hence, it is recommended to perform automated browser testing on a remote Selenium grid. When choosing an attribute for an NUnit parameterized test, you should also look at the complexities involved in adding/removing test cases. To summarize, NUnit parameterized tests are extremely useful in cutting down duplication in tests that can unnecessarily bloat the test code’s size.
I hope the NUnit parameterized test example I have showcased above will help make a difference in your test strategies!
Happy testing!
Published at DZone with permission of Himanshu Sheth. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments