Run Unit tests for .NET applications – Azure DevOps

In this article I will demonstrate how one can write and integrate Unit tests on .NET solutions and automate the testing procedure through Azure DevOps.

For my scenario I have a simple Console App .NET application inside a solution with the name ConsoleApp1. This solution includes also another project, the TestProject1 which is of type MSTest project.

My ConcoleApp only contains a simple function that adds two numbers. The code is shown below as well as the solution structure.

The unit test methods are also simple and will verify whether the add function works properly. The TestMethod1 will verify if the int input is correct, and the second one will fail as I try to evaluate a string with an int value.

In order to build the tests you should right click on TestProject1 and press run tests.

By checking the output we can verify that the UnitTest1 was successful in comparison with UnitTest2 which failed.

Lets now examine how to automate the procedure of the build and test using Azure DevOps. The first step is to upload the solution on a source control site and integrate Azure DevOps with it. I have already documented the procedure on a previous article build-pipeline-for-net-solutions-using-cake2-azure-devops

My pipeline contains two stages, one for the build of the project and one for the run of unit tests. The code is available below:

pool:
  vmImage: ubuntu-latest

trigger: none
pr: none 

stages:

  - stage:
    displayName: Build .NET application
    jobs:
      - job:
        displayName: build job
        steps:
          - task: UseDotNet@2
            displayName: Use .NET6 to build
            inputs:
              version: '6.0.x'
              includePreviewVersions: true
          - task: DotNetCoreCLI@2
            displayName: Building Visual studio solution
            inputs:
              command: 'build'
              projects: '**/*.csproj'
              arguments: '--configuration debug'

  - stage: 
    displayName: Run unit tests
    jobs:
      - job:
        displayName: unit tests job
        steps:
          - task: DotNetCoreCLI@2
            displayName: Run Visual studio tests
            inputs:
              command: test
              projects: '**/*Test*/*.csproj'
              arguments: '--configuration debug'

As you can verify the tests stage failed because of the unit test that was not successful.

Azure DevOps also can give you an overview of how many tests succeeded. In my case I had 50% success rate because 1 out of 2 tests failed.

By pressing the 50% tests button you can also get a detailed review of the tests.

In more detail the method that fails is TestMethod2 and the exact message points to the comparison between the int and string values.

Video tutorial on YouTube:

Azure DevOps best practices – jobs and stages

Azure DevOps stages and jobs on build pipelines could save you a lot of minutes when you deal with more complex setups and heavy projects. In this article I will analyze how you can use jobs and stages to maximize performance and parallelism.

Lets take the starter pipeline example on Azure DevOps (code below). Although this is perfect for a single task scenario, it will execute tasks on a row without parallelism. The output of this pipeline will be two printed messages on your debug window.

trigger:
- main

pool:
  vmImage: ubuntu-latest

steps:
- script: echo Hello, world!
  displayName: 'Run a one-line script'

- script: |
    echo Add other tasks to build, test, and deploy your project.
    echo See https://aka.ms/yaml
  displayName: 'Run a multi-line script'

The two tasks that are included (script task) are located under steps. Those steps are part of a job that is not present as it is the only one.

The hierarchy of a pipeline can include the below: Stages -> Jobs -> Steps -> Tasks

On the starter pipeline two tasks are included under steps that belong on a single job. Jobs big advantage is that they can run in parallel. Take for example a big pipeline that includes a lot of jobs 30 and more. It would be time killing to wait all these jobs execute one by one. Keep in mind that one job can also fail and you will have to start the process again.

If you have a single job and all tasks are included on it, you have to use continueOnError if you do not want to stop the pipeline on a task failure.

The below example shows two jobs that can be executed on parallel based on your Azure DevOps configuration (this may include more costs on your subscription). As you can see the failure of the first job will not affect the second job that will be executed. If you are also eligible for parallel jobs, these can run simultaneously if you do not have constraints between them.

pool:
  vmImage: ubuntu-latest

trigger: none
pr: none  

jobs:
  - job:
    displayName: Install npm and yarn tools 
    steps:
      - task: Npm@1
        displayName: Install npm
        inputs:
          command: 'install'
          workingDir: '$(Pipeline.Workspace)/s'

      - task: PowerShell@2
        displayName: Install yarn
        inputs:
          targetType: 'inline'
          script: |
            npm install -g yarn

  - job:
    displayName: build code and publish artifact
    steps:

      - task: PowerShell@2
        displayName: build code
        inputs:
          targetType: 'inline'
          script: |
            Write-Host "this task will be executed"
      - task: PowerShell@2
        displayName: this task will fail
        inputs:
          targetType: 'inline'
          script: |
            fail

Lets now examine the power of stages. The stage includes multiple jobs as you can see from the example below. A typical production environment will include stages for QA -> DEV -> Production deployments.

This approach big advantage is that you can rerun failed jobs separately and also rerun the whole stage in separation from each other. As a typical build pipeline may take a lot of minutes to complete by using stages you do not have to rerun the whole pipeline on a task failure.

trigger:
- none

pool:
  vmImage: ubuntu-latest

stages:
- stage: BuildApp
  displayName: Build Apps
  jobs:
  - job: BuildFrontendApp
    displayName: Build Frontend App
    steps:
    - script: echo building frontend app
      displayName: build frontend app
    - script: echo running unit tests for frontend app
      displayName: unit tests frontend

  - job: BuildBackendApp
    displayName: Build Backend App
    steps:
    - script: echo building backend app
      displayName: build backend app
    - script: echo running unit tests for backend app
      displayName: unit tests backend

- stage: DeployDev
  displayName: Deploy to DEV environment 
  jobs:
  - job: DeployFrontendDev
    displayName: Deploy frontend to DEV
    steps:
    - checkout: none
    - script: echo deploying frontend app to DEV
      displayName: deploy frontend app to DEV

  - job: DeployBackendDev
    displayName: Deploy backend to DEV
    steps:
    - checkout: none
    - script: echo deploying backend app to DEV
      displayName: deploy backend app to DEV

- stage: DeployProd
  displayName: Deploy to PROD environment
  jobs:
  - job: Failjob
    displayName: Running this job will fail
    steps:
    - checkout: none
    - script: kati
      displayName: deploy frontend app to PROD

  - job: DeployBackendProd
    displayName: Deploy backend to PROD
    steps:
    - checkout: none
    - script: echo deploying backend app to PROD
      displayName: deploy backend app to PROD

https://docs.microsoft.com/en-us/azure/devops/pipelines/licensing/concurrent-jobs?view=azure-devops&tabs=ms-hosted

Video tutorial on YouTube:

Automatically update your GitHub repositories with a powershell script

Developers often have a lot of repositories stored on their local machines. These repositories get updated from other developers and they stay outdated. In many cases developers forget to fetch and pull the latest changes on those repositories and when they commit code, the IDE will notify of the new changes. When this is the case, the commit will get an non explanatory message as the latest of the commit and you will have to navigate on the actual commit to verify the changes and commit message.

Commit message
Merge branch test/v3.0.0 of https://github.com/org/repository

In order to resolve this issue, you can create a powershell script that can automatically fetch the latest changes of your local repositories. You will need to change your repositories base location.

#change your github location
$github_directory = "C:\Users\galexiou\Documents\GitHub"
Get-ChildItem $github_directory | ForEach-Object {
if($_.Attributes -eq "Directory")
{
Write-Host $_.FullName
Set-Location $_.FullName
git fetch
git pull
}
}

As you can see from the output below this script will go and fetch the latest changes on the repositories that have been updated.

You can also create a cron job or an automated windows task in order to run this job automatically on computer startup or on your work schedule start. For example

On task scheduler press create task

and select your triggers (when this task will run) along with the action. This will be the run of the powershell script. On the argument you must specify the -File location (where you stored your powershell script).

Build pipeline on tag push – Azure DevOps build triggers

There are multiple ways to define your continuous integration trigger on a pipeline depending on your needs. One common approach is to trigger a build whenever a new merge or push is done on your branch.

For example with the below notation you could trigger a new build every time a new push is merged on the uat branch.

trigger:
- uat

Another approach could be the pull request. Every time a new pull request is created for a specific branch your build could be initiated. In order to accomplish that you should use the pr keyword.

The below example will trigger when a new pull request is created and the merge destination is main branch. This approach could help you identify if the code of a specific feature/branch actually builds and can be merged on your main branch.

pr:
  branches:
    include:
    - main

Another approach is the tags functionality. You could run a build only if a specific tag is pushed along with the commit.

The below example will only build when the tag release.* is pushed on the branch on which the pipeline is located.

trigger:
  tags:
    include:
    - release.*

Some tags that could trigger my build are: release.v1 , release.master, release.v2

In order to push a tag on your branch using cmd you should

git add .
git commit -m "commit message"
git tag mytag
git push --tags

Then on tags section of your repository you can locate your new tag.

Documentation of triggers for Azure pipelines:

https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/trigger?view=azure-pipelines

Video tutorial on YouTube: