Intégration continue pour un projet .NET Core 3.0 utilisant TravisCI
Ce week-end dernier, j’ai décidé que je voulais démarrer correctement mon projet scraper-checker-downloader que j’ai réalisé dans différents référentiels.
Après avoir démarré un autre projet, cela devait être cool, comme vraiment cool, en utilisant CI/CD, pull request, documentation, badges dans readme, tout ce que j’ai vu, c’est cool et ce sont en effet les meilleures pratiques.
Et après un bon week-end, j’ai fini par créer Dramarr, un ensemble d’outils qui suppriment et téléchargent des émissions à partir de différentes sources.
Il existe différents référentiels dans l’organisation et la plupart d’entre eux sont des bibliothèques qui sont compilées, testées et déployées elles-mêmes dans la demande d’extraction et lorsqu’elles sont fusionnées dans la branche principale.
C’est ce qu’on appelle CI/CD ou intégration continue/livraison continue.
Mais pour ce tutoriel nous allons juste parler de CI.
Intégration continue
Qu’est-ce que c’est ?
Tiré du [blog](https://martinfowler.com/articles/continuousIntegration.html de Martin Fowler, qui est la meilleure explication que j’ai lue :
L’intégration continue est une pratique de développement logiciel dans laquelle les membres d’une équipe intègrent fréquemment leur travail, généralement chaque personne l’intègre au moins quotidiennement, ce qui conduit à plusieurs intégrations par jour. Chaque intégration est vérifiée par un build automatisé (incluant un test) pour détecter les erreurs d’intégration le plus rapidement possible.
Outils
Il existe de nombreux outils pour intégrer votre flux de travail avec CI/CD, mais pour ce didacticiel, nous utiliserons Github pour stocker notre code et les outils TravisCI pour configurer le CI. Concernant le langage et les frameworks, nous utiliserons C# et le nouveau .NET Core 3.0.
Exigences
Pour que cela fonctionne, vous avez besoin de trois choses simples :
- Dernière version de Visual Studio 2019
- Compte Github
- Compte Travis-CI lié à votre compte Github
Projet
Pour le bien de ce didacticiel, nous allons réaliser une simple calculatrice. Nous allons créer une bibliothèque, un outil de ligne de commande et un projet de test pour tout tester.
Ce projet de test sera également en cours d’exécution lorsque nous configurerons le CI, ce qui signifie que si à l’avenir nous apportons une modification au code et que les tests que nous avons initialement créés ne réussissent pas, nous recevrons une notification ou nous pourrons simplement rejeter la pull request.
Création du dépôt Github
Tout d’abord, nous allons créer un référentiel Github, alors accédez à Github, créez le référentiel et clonez-le dans votre environnement local. J’ai décidé d’appeler ce nouveau référentiel CalculatorCLI-demo.

Créer la solution
Créons maintenant une solution vide appelée CalculatorCLI, dans le dossier racine du référentiel cloné
Bibliothèque principale
Comme ce serait le cas dans un projet du monde réel, nous stockerons notre logique dans un projet séparé qui génère une bibliothèque, alors créons-la.
Allez créer un Class Library (.NET Standard) et nommez-le CalculatorCLI.Core
###NETCore versionDès que vous créez le projet, accédez aux propriétés du projet et remplacez Target framework par .NET Standard 2.1, afin de le rendre compatible avec les projets construits dans .NET Core 3.0.
###Code
Pour le bien du didacticiel, créons une classe simple qui gère les opérations.
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
Maintenant que nous avons le projet principal, créons l’application. Dans ce cas, il s’agira d’une simple application console qui accepte les arguments et affiche un résultat.
Alors allons-y et créons un nouveau Console App (.NET Core), je l’ai nommé CalculatorCLI.CLI.
###NETCore version
Comme nous l’avons fait précédemment, dès que vous créez le projet, accédez aux propriétés du projet et remplacez le Target framework par .NET Core 3.0, si ce n’est pas déjà le cas.
Ajoutez ensuite la référence au ConsoleCLI.Core à notre projet nouvellement créé.
###Code
Maintenant, pour le code, c’est plus simple qu’avant.
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[3]);
int _right = Int32.Parse(args[5]);
string _operator = args[1];
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");
}
}
}
Nous utiliserons cette application à partir d’une commande similaire, donc pour la faire fonctionner, nous devons l’appeler en passant certains paramètres. Par exemple :
ConsoleCalculator.CLI.exe -op + -l 10 -r 20
Ce qui se traduit par :
ConsoleCalculator.CLI.exe -operator + -leftValue 10 -rightValue 20
Le code pour cela est assez simple, s’il ne correspond pas à un certain modèle d’expression régulière, c’est un mauvais appel et il appelle le PrintUsage(). Cela signifie que si nous entrons quelque chose de différent d’un nombre, parce qu’il est défini sur l’expression régulière, il n’essaiera même pas de faire le calcul.
Cela signifie que si nous l’appelons ainsi :
ConsoleCalculator.CLI.exe -operator + -leftValue asdfg -rightValue ghjk
Il n’entrera jamais dans la logique des opérations et nous enregistrons les contrôles futurs comme TryParse les valeurs.
##Tester
Nous avons la bibliothèque principale et la ligne de commande, mais nous devons maintenant tester, car c’est ce que nous voulons faire dans le CI.
Alors allons-y et créons un nouveau MSTest Test Project (.NET Core) et nommons-le CalculatorCLI.Tests.
###NETCore version
Comme nous l’avons fait auparavant, dès que vous créez le projet, accédez aux propriétés du projet et remplacez Target framework par .NET Core 3.0, si ce n’est pas déjà le cas.
Ajoutez ensuite la référence aux ConsoleCLI.Core et ConsoleCLI.Core à notre projet de test nouvellement créé.
###Code
Nous allons diviser le test en deux fichiers différents : CoreTests.cs et 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]
[ExpectedException(typeof(System.DivideByZeroException))]
public void ShouldThrowExceptionForDivideByZero()
{
var operation = new Operation("/", _left, 0);
operation.DoOperation();
}
[TestMethod]
[ExpectedException(typeof(System.Exception), "Operator invalid")]
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);
}
}
}
Avec tout ce qui a été créé, nous aboutirons à une solution comme celle-ci :

Et avec cela, nous pouvons maintenant exécuter les tests, alors accédez au Test Explorer dans Visual Studio et exécutez-les !

Travis CI
Si vous ne l’avez pas déjà fait, TravisCI est un système hébergé d’intégration et de déploiement continu.
Nous devons suivre certaines étapes ici, mais nous allons d’abord lier notre référentiel Github pour qu’il soit écouté par les agents TravisCI afin de construire et de tester notre projet.
Activer le référentiel
Pour ce faire, connectez-vous à la page Travis CI et accédez à vos référentiels, puis filtrez le projet que vous avez créé et activez-le, en cliquant sur le curseur à côté du nom du référentiel.

Créer .travis.ymlNous devons créer un fichier appelé .travis.yml à la racine de votre projet, car comme indiqué dans la documentation :
Travis exécute uniquement les builds sur les commits que vous poussez après avoir ajouté un fichier .travis.yml.
Allez donc créer un fichier .travis.yml à la racine du dépôt avec les lignes suivantes :
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
Je n’entrerai pas dans la syntaxe du fonctionnement du fichier .travis.yml, mais passons en revue ce qu’il fait :
- Nous avons configuré que la langue sera
csharp. - Nous n’utiliserons pas
monocar.NET Core 3.0fonctionnera en natif sous Linux. - Nous définissons la version
dotnetsur3.0. - Nous définissons le
os, par défaut c’estlinuxmais je l’ai quand même ajouté. - Maintenant, nous avons
before_scriptqui s’exécutera avant la logique majeure .ici, donc ce que j’ai mis était d’exécuterdotnet restoresur la solution pour que tout se charge parfaitement plus tard. - Maintenant, dans le
script, nous allons faire undotnet builldet undotnet testà notre solution, cela vérifiera qu’elle compile puis exécutera les tests.
Et nous avons terminé !
Télécharger sur le maître
Il ne nous reste plus qu’à tout pousser pour maîtriser.
git add --all
git commit -m "Initial files"
git push
Vérifier l’intégration continue
Nous pouvons vérifier l’état CI du push vers master que nous avons effectué à la fois dans la page du référentiel ou dans le tableau de bord TravisCI.
En cours


Terminé


Cassons-le
Maintenant, pour voir à quel point cela est puissant, cassons le code et modifions la bibliothèque principale afin de la faire échouer.
Modifications du code
Alors allez dans le Operation.cs et changez quelque chose qui interrompra certains 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");
}
}
}
}
Et si nous exécutons à nouveau le test, parce que nous avons modifié le cas en addition, il échouera :
case "+":
OperatorEnum = OperatorsEnum.SUBSTRACT;
break;
Comme prévu, cela a échoué dans le cas ShouldAdd :

Maintenant, validez ce changement et poussez-le vers master, et attendez les résultats de l’agent TravisCI.
git add --all
git commit -m "Breaking changes"
git push
Construire
Passons maintenant aux journaux TravisCI et nous verrons que nous avons réussi à interrompre le projet, car les tests d’intégration échouent et l’état de la construction est une erreur.

À la toute fin du journal, nous pouvons voir l’erreur elle-même :

Réparons-le à nouveau !
Maintenant, revenez sur ce que nous avons fait, poussez le code vers la maîtrise et vérifiez l’état de la nouvelle version.
Les tests réussissent :

Et la construction est aussi une réussite :

#Conclusion
C’est vraiment assez puissant, CI et CD existent depuis longtemps, mais maintenant il est assez simple de le faire fonctionner dans chaque projet, peu importe sa taille ou sa simplicité.De mon point de vue, tout le monde devrait au moins configurer CI pour chacun de ses projets, car c’est une bonne pratique et cela vous fera éventuellement gagner du temps lors du débogage et de la recherche d’erreurs qui ne devraient pas se produire si vous aviez défini correctement tests et CI.
C’est ça
Il s’agit de savoir comment créer une solution .NET Core 3.0 avec une intégration continue sur chaque build à l’aide de TravisCI et en stockant le code dans Github.
Vous pouvez trouver le code source de ce projet ici.