Organizing and testing projects with the .NET CLI
This tutorial follows Tutorial: Create a console application with .NET using Visual Studio Code, taking you beyond the creation of a simple console app to develop advanced and well-organized applications. After showing you how to use folders to organize your code, the tutorial shows you how to extend a console application with the xUnit testing framework.
Note
This tutorial recommends that you place the application project and test project in separate folders. Some developers prefer to keep these projects in the same folder. For more information, see GitHub issue dotnet/docs #26395.
Using folders to organize code
If you want to introduce new types into a console app, you can do so by adding files containing the types to the app. For example, if you add files containing AccountInformation
and MonthlyReportRecords
types to your project, the project file structure is flat and easy to navigate:
/MyProject
|__AccountInformation.cs
|__MonthlyReportRecords.cs
|__MyProject.csproj
|__Program.cs
However, this flat structure only works well when the size of your project is relatively small. Can you imagine what will happen if you add 20 types to the project? The project definitely wouldn't be easy to navigate and maintain with that many files littering the project's root directory.
To organize the project, create a new folder and name it Models to hold the type files. Place the type files into the Models folder:
/MyProject
|__/Models
|__AccountInformation.cs
|__MonthlyReportRecords.cs
|__MyProject.csproj
|__Program.cs
Projects that logically group files into folders are easy to navigate and maintain. In the next section, you create a more complex sample with folders and unit testing.
Organizing and testing using the NewTypes Pets Sample
Prerequisites
- .NET 5.0 SDK or a later version.
Building the sample
For the following steps, you can either follow along using the NewTypes Pets Sample or create your own files and folders. The types are logically organized into a folder structure that permits the addition of more types later, and tests are also logically placed in folders permitting the addition of more tests later.
The sample contains two types, Dog
and Cat
, and has them implement a common interface, IPet
. For the NewTypes
project, your goal is to organize the pet-related types into a Pets folder. If another set of types is added later, WildAnimals for example, they're placed in the NewTypes folder alongside the Pets folder. The WildAnimals folder may contain types for animals that aren't pets, such as Squirrel
and Rabbit
types. In this way as types are added, the project remains well organized.
Create the following folder structure with file content indicated:
/NewTypes
|__/src
|__/NewTypes
|__/Pets
|__Dog.cs
|__Cat.cs
|__IPet.cs
|__Program.cs
|__NewTypes.csproj
IPet.cs:
using System;
namespace Pets
{
public interface IPet
{
string TalkToOwner();
}
}
Dog.cs:
using System;
namespace Pets
{
public class Dog : IPet
{
public string TalkToOwner() => "Woof!";
}
}
Cat.cs:
using System;
namespace Pets
{
public class Cat : IPet
{
public string TalkToOwner() => "Meow!";
}
}
Program.cs:
using System;
using Pets;
using System.Collections.Generic;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
List<IPet> pets = new List<IPet>
{
new Dog(),
new Cat()
};
foreach (var pet in pets)
{
Console.WriteLine(pet.TalkToOwner());
}
}
}
}
NewTypes.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Execute the following command:
dotnet run
Obtain the following output:
Woof!
Meow!
Optional exercise: You can add a new pet type, such as a Bird
, by extending this project. Make the bird's TalkToOwner
method give a Tweet!
to the owner. Run the app again. The output will include Tweet!
Testing the sample
The NewTypes
project is in place, and you've organized it by keeping the pets-related types in a folder. Next, create your test project and start writing tests with the xUnit test framework. Unit testing allows you to automatically check the behavior of your pet types to confirm that they're operating properly.
Navigate back to the src folder and create a test folder with a NewTypesTests folder within it. At a command prompt from the NewTypesTests folder, execute dotnet new xunit
. This command produces two files: NewTypesTests.csproj and UnitTest1.cs.
The test project can't currently test the types in NewTypes
and requires a project reference to the NewTypes
project. To add a project reference, use the dotnet add reference
command:
dotnet add reference ../../src/NewTypes/NewTypes.csproj
Or, you also have the option of manually adding the project reference by adding an <ItemGroup>
node to the NewTypesTests.csproj file:
<ItemGroup>
<ProjectReference Include="../../src/NewTypes/NewTypes.csproj" />
</ItemGroup>
NewTypesTests.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../src/NewTypes/NewTypes.csproj"/>
</ItemGroup>
</Project>
The NewTypesTests.csproj file contains the following package references:
Microsoft.NET.Test.Sdk
, the .NET testing infrastructurexunit
, the xUnit testing frameworkxunit.runner.visualstudio
, the test runnerNewTypes
, the code to test
Change the name of UnitTest1.cs to PetTests.cs and replace the code in the file with the following code:
using System;
using Xunit;
using Pets;
public class PetTests
{
[Fact]
public void DogTalkToOwnerReturnsWoof()
{
string expected = "Woof!";
string actual = new Dog().TalkToOwner();
Assert.NotEqual(expected, actual);
}
[Fact]
public void CatTalkToOwnerReturnsMeow()
{
string expected = "Meow!";
string actual = new Cat().TalkToOwner();
Assert.NotEqual(expected, actual);
}
}
Optional exercise: If you added a Bird
type earlier that yields a Tweet!
to the owner, add a test method to the PetTests.cs file, BirdTalkToOwnerReturnsTweet
, to check that the TalkToOwner
method works correctly for the Bird
type.
Note
Although you expect that the expected
and actual
values are equal, an initial assertion with the Assert.NotEqual
check specifies that these values are not equal. Always initially create a test to fail in order to check the logic of the test. After you confirm that the test fails, adjust the assertion to allow the test to pass.
The following shows the complete project structure:
/NewTypes
|__/src
|__/NewTypes
|__/Pets
|__Dog.cs
|__Cat.cs
|__IPet.cs
|__Program.cs
|__NewTypes.csproj
|__/test
|__NewTypesTests
|__PetTests.cs
|__NewTypesTests.csproj
Start in the test/NewTypesTests directory. Run the tests with the dotnet test
command. This command starts the test runner specified in the project file.
As expected, testing fails, and the console displays the following output:
Test run for C:\Source\dotnet\docs\samples\snippets\core\tutorials\testing-with-cli\csharp\test\NewTypesTests\bin\Debug\net5.0\NewTypesTests.dll (.NETCoreApp,Version=v5.0)
Microsoft (R) Test Execution Command Line Tool Version 16.8.1
Copyright (c) Microsoft Corporation. All rights reserved.
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.50] PetTests.DogTalkToOwnerReturnsWoof [FAIL]
Failed PetTests.DogTalkToOwnerReturnsWoof [6 ms]
Error Message:
Assert.NotEqual() Failure
Expected: Not "Woof!"
Actual: "Woof!"
Stack Trace:
at PetTests.DogTalkToOwnerReturnsWoof() in C:\Source\dotnet\docs\samples\snippets\core\tutorials\testing-with-cli\csharp\test\NewTypesTests\PetTests.cs:line 13
Failed! - Failed: 1, Passed: 1, Skipped: 0, Total: 2, Duration: 8 ms - NewTypesTests.dll (net5.0)
Change the assertions of your tests from Assert.NotEqual
to Assert.Equal
:
using System;
using Xunit;
using Pets;
public class PetTests
{
[Fact]
public void DogTalkToOwnerReturnsWoof()
{
string expected = "Woof!";
string actual = new Dog().TalkToOwner();
Assert.Equal(expected, actual);
}
[Fact]
public void CatTalkToOwnerReturnsMeow()
{
string expected = "Meow!";
string actual = new Cat().TalkToOwner();
Assert.Equal(expected, actual);
}
}
Rerun the tests with the dotnet test
command and obtain the following output:
Test run for C:\Source\dotnet\docs\samples\snippets\core\tutorials\testing-with-cli\csharp\test\NewTypesTests\bin\Debug\net5.0\NewTypesTests.dll (.NETCoreApp,Version=v5.0)
Microsoft (R) Test Execution Command Line Tool Version 16.8.1
Copyright (c) Microsoft Corporation. All rights reserved.
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
Passed! - Failed: 0, Passed: 2, Skipped: 0, Total: 2, Duration: 2 ms - NewTypesTests.dll (net5.0)
Testing passes. The pet types' methods return the correct values when talking to the owner.
You've learned techniques for organizing and testing projects using xUnit. Go forward with these techniques applying them in your own projects. Happy coding!