Lesson Sunday

Now that we have a basic understanding of Test-Driven Development, including how it works in conjunction with automated testing, let's revisit the leap year application we started in the pre-work. We will write our specs and translate them to unit tests, which we'll run using a tool called MSTest.

MSTest


MSTest is a unit testing framework that allows us to set up test cases. We can tell our test to expect a specific result and then our test will let us know whether that expected result is achieved or not. Good unit tests should cover not just expected inputs but also edge cases. Edge cases can occur when an extreme or unusual parameter is passed into a method. For example, a method that calculates a person's overall health partly based on their age may not correctly account for a person that is 117 years old.

Setting Up a Project to Use MSTest


Let's walk through how to set up MSTest in our own C# projects.

Directory Structure Setup

  • Create a new directory called Calendar.Solution.
  • In the Calendar.Solution directory create two additional subdirectories:
    • Calendar
    • Calendar.Tests

The directory structure will look like this:

Calendar.Solution
├── Calendar
└── Calendar.Tests
  • Calendar.Solution is the parent directory where all our code lives. It actually contains two versions of the same project:
    • Calendar will hold our application logic. This is sometimes referred to as the production project because it contains the code that will actually run when we launch the program.
    • Calendar.Tests will hold our automated tests. It's the test version of our project. We should always append a .Tests suffix to this directory.

.csproj Files

Next we need special .csproj files.

Production

Create a file in the Calendar.Solution/Calendar directory called Calendar.csproj. The resulting structure will look like this:

Calendar.Solution
├── Calendar
│   └── Calendar.csproj
└── Calendar.Tests

Place the standard boilerplate in the new Calendar.csproj file:

Calendar.Solution/Calendar/Calendar.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

</Project>

Note that this doesn't have the output type in the Property Group that we would include in a project with a front end, since this example is to show the setup for a project purely for the purposes of testing. As such, this will throw an error if you try to include a front end and run dotnet run, since there is no executable file for it to run. If you want to create an executable program with a user interface, you will need to add <OutputType>Exe</OutputType> to the <PropertyGroup> element as in previous projects.

Tests

Our production project and test project are technically two separate projects, which means that each needs its own .csproj file.

Let's add a Calendar.Tests.csproj file to the Calendar.Tests subdirectory. The resulting file structure will look like this:

Calendar.Solution
├── Calendar
│   └── Calendar.csproj
└── Calendar.Tests
    └── Calendar.Tests.csproj

Add the following code to Calendar.Tests.csproj:

Calendar.Solution/Calendar.Tests/Calendar.Tests.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
    <PackageReference Include="MSTest.TestAdapter" Version="1.3.2" />
    <PackageReference Include="MSTest.TestFramework" Version="1.3.2" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\Calendar\Calendar.csproj" />  
  </ItemGroup>

</Project>

This .csproj file looks very different from the boilerplate .csproj files we've used so far. This is because a .csproj file is used to import necessary external packages. In this case, we need packages to run our tests.

  • In the <ItemGroup> area, we import three packages, each in special <PackageReference> tags:
    • Microsoft.NET.Test.Sdk imports functionality to set up and build our testing environment;
    • MSTest.TestAdapter will run our tests for us;
    • MSTest.TestFramework allows us to create test classes and methods and to use other built-in code to construct tests.
  • In the second <ItemGroup> element, our test project references our main production project. This allows our tests to locate and use the namespaces of our production project without interfering with it.

Acquiring and Installing Packages

Now that our .csproj files are complete, we need to add these tools to our project. Navigate into the Calendar.Tests folder in the command line and run the following:

$ dotnet restore

This command tells NuGet, a free open source package manager that comes with .NET, to retrieve and install the packages we listed in .csproj in our application. It's much easier than manually locating and installing packages like we did in Intro!

This command automatically creates new obj directories in our Calendar and Calendar.Tests project, each containing multiple files, looking something like this:

Calendar.Solution
├── Calendar
│   ├── Calendar.csproj
│   └── obj
│       ├── Calendar.csproj.nuget.cache
│       ├── Calendar.csproj.nuget.g.props
│       ├── Calendar.csproj.nuget.g.targets
│       └── project.assets.json
└── Calendar.Tests
    ├── Calendar.Tests.csproj
    └── obj
        ├── Calendar.Tests.csproj.nuget.cache
        ├── Calendar.Tests.csproj.nuget.g.props
        ├── Calendar.Tests.csproj.nuget.g.targets
        └── project.assets.json

These files contain detailed references and configuration instructions that our program will reference later on. Depending on your machine, you may have differently named files in this folder. They contain special internal code and we won't need to interact with these files for the most part. For now, simply leave them alone. If these files are accidentally deleted or modified, they can be restored with $ dotnet restore command.

Note on restore Commands

Keep in mind that anytime we update a .csproj file, we need to run > dotnet restore to download and install updated packages.

It's also essential to ensure that we run console commands within the correct project. In this case, we need to be running the command in our Calendar.Tests folder. If our terminal is located in the root directory of the project, we can run commands that specify an individual project like this:

$ dotnet restore Calendar.Tests

Now that our project directory and necessary packages are in place, let's start building our tests.

Terminology


  • MSTest: A unit testing framework that allows us to set up test cases.

  • NuGet: Free open source package manager that comes with .NET.

  • Unit test: A test to verify a single behavior.

  • Edge case: When an extreme or unusual parameter is passed into a method.

Setting Up Tests

csproj file for tests:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
    <PackageReference Include="MSTest.TestAdapter" Version="1.3.2" />
    <PackageReference Include="MSTest.TestFramework" Version="1.3.2" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\ProjectName\ProjectName.csproj" />  
  </ItemGroup>

</Project>

After .csproj file is added, run the following in test directory:

$ dotnet restore

Directory structure for a project with tests. Note that all files in obj folders are generated by .NET.

ProjectName.Solution
├── ProjectName
│   ├── ProjectName.csproj
│   └── obj
│       ├── ProjectName.csproj.nuget.cache
│       ├── ProjectName.csproj.nuget.g.props
│       ├── ProjectName.csproj.nuget.g.targets
│       └── project.assets.json
└── ProjectName.Tests
    ├── ProjectName.Tests.csproj
    └── obj
        ├── ProjectName.Tests.csproj.nuget.cache
        ├── ProjectName.Tests.csproj.nuget.g.props
        ├── ProjectName.Tests.csproj.nuget.g.targets
        └── project.assets.json

Lesson 5 of 20
Last updated April 14, 2022