Index
In this post we'll take a quick look at what tests are, beginning with unit tests.
But before diving into one specific type of test, we need to understand what tests are and why they are useful.
1 - What are tests
First, we need to be clear about the importance that tests provide to our software, because writing tests allows us to ensure that our code works, using different input elements and under different circumstances.
1.1 - Different types of tests
When we talk about testing, it is often represented visually as a pyramid:
This pyramid can change, but to simplify, I have divided it into three sections, which are the most common areas to test:
First, at the bottom, we have:
- Unit tests, which test specific methods or services individually.
- This is the type of test we will write most frequently, and the focus of this post.
- Integration tests, which test different integrations in the system, "semi-complete" processes, but always simulating calls to external applications or databases.
- We will cover this type of test later. An example might be the process of creating a user in a service, but not saving that user to the database.
- End to end tests, which check a complete process, in which we do not simulate calls to other services or databases.
- For example, as with the previous case, creating a user, but this time the user is actually created in the database.
A pyramid is used to indicate that there are more tests at the bottom than at the top. The same applies for finding and fixing bugs: it's easier to fix a bug found during a unit test than one found in integration or end to end tests.
Ideally, all these tests should be automated, and this can be done quite easily. However, it's common for companies to run end-to-end tests manually.
2 - Why write tests?
As I mentioned above, writing tests offers several advantages, including:
- Knowing that your code works.
- You should write tests for all or most possible cases, to ensure that piece of code is bug free.
- Improving code quality, by ensuring that if you have a passing test and you change something in the code which then stops working, the test will fail.
- Better error control, allowing you to filter them before they happen or capture and throw appropriate exceptions. Remember there is a post about how to capture exceptions.
3 - How to create unit tests in C#
It is worth noting that you can create unit tests anywhere in your code, but to keep things organized, it is good practice to create a new project dedicated exclusively to tests within your solution.
To do this in Visual Studio, click File -> New -> Project
and select a test project.
As you can see, there are three types of tests for .NET Core
- MsTest
- NUnit
- xUnit
All three frameworks are very similar, with differences in some settings, etc., which will be explained in their own posts.
Select MsTest Test Project
and you'll see that Visual Studio creates a new project, which contains a single class, the one that contains your test.
Note: For the example I created a small library in C# which is a calculator that simply adds, subtracts, multiplies and divides. You can see a more complete test example in the CSV library I have on Github.
So, the class we are going to test is the following:
public static class CalculadoraEjemplo
{
public static int Suma(int item1, int item2)
{
return item1 + item2;
}
public static int Resta (int item1, int item2)
{
return item1 - item2;
}
public static int Multipliacion (int item1, int item2)
{
return item1 * item2;
}
public static double Division (int item1, int item2)
{
return item1 / item2;
}
}
And the class containing the tests, which is created automatically when you create the project, is the following:
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
}
}
As you can see, by default it creates a class that will execute a test, which is indicated by the [TestClass]
attribute, and the method that will execute the test has a [TestMethod]
attribute.
3.1 - Add the reference of the project to be tested
When you want to test a project or library, you need to add the reference in your test project's csproj file. In this case, the following lines:
<ItemGroup>
<ProjectReference Include="..\Calculadora\Calculadora.csproj" />
</ItemGroup>
Another way is to right-click on Dependencies in the test project and select Add Reference
.
4 - Creating a unit test in C#
When creating a test, you should keep in mind a certain structure so that your tests are easy for other developers to understand.
By convention, there are some "best practices" for tests known as AAA
, which stands for:
- Arrange -> Initialize variables
- Act -> Call the method being tested
- Assert -> Check the result
And when we write the test, you can see the example:
[TestMethod]
public void Test_Calcular_Suma()
{
//Arrange : Initialize variables
int sumando1 = 2;
int sumando2 = 3;
//Act : call the method to test
int resultado = CalculadoraEjemplo.Suma(sumando1, sumando2);
//Assert: check the value against the expected
Assert.Equals(5, resultado);
}
As you can see, first we assign values, then call the sum method, and finally, check that the result is correct.
Test functionality goes beyond checking numbers. Assert
can take any type of data, and there are many options, for example .Null
to check if an item is null
, or .True
to check if it is true.
4.1 - Checking for an exception in a test
You can also check for exceptions. The way you do it varies depending on whether you're using MsTest, NUnit, or xUnit
. -If you don't remember what an exception is, here is a link-
With MsTest
you use the [ExpectedException]
attribute on the method, which will capture and check for the exception, and in this case, you won't need an Assert.
[TestMethod]
[ExpectedException(typeof(DivideByZeroException))]
public void Test_Calcular_Division()
{
//Arrange : Initialize variables
int dividendo = 120;
int divisor = 0;
//Act : call the method to test
double resultado = CalculadoraEjemplo.Division(dividendo, divisor);
//Assert: check the value against the expected
Assert.Equals(24, resultado);
}
5 - Code coverage
Finally, let's look at what code coverage is. When programming, we always wonder if all our code is covered by tests.
If we cover our code with tests, we can know, at least, that the normal course of execution will happen with no errors, although there may still be exceptions, which is why they are called exceptions.
Visual Studio contains a code coverage tool, but unfortunately only in the Enterprise version. There are other alternatives, but all of them are paid tools, so for now we will not see the results.
Before finishing with code coverage, don't become obsessed with 100% coverage. Especially with integration tests, if the logic is very complex, it can take a lot of time to cover 100% of the code, so in such cases it's better to spend 8 hours covering 70% than spend 40 hours reaching 100%, although ideally that remaining 30% should be covered with small unit tests.
Conclusion:
Creating tests is necessary in development, because it not only lets us check that the code we've written is correct, but also highlights if another functionality is broken. If so, the tests will fail, letting us know that feature stopped working.
In large projects, they are also very helpful, as developing by using tests is much easier than manually testing everything. Obviously this doesn't apply for user interface code, but in other cases, writing the test that implements our functionality is very useful. This kind of programming is called Test Driven Design
.
The fact that a company uses tests is a sign of professionalism, so you can trust companies with tests in place.
In another, more advanced post, we will see how to use mock
to simulate calls to a database or to different services.
If there is any problem you can add a comment bellow or contact me in the website's contact form