Menu

Continuous integration for a .NET Core 3.0 project using TravisCI

Continuous integration for a .NET Core 3.0 project using TravisCI

This last weekeend I decided that I wanted to properly start my scraper-checker-downloader project that I’ve been doing in different repositories.

After starting yet another project, this had to be cool, like real cool, using CI/CD, pull request, documentation, badges in readme, everything that I’ve seen that it’s cool and indeed are the best practices.

And after a good weekend I ended up creating Dramarr, a set of tools that scrap and download shows from different sources.

It has different repositories in the organization and most of them are libraries that are being compiled, tested and deployed themselves in the pull request and when they’re merged in the master branch.

That is what it’s called CI/CD or continuous intregration/continuous delivery.

But for this tutorial we are just going to talk about CI.

Continuous integration
What is it?

Taken from Martin Fowler’s blog, which is the best explanation that I’ve read:

Continuous Integration is a software development practice where members of a team integrate their work frequently, usually each person integrates at least daily - leading to multiple integrations per day. Each integration is verified by an automated build (including test) to detect integration errors as quickly as possible.

Tools

There are many tools to integrate your workflow with CI/CD, but for this tutorial we’ll be using Github to store our code and the TravisCI tools to setup the CI. Regarding the language and frameworks, we will use C# and the new.NET Core 3.0.

Requirements

In order to make this work you need three simple things:

  1. Latest version of Visual Studio 2019
  2. Github account
  3. Travis-CI account linked to your Github account

Project

For the sake of this tutorial, we will be doing a simple Calculator. We will be creating a library, a command line tool and a testing project to test everything.

This testing project will also be running when we set up the CI, meaning that if in the future we make a change to the code and the tests that we initially created are not passing, we will be getting a notification or we could simply reject the pull request.

Creating the Github repository

First, we will be creating a Github repository, so reach over to Github and create the repository and the clone it to your local environment. I decided to call this new repository CalculatorCLI-demo.

Creating the solution

Now let’s create an empty solution called CalculatorCLI, in the root folder of the cloned repository

Core library

As it would be in a real world project, we will be storing our logic in a separate project that generates a library, so let’s create it.

Go and create a Class Library (.NET Standard) and name it CalculatorCLI.Core

NET Core version

As soon as you create the project, go to the project properties and change the Target framework to .NET Standard 2.1, in order to make it compatible with projects built in .NET Core 3.0.

Code

For the sake of the tutorial, let’s make a simple class that handles operations.

 

using System; 
namespace ConsoleCalculator.Core 
{ 
   public enum OperatorsEnum 
   { 
       ADD, 
       SUBSTRACT, 
       MULTIPLY, 
       DIVIDE 
    } 

    public class Operation 
    { 
        public OperatorsEnum OperatorEnum { get; set; } 
        public int LeftValue { get; set; } 
        public int RightValue { get; set; } 

        public Operation(string operatorString, int leftValue, int rightValue) 
        { 
          switch (operatorString)
          { 
               case "+":
                    OperatorEnum = OperatorsEnum.ADD;
                    break; 
               case "-": OperatorEnum = OperatorsEnum.SUBSTRACT;
               break; 
               case "*": 
                   OperatorEnum = OperatorsEnum.MULTIPLY; 
                   break; 
               case "/": 
                   OperatorEnum = OperatorsEnum.DIVIDE;
                   break;
               default: 
                   throw new Exception("Operator invalid");
          } 

          LeftValue = leftValue; 
          RightValue = rightValue; 
    } 

    public int DoOperation()
    { 

        switch (OperatorEnum)
         { 
            case OperatorsEnum.ADD:
                return LeftValue + RightValue;
            case OperatorsEnum.SUBSTRACT: 
                return LeftValue - RightValue;
            case OperatorsEnum.MULTIPLY: 
                return LeftValue * RightValue; 
            case OperatorsEnum.DIVIDE:
                return LeftValue / RightValue; 
            default: throw new Exception("Operator is not valid"); 
          } 
      } 
   } 
}

 

CLI

Now that we have the core project, let’s create the application. In this case it will be a simple console application that accepts arguments and it shows a ouputs a result.

So let’s go ahead and create a new Console App (.NET Core), I named it CalculatorCLI.CLI.

NET Core version

As we did before, as soon as you create the project, go to the project properties and change the Target framework to .NET Core 3.0, if it’s not like that already.

Then add the reference to the ConsoleCLI.Core to our newly created project.

Code

Now for the code, this is simpler than before.

 

using ConsoleCalculator.Core;
using System; 
using System.Text.RegularExpressions; 

namespace ConsoleCalculator.CLI 
{ 
    public class Program
    { 
       public static void Main(string[] args)
       { 
           if (args.Length == 0)
           { 
               PrintUsage(); 
           } 
           else 
           { 
               var joinedArgs = string.Join(" ", args); 
               var regex = @"-op [\+\-\*\/] -l [-0-9]+ -r [-0-9]+"; 

               if (Regex.IsMatch(joinedArgs, regex))
               { 
                   int _left = Int32.Parse(args);
                   int _right = Int32.Parse(args); 
                   string _operator = args;

                   var _operation = new Operation(_operator, _left, _right); 
                   var _result = _operation.DoOperation(); 

                   Console.WriteLine($"Result is: {_result}"); 
                } 
                else 
                { 
                    PrintUsage(); 
                 } 
           } 
      } 

      public static void PrintUsage()
      { 
          Console.WriteLine($"Welcome to ConsoleCalculator!");
          Console.WriteLine($"");
          Console.WriteLine($"-op Operator, it must be +,-,*,/");
          Console.WriteLine($"-l Left number");
          Console.WriteLine($"-r Left number"); 
          Console.WriteLine($""); 
          Console.WriteLine($"Example usage: -op + -l 5 -r 6");
      } 
   } 
} 

 

We will be using this application from command like, so in order to make it work, we have to call it passing some parameters. For example:

ConsoleCalculator.CLI.exe -op + -l 10 -r 20 

Which translates to:

ConsoleCalculator.CLI.exe -operator + -leftValue 10 -rightValue 20 

The code for that is pretty simple, if it doesn’t match a certain regex pattern, it’s a wrong call and it call the PrintUsage(). This means that if we enter a something different than a number, because it’s set on the regex, it won’t even try to make hte calculation.

That means, if we call it like this:

ConsoleCalculator.CLI.exe -operator + -leftValue asdfg -rightValue ghjk 

It won’t ever enter inside the operations logic and we are saving future checks like TryParseing the values.

Test

We have the core library and the command line, but we need now testing, because that’s what we want to do in the CI.

So let’s go ahead and create a new MSTest Test Project (.NET Core) and name it CalculatorCLI.Tests.

NET Core version

As we did before, as soon as you create the project, go to the project properties and change the Target framework to .NET Core 3.0, if it’s not like that already.

Then add the reference to the ConsoleCLI.Core and ConsoleCLI.Core to our newly created test project.

Code

We are going to split the test in two different files: CoreTests.cs and CLITests.cs

 

using CalculatorCLI.Core;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System; 
using System.Collections.Generic; 
using System.Text; 

namespace CalculatorCLI.Tests 
{ 
    [TestClass] 
    public class CoreTests
    { 
        public int _left = 2; 
        public int _right = 2; 

        [TestMethod] 
        public void ShouldAdd() 
        { 

            var expectedResult = 4;
            var operation = new Operation("+", _left, _right); 
            var functionResult = operation.DoOperation(); 

            Assert.AreEqual(functionResult, expectedResult);
        } 

        [TestMethod]
        public void ShouldSubstract()
        { 
           var expectedResult = 0; 
           var operation = new Operation("-", _left, _right); 
           var functionResult = operation.DoOperation(); 

           Assert.AreEqual(functionResult, expectedResult); 
        } 

        [TestMethod] 
        public void ShouldMultiply()
        { 
            var expectedResult = 4; 
            var operation = new Operation("*", _left, _right); 
            var functionResult = operation.DoOperation(); 

            Assert.AreEqual(functionResult, expectedResult);
        } 

        [TestMethod]
        public void ShouldDivide()
        { 
        var expectedResult = 1; 
        var operation = new Operation("/", _left, _right); 
        var functionResult = operation.DoOperation(); 
   
            Assert.AreEqual(functionResult, expectedResult); 
         } 

         [TestMethod] 
          
         public void ShouldThrowExceptionForDivideByZero()
         { 
            var operation = new Operation("/", _left, 0);
            operation.DoOperation();
         } 
         [TestMethod] 
          
         public void ShouldThrowExceptionForWrongOperator() 
         { 
             var operation = new Operation("text", _left, 0); 
             operation.DoOperation(); 
         } 
     } 
}

 

 

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System; 
using System.Collections.Generic;
using System.Text; 

namespace CalculatorCLI.Tests 
{ 
   [TestClass] 
   public class CLITests 
   { 
       public string _left = "2"; 
       public string _right = "2"; 
       
       [TestMethod] 
       public void ShouldAdd() 
       { 
          var args = new string[] { "-op", "+", "-l", "45", "-r", "96" }; 
          CalculatorCLI.CLI.Program.Main(args); 
       } 
       
       [TestMethod] 
       public void ShouldSubstract() 
       { 
          var args = new string[] { "-op", "-", "-l", "45", "-r", "96" }; 
          CalculatorCLI.CLI.Program.Main(args);
       } 

       [TestMethod] 
       public void ShouldMultiply()
       { 
           var args = new string[] { "-op", "*", "-l", "45", "-r", "96" }; 
           CalculatorCLI.CLI.Program.Main(args); 
       } 

       [TestMethod] 
       public void ShouldDivide() 
       { 
           var args = new string[] { "-op", "/", "-l", "45", "-r", "96" }; 
           CalculatorCLI.CLI.Program.Main(args);
       } 
   } 
} 

 

 

 

 

With everything created we will end up with a solution like this:

 

 

And with this we can now run the Tests, so go to the Test Explorer in the Visual Studio and run them!


Travis CI

If you don’t already now, TravisCI is a hosted continuous integration and deployment system.

There are some steps that we have to here, but first we are going to link our Github repository to be listened by the TravisCI agents in order to build and test our project.


Enable repository

In order to do this, login to the Travis CI page and go to your repositories, then filter for the project that you created and enable it, by clicking on the slider next to the repository name.


Create .travis.yml

 

We need to create a file called .travis.yml in the root of you project, this is because as stated in the documentation:

 

Travis only runs builds on the commits you push after you’ve added a .travis.yml file.

So go and create a .travis.yml file in the root of the repository with the following lines:

 

language:
    csharp 
sudo: required 
mono: none 
dotnet: 3.0 

os: - linux 

before_script: 
- dotnet restore ".\CalculatorCLI\CalculatorCLI.sln" 

script: 
- dotnet build ".\CalculatorCLI\CalculatorCLI.sln" -c Release 
- dotnet test ".\CalculatorCLI\CalculatorCLI.sln" -c Release -v n

 

I won’t get into the sytax of how the .travis.yml file works, but let’s review what this is doing:

  1. We set up that the language will be csharp.
  2. We won’t be using mono because .NET Core 3.0 will run native in Linux.
  3. We set the dotnet version to 3.0.
  4. We set the os, by default it’s linux but I added it anyways.
  5. Now we have before_script which will wun before the major logic .here, so what I put was to run dotnet restore to the solution so everything loads perfectly later.
  6. Now in the script, we will be doing a dotnet builld and dotnet test to our solution, this will check that it compiles and then run the tests.

Aaand we are done!

Upload to master

Now we just need to push everything to master.

git add --all
git commit -m "Initial files" 
git push 

Check continuous integration

We can check the CI status of the push to master that we did in in both the repository page or the TravisCI dashboard.

On progress

 

Finished

Let’s break it

Now in order to see how powerful this is, let’s break the code and change the core library in order to make it fail.

Code changes

So go to the Operation.cs and change something that will break some tests.

 

using System; 
namespace CalculatorCLI.Core 
{ 
    public enum OperatorsEnum 
    { 
      ADD, 
      SUBSTRACT, 
      MULTIPLY, 
      DIVIDE 
    } 

    public class Operation { public OperatorsEnum OperatorEnum { get; set; } 
    public int LeftValue { get; set; } 
    public int RightValue { get; set; } 
    
    public Operation(string operatorString, int leftValue, int rightValue) 
    { 
        switch (operatorString) 
        { 
           case "+": 
               OperatorEnum = OperatorsEnum.SUBSTRACT; 
               break; 
           case "-": 
               OperatorEnum = OperatorsEnum.SUBSTRACT; 
               break; 
           case "*": 
               OperatorEnum = OperatorsEnum.MULTIPLY; 
               break; 
           case "/": 
               OperatorEnum = OperatorsEnum.DIVIDE; 
               break; 
           default: 
               throw new Exception("Operator invalid"); 
        } 

        LeftValue = leftValue; 
        RightValue = rightValue; 
    } 

    public int DoOperation() 
    { 
        switch (OperatorEnum) 
        { 
            case OperatorsEnum.ADD: 
                return LeftValue + RightValue; 
            case OperatorsEnum.SUBSTRACT: 
                return LeftValue - RightValue; 
            case OperatorsEnum.MULTIPLY: 
                return LeftValue * RightValue; 
            case OperatorsEnum.DIVIDE: 
                return LeftValue / RightValue; 
            default: 
                throw new Exception("Operator is not valid"); 
         } 
     } 
  } 
}

 

And if we run the test again, because we chagned the case to addition, it will fail:

case "+": 
    OperatorEnum = OperatorsEnum.SUBSTRACT; 
    break; 

As expected, it failed for the ShouldAdd case:

Now make a commit of this change and push it to master, and wait for the results from the TravisCI agent.

git add --all git commit -m "Breaking changes" git push 

Build

Now let’s go to TravisCI logs and we will see that we successfully broke the project, because the integration test are failing and the build status is error.

At the very end of the log we can see the error itself:

Let’s fix it again!

Now revert what we did and push the code to master, and check the status of the new build.

Tests are passing successfully:

And build is also a success:

Conclusion

It’s really quite powerful, CI and CD have existed since long time ago, but now it’s pretty simple to make it run in every single project, it doesn’t matter how small or simple it is.

From my point of view, everyone should at least set up CI for each of their projects, because it’s a good practice and it will eventually save you time debugging and finding error that shouldn’t occurr if you had set proper tests and CI.

That’s it

That’s it about how to create a .NET Core 3.0 solution that has continuous integration on every build using TravisCI and storing the code in Github.

You can find the source code for this project here.

 
Posts relacionados
¿Cuándo usar BDD VS TDD? Diferencias y Casos de uso
Publicado por Sergio Darias Pérez  |  19 enero 2024

Descubre las diferencias entre BDD y TDD, cómo estos enfoques de desarrollo de software mejoran la eficiencia en cada proyecto y cuando debes usarlos

Leer más
¿Qué es y para qué sirve un TDD o Test Driven Development?
Publicado por Sergio Darias Pérez  |  16 febrero 2023

En este post te explicamos en que consiste el enfoque de TDD o Test Driven Development durante el ciclo de vida del software y cuáles son sus ventajas

Leer más
Descubriendo Extensiones Únicas para Visual Studio Code: Rest Client
Publicado por Héctor García Delgado  |  30 agosto 2022

Cómo probar y trabajar con una API web desde Visual Studio Code gracias a Rest Client. En este post te contamos como funciona paso a paso 😉

Leer más