MSTest is a framework that provides the methods and assertions for writing and executing unit tests in C#.
In C#, attributes are formalized bits of information about a program. They operate
somewhere between actual code syntax and a comment on the code. Attributes do not
directly affect the code they annotate, but they do supply information to the compiler.
An attribute is enclosed in square brackets,
, and placed above the item it decorates.
To run unit tests using the native tools available in Visual Studio, the attributes [TestClass] and [TestMethod] are used to indicate that certain classes and methods should be treated as test cases. We describe how to use these in the examples that follow.
To test a simple .NET Core console project, we add an MSTest project into the same solution. An MSTest project is a console project with an added MSTest dependency.
Turn your attention to the
reading examples repo
. Inside the solution, we have two projects,
Car project is a simple .NET Console app like the others you have encountered
in this course so far.
CarTests is a new type of project, MSTest Project.
On a Mac, to select this type of project looks like so:
On a Windows:
MSTest is a C# testing framework. When we create a Visual Studio MSTest Project, the
necessary API and classes are added as dependencies of the
CarTests project. A dependency
is a separately developed program or piece of code that another program or piece of code
uses to carry out its function. Our C# tests will depend on MSTest code.
Along the same lines, since
CarTests tests the methods inside of
Car, we must add the
Car project as a dependency of
Right click on the
Dependencies directory in
CarTests and add a project reference to
Car class and look around. Here, we provide a class,
Car, with basic
information about a make, model, gas level, and mileage. We also give it getters, setters, and a few other methods.
In the same project, the
Program class prints the
model of a given
Car object. Run the project to verify it works.
CarTests. It’s empty, save for a few TODOs. Let’s tackle the
first TODO to make a new empty test. Starting with an empty test lets us validate that we can
use MSTest in our current environment.
You may notice that we have instantiated a
Car object by calling it
Car.Car. This is because our namespace is also called
Car. If you remove the namespace name, you will get an error because a namespace cannot be instantiated as an object.
Another benefit of coding in an IDE, Visual Studio contains its own test runner. A test runner is
simply a tool to execute tests and deliver their results. In order to indicate that
unit tests that we want the test runner to run, we must give it the
[TestClass] attribute. As you might
[TestMethod] annotates a method to signal it as a test case. Both of these attributes come to us
via the Visual Studio test runner.
CarTests, on top of
public class CarTests, add
[TestClass]. Then, create the following empty
test underneath the first TODO. As usual, be sure write this code rather than copy/paste it:
Our empty test is aptly named
EmptyTest() as a description of its role. This test does
not follow the AAA rule from our testing best practices, as it jumps straight to
asserting. Nor is it relevant, for that matter. The goal of this empty unit test is not to
demonstrate all of our
, but rather, to verify that our testing setup is in place.
The three arguments in our test care defined as “expected”, “actual”, and “delta”. This empty test
asserts an expected value of
10 to equal an actual value of
with an accepted
The third argument, called
delta, is the amount of allowed difference between the
expected and actual values. If the difference between the two values is within
that range, then the test still passes.
This argument is optional for some comparisons and required for others. One
scenario in which it is required is when comparing doubles.
Why is it required? Well, that’s kind of a long story. Some number types are
Due to the nature of their storage, these types carry with them a certain
In brief, the
delta argument ensures we can still reasonably compare two doubles.
10. But let’s run it so
we know our test runner works.
Like running console projects, there are many ways to run unit tests and view the results. Here are some options to try:
Mac Users: Running Tests
For Mac users, run the
CarTests project just like you would any other project.
If the panel does not open once the test are finished running, look for the Test Results panel name on the margins of your IDE and open it manually.
Windows Users: Running Tests
For Windows users, you’ll want to find and open the Test Explorer panel. If you don’t already have it docked, you can find it listed in the top Test menu.
With the panel open, select the Run All Tests option.
If you see that the test fails to run, neither passing nor failing, you may need to adjust a setting to use 64bit processing.
You may also need to update some of the testing packages. Right click on the
CarTests project and select Manage NuGet Packages…. If you see some items
in the Update section of the panel that opens, run the updates. Close and reopen
the Team Explorer panel and Visual Studio to ensure the changes are applied.
All Users: Output and Adding More Tests
Once you run the test, you will see a new output panel with a green check mark indicating the test passed and a message stating the test passed.
We know now how the test runner behaves when a test passes and can begin the real work of unit
Car class. One responsibility of the
Car class constructor is to set its initial
gasTankLevel field. This field is determined by the constructor argument for
This class-specific behavior is a good item to test. Under your second TODO, write a test to verify that the
constructor sets the
To test the
Car class, we must make it available to us by adding
using Car; to the top of your
Car is the namespace we have assigned to the
Car class. Namespaces are used in C# to
organize code. You’ve seen them before in other using statements.
Here, we give the test a descriptive name,
TestInitialGasTank(), initialize a new
Car object, and test that the constructor correctly sets the
We’ve done our best to address testing best practices :
- We arrange the one variable our test requires:
- We act on the
Carconstructor method as well:
new Car("Toyota", "Prius", 10, 50);.
- We assert that the expected value of
10will equal the actual value returned from getting the tank level (
- We arrange the one variable our test requires:
As it is written, we expect that our test will always pass.
This is our first real test, so we don’t yet have much to group it with. That said, the test assesses a method in
Carand is situated in a class called
CarTests, so it meets the minimum requirements or relevancy. The next section gives us another attribute to use to help group testing variables.
Our test evaluates a simple field assignment but it is not trivial. The line in the constructor being tested is not very complex, but this makes for a good unit test. We want to make sure the basic functionality of our class works as we expect.
CarTest to see that both tests pass.
If you want to rerun only one test, right click on its listing in the results pane.
[TestMethod] are required to run tests, there are many other
attributes you may find useful as your test files grow in scope. One such item to know
is [TestInitialize]. Methods with the attribute
[TestInitialize] will run before each
test method is run in a class.
In the case of
CarTest, it would be nice to not need to create a new
Car instance for
each test we write. In your
TestInitialGasTank() method, remove the line initiating
Above your relevant test, add the following
Now, run the test project and ensure your test still passes.
[TestCleanup], conversely, defines a set of conditions to be met after each test in a
suite is run.
We won’t encounter a scenario where we ask you to use
[TestCleanup] in this class. As you explore writing
your own unit tests, you may find a yourself in a situation where you need or want it. One use case for
[TestCleanup] might be testing database transactions. You don’t want changes to a database to persist
after test execution, so you can use
[TestCleanup] to rollback, or reverse, a test transaction.
You can find more information on this attribute and other items available in the Visual Studio testing namespace here .
In addition to the very commonly used
you see above, here are a few other methods you should have in
your unit testing playbook.
|Asserts that two values, expected and actual, are equal to each other (optionally, within a given range of difference)|
|Asserts that a given condition is false|
|Asserts that a given condition is true|
|Asserts that a given object is not null|
Checkout the Assert class for a full listing of methods.
Check Your Understanding
Write another version of
IsFalse(), comparing the value to
Assert.IsFalse(Car.GasTankLevel == 0);
Assert.IsFalse(test_car.GasTankLevel == 0);
Assert.False(test_car.GasTankLevel == 0);
Assert.IsFalse(test_car.GasTankLevel = 0);
Write another version of
Assert.IsTrue(test_car.gasTankLevel == 10);
Assert.IsTrue(Car.GasTankLevel == 10);
Assert.IsTrue(test_car.GasTankLevel == 0);
Assert.IsTrue(test_car.GasTankLevel == 10);