Orchestrating Visual Studio Code : Part 5 : Unit Testing

In this section, we will be automating unit tests with Visual Studio Code tasks.


XUnit

xUnit is a lightweight test framework that is very popular in the .NET community. It supports extensions for BDD with the xBehave library that can be very helpful in descibing tests with your Product Team as well as increased readability via syntax similar to Gherkin notation.


GitLens

GitLense is a Visual Studio Code plugin that gives insights into your underlying git repository (similar to CodeLens) as well as providing test run/debug links throughtout your code.

Your First Test

We will be creating a test project, adding the xUnit and xBehave NuGet packages, and wiring it up to a VSCode task so that we can run it from the IDE.

The Test Project

We are going to add a new top-level folder /test/UnitTests to hold your solution's tests. Navigate your shell to that folder and create a project.

mkdir -p test/UnitTests
cd test/UnitTests
dotnet new classlib

We need to switch the project from netstandard2.0 to netcoreapp2.0 to support the xunit dotnet cli integration. Change the following in UnitTests.csproj:

<PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>

Make sure to add the new test project to our solution. Run the following from the root of the project:

dotnet sln add test/UnitTests/UnitTests.csproj

Adding Test Packages

Within the ./test/UnitTests directoy, we will add xUnit...

dotnet add package xunit --version 2.3.1
dotnet add package xunit.runner.visualstudio --version 2.3.1

... and then add the xunit cli tool to the UnitTests.csproj:

<ItemGroup>
    <DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
</ItemGroup>

The OmniSharp extension we loaded earlier will prompt to Restore Packages. This will pull the binaries down from NuGet into your local test project.


Creating a Smoke Test

We are going to create a simple smoke test for this example:

└── test
    └── UnitTests
        ├── SmokeTest.cs
        ├── UnitTests.csproj
using Xunit;

namespace UnitTests
{
    public class SmokeTest
    {
        [Fact]
        public void CanAssertTrue()
        {
            Assert.True(true, "Our first test!");
        }
    }
}
TODO

From time to time, OmniSharp will hang when loading new packages. If you aren't seeing intellisense, press F1 and select Reload Window. This will reset the analysis engine.


The Unit Test Task

We are now going to add a new tasks to our .vcode/tasks.json file:

{
    "label": "unit-tests",
    "type": "shell",
    "group": {
        "kind": "test",
        "isDefault": true
    },
    "osx": {
        "command": "bash ./scripts/project-tasks.sh unitTests"
    },
    "presentation": {
        "echo": true,
        "reveal": "always",
        "focus": true,
        "panel": "dedicated"
    },
    "problemMatcher": [],
    "windows": {
        "command": ".\\scripts\\project-tasks.ps1 -UnitTests"
    }
}
tasks.json

The Unit Test Script

The task will call the unitTests() method in the scripts/project-tasks.sh file which iterates over the projects under the /test directory and executes dotnet test for folders with UnitTests in their name.

# #############################################################################
# Runs the unit tests.
#
unitTests () {

    echo -e "${GREEN}"
    echo -e "++++++++++++++++++++++++++++++++++++++++++++++++"
    echo -e "+ Running unit tests                            "
    echo -e "++++++++++++++++++++++++++++++++++++++++++++++++"
    echo -e "${RESTORE}"

    for dir in test/*UnitTests*/ ; do
        [ -e "$dir" ] || continue
        dir=${dir%*/}
        echo -e "Found tests in: test/${dir##*/}"
        cd $dir
        dotnet test 
        rtn=$?
        if [ "$rtn" != "0" ]; then            
            echo -e "${RED}An error occurred${RESTORE}"
            exit $rtn
        fi
    done

}
project-tasks.sh


Running Your Tests

At this point, press F1 and run the task unit-tests. You should see the following output:

++++++++++++++++++++++++++++++++++++++++++++++++
+ Running unit tests
++++++++++++++++++++++++++++++++++++++++++++++++

Found: test/UnitTests
Build started, please wait...
Build completed.

Test run for /VSCode-Orchestration/test/UnitTests/bin/Debug/netcoreapp2.0/UnitTests.dll(.NETCoreApp,Version=v2.0)
Microsoft (R) Test Execution Command Line Tool Version 15.6.0-preview-20180109-01
Copyright (c) Microsoft Corporation.  All rights reserved.

Starting test execution, please wait...
[xUnit.net 00:00:00.6418600]   Discovering: UnitTests
[xUnit.net 00:00:00.7619690]   Discovered:  UnitTests
[xUnit.net 00:00:00.7693280]   Starting:    UnitTests
[xUnit.net 00:00:00.9738810]   Finished:    UnitTests

Total tests: 1. Passed: 1. Failed: 0. Skipped: 0.
Test Run Successful.
Test execution time: 2.0896 Seconds

Debugging Your Test

Installing GitLense

We will now load the GitLense extension to allow running and debugging individual tests. Open the extensions menu on the left panel and search for GitLense to install it.

Running and Debugging a single test

Once GitLense is loaded, you will see gray annotations above your classes and methods. If the method is a [Test], [Fact], [Scenario], or any other attribute designating a testable mehtod, you will see two options, run test and debug test.

GitLense also provides insights into your git repository blame logs. You can see what time and which team member last updated each line of code. This is very simlar to Visual Studio's CodeLense feature (but free!).


Conclusion

We learned how to wire-up multiple unit test projects to run within VS Code.


The Source Code

You can find the source code for this article in the following repository under the part-5-unit-testing branch:

https://github.com/christophla/VSCode-Orchestration/tree/part-5-unit-testing


Next Post : Code Coverage

In the next post we will learn how to add code coverage reports to our test runs with gulp and lcov.


Let's add code coverage!

Previous Post : Running Docker Containers

https://christophertown.com/orchestrating-vscode-running-docker-containers