Unit Testing
Use SnapDevelop to run unit tests to keep code healthy, and find errors before the release of an application. Run your unit tests frequently to make sure your code is working properly.
xUnit unit testing tool
SnapDevelop installs by default the xUnit unit testing framework. Once installed, you can create an xUnit Test project in SnapDevelop to run unit tests. xUnit Test projects will automatically include xUnit related packages when created.
SnapDevelop also provides the xUnit Test window for running unit tests, viewing test results, and debugging unit tests. The xUnit Test window can be opened from the Test > xUnit Test menu.
The xUnit Test window will automatically display the test methods in the solution. If your solution does not include an xUnit Test project, the xUnit Test node will be empty. Please add an xUnit Test project to your solution, or use our test project (download from here).
Running unit tests
When you open the xUnit Test window, all test methods for the current solution are displayed in a treeview structure: xUnit Test root node > Project > Namespace > Class > Test Methods (if the method contains multiple groups of test data, the data is also displayed on the next layer). The treeview automatically updates as you add or delete items. You can view the treeview by clicking the expand or collapse icon in the window navigation bar (or right-clicking the treeview and selecting expand or collapse ). Or use the search box to quickly find test methods.
You can run the test method by:
Click the Run All Tests in View icon in the navigation bar to run all test methods visible in the treeview. If you filter out some test methods via the search box, only the test methods that are visible in the treeview will run.
Select the node in the tree view, then click the Run icon in the navigation bar (or select Run in the right-click menu) to run the test method under the current node.
If you have already run the test method, you can click the Repeat Last Run icon in the navigation bar to run the test again.
If you have already run a test method, you can click the Run Failed Tests icon in the navigation bar to run all the failed test, or click the drop-down menu of the Run icon and select Run Tests That Are Not Running or Run Passed Tests.
View test results
After running the test method, you can view the test method's duration, traits, and error messages in the treeview of the xUnit Test window. If the Trait attribute is used in the method, the method attribute defined in the Trait attribute will be displayed in the Traits column.
When you click on a node in the treeview, xUnit Test window will display the Group Summary or Test Detail Summary.
When you select a node above the test method level (e.g. project node, namespace node), the Test Detail Summary will display statistics for the current group, including the total number of test methods, total duration, and test results.
When you select the Test Method node, the Test Detail Summary will display the detailed information of the current test method, including the method name, test result, the code file and code line where the method is located, and the time-consumed ( if already running). You can click the code file to open the file to view the current test method. Or right-click the test method in the treeview and select Go to Test. If the test fails, you will also see error stack information here.
Debug unit tests
You can debug a test method if it does not behave as expected.
You can click the drop-down menu for the Run icon in the xUnit Tests window navigation bar and select Debug, Debug All In View, or Debug Last run. Or right-click a node in the treeview and select Debug from the context menu.
An xUnit test tutorial
Opening an existing solution for testing
There is a demo solution provided for you to walk through the tutorial. Please download it from here.
Now, load the demo solution and open the Calculator.cs file. You need to add the actual business logic to it. This is how the Calculator class should be:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Calculator
{
public class Calculator
{
public static double Addition(double num1, double num2)
{
return num1 + num2;
}
public static double Subtraction(double num1, double num2)
{
return num1 - num2;
}
public static double Multiplication(double num1, double num2)
{
return num1 * num2;
}
public static double Division(double num1, double num2)
{
return num1 / num2;
}
}
}
As you can see, it is a very simple class. It only performs some very simple operations and returns the result of each operation. But this is enough to start using the xUnit Test projects.
Also notice the use of the static
keyword. We will use this to avoid having to instantiate it later on.
Creating an xUnit test project
Let's add a new xUnit Test project to our solution. Right-click on the solution. Then click Add > New Project.
In the Add New Project painter, select xUnit Test and click Next.
After introducing a name Calculator.Tests to the project and choosing the target framework, the xUnit Test project is created.
This will create a simple project with one class for the basic starting point of your tests.
Since we will create our own class to test our code, we can proceed to delete this object. So, right-click on the UnitTest1.cs object and then select Delete.
Performing unit test using the Fact
attribute
Adding a test class
Then create your own class by right-clicking on the Calculator.Tests project and selecting Add > Class. Name it Calculator.Tests.cs and click Create.
The following code will be generated for CalculatorTests class. Delete the Test1() method and the [Fact] attribute, and then add using statement: "using Xunit;".
using System;
using Xunit;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Calculator.Tests
{
public class CalculatorTests
{
// ***Delete these lines of code*** //
[Fact]
public void Test1()
{
}
// ******************************* //
}
}
Now that we have our testing class, we will add a reference to the Calculator.Tests project. To do so, right-click on the Calculator.Tests project and select Add Project Reference….
Then select the checkbox for the project Calculator and click OK.
This will add a reference to our project and make it available to our testing project.
Our class is ready for us to start coding our tests.
using System;
using Xunit;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Calculator.Tests
{
public class CalculatorTests
{
}
}
Developing the test method
Here is where we start developing our first method that will test the Addition method from our Calculator class. The coding of our tests will be done on the CalculatorTests class, and any required changes to the actual app will be done to the methods of the Calculator class.
One very useful thing to keep in mind is to apply a naming convention for our test methods. One of these conventions states that you need to use the word Should
in the name of your test method in order to make it clear that the method should perform certain testing task. But for this section we will use our own naming convention that will start with the name of the method to test, then a simple and short explanation of what it should do. So we will name it:
public void Addition_ShouldCalculateSimpleValues()
{
}
Notice that it returns void
. That is because testing methods usually don't need to return any value. So this method will test the Addition
method.
The testing methods usually have three main sections inside of their lines of code: Instantiation
, Action
and Assertion
.
The Instantiation
section is where we set up what's needed for the test to run. The Action
section is where we define the method that we want to test, and any other operations needed for it to run. Then, the Assertion
section is where we actually assert that the method should do what we expect it to do. For that, we will use the Assert object.
The Assert object has many APIs that help you determine if the test passed or failed. You can find all of the APIs of Assert using the Intelligent Code Completion feature of the SnapDevelop IDE.
So let's add the code to our test method for the actual Test:
[Fact]
public void Addition_ShouldCalculateSimpleValues()
{
// Instantiation
double expected = 9;
// Action
double actual = Calculator.Addition(3, 6);
// Assertion
Assert.Equal(expected, actual);
}
Notice how we decorated our test method with the attribute [Fact]
. That is because there are mainly two different types of test methods:
- A
[Fact]
test is always true. It can test invariant conditions. - A
[Theory]
test is only true for a particular set of data.
We will start by testing a Fact because we know, for sure, that 3 plus 6 equals 9. We'll test a Theory after that in this section.
So now, click on the Test > xUnit Test menu option to open the xUnit Test window. You will notice that our test already appears there:
Running your test!
It's time to run your test! Click on Run All in the xUnit Test window. Wait a few seconds for the projects to build and the test to run. Then you will notice that the test has actually passed!
This was an actual test that asserts that if you pass a 3 and a 6 to the Addition
method, it should return 9.
Performing unit test using the Theory attribute
Even though this was very simple, it illustrates the basics of how [Fact]
tests are developed and tested against our code. But we will now perform a more close to the real-world test, where we want to run [Theory]
tests to challenge our code against several different sets of values, thus actually testing different scenarios that our methods could, would, or should stumble upon. So, we'll change our Fact to a Theory.
Developing the test method
[Theory]
tests usually test different scenarios, or values, of data. We need to pass these values to our test method. In order to do that, we use the [InlineData]
attribute. This attribute will set the different scenarios that will test the different values of our data. We will now add some of these scenarios to our method:
[Theory]
[InlineData(3, 6, 9)]
[InlineData(4, 3, 7)]
[InlineData(-10, 10, 0)]
[InlineData(34, 5.33, 39.33)]
[InlineData(-10, -10, -20)]
public void Addition_ShouldCalculateSimpleValues(double num1, double num2, double expected)
{
// Action
double actual = Calculator.Addition(num1, num2);
// Assertion
Assert.Equal(expected, actual);
}
Notice the changes we made to our method. We have added three new parameters in order to receive the values from our Theory. Plus, calling our actual method, it passes the parameters making it more real-worldish.
If we do a build to our solution (press F6), you will notice that now each one of the InlineData
attributes we added has become tests in our xUnit Test window.
Running your test!
We can now click on Run All again to run all our tests. They should all pass!
Fixing a failed test
So far all Theory
tests have passed successfully. But what if one of them didn't? Well, that's the whole purpose of this approach. How to tackle a test that doesn't pass? What to do next? Well… let's see another test method example:
[Theory]
[InlineData(8, 4, 2)]
[InlineData(-9, 3, -3)]
[InlineData(15, 0, 0)]
public void Division_ShouldCalculateSimpleValues(double num1, double num2, double expected)
{
// Action
double actual = Calculator.Division(num1, num2);
// Assertion
Assert.Equal(expected, actual);
}
This testing method example, runs three Theory
tests of the Division
method.
If you run these tests, you'll notice that one of them fails.
But this is indeed useful information, because it is telling us that our method is not prepared to divide by zero and return a zero. It is actually returning undetermined or infinite.
This means that we need to refactor our code so that it can pass this particular test too. And this is where we actually make changes to our Calculator class in order to comply with all the requirements, or tests, that our method should.
Calculator.cs
public static double Division(double num1, double num2)
{
if (num2 == 0)
{
// Refactored logic for Division by zero
return 0;
}
else
{
return num1 / num2;
}
}
And, since we now know for a Fact that all divisions by zero should return zero, then we can make that test a Fact:
CalculatorTests.cs
[Theory]
[InlineData(8, 4, 2)]
[InlineData(-9, 3, -3)]
public void Division_ShouldCalculateSimpleValues(double num1, double num2, double expected)
{
// Action
double actual = Calculator.Division(num1, num2);
// Assertion
Assert.Equal(expected, actual);
}
[Fact]
public void Division_ShouldDivideByZero()
{
// Instantiation
double expected = 0;
// Action
double actual = Calculator.Division(15, 0);
// Assertion
Assert.Equal(expected, actual);
}
If we build the solution (press F6), the tests in the xUnit Test window will update.
At this point, if we Run All the tests again, then all our tests should pass!
This is exactly what we are aiming for!
Debugging your tests
Now that we have several tests, there may be the need to debug a test to make sure that it is actually doing what it is expected to do. This is actually quite simple: First you need to add a breakpoint to your code. You can do that by clicking on the sidebar on the Code Editor window in the line you want to add the breakpoint to.
Then right-click on the test you want to debug, and then select Debug.
Now you can continue debugging your code as you would normally do.
Final notes
Keep in mind that:
You should develop your code to pass your tests, not develop your tests so that your code can pass your tests.
You should create tests for as many possible scenarios (Fact and Theory) as you can. This provides confidence that you will deploy better working code to production.
Your testing project (Calculator.Tests in this section) is not necessarily needed in a production environment.
xUnit Test projects are indeed projects that will assist you to develop better working code from the very beginning.
We encourage you to continue creating tests for this Calculator class to better grasp the concepts mentioned in this section.