Kontinuierliche Integration für ein .NET Core 3.0-Projekt mit TravisCI
Letztes Wochenende habe ich beschlossen, dass ich mein Scraper-Checker-Downloader-Projekt, das ich in verschiedenen Repositories durchgeführt habe, richtig starten möchte.
Nachdem ich ein weiteres Projekt gestartet hatte, musste es cool sein, wie wirklich cool, mit CI/CD, Pull-Request, Dokumentation, Abzeichen in der Readme-Datei und allem, was ich gesehen habe, dass es cool ist und tatsächlich die Best Practices sind.
Und nach einem guten Wochenende habe ich schließlich [Dramarr](https://github.com/Dramarr erstellt, eine Reihe von Tools, mit denen Sendungen aus verschiedenen Quellen verschrottet und heruntergeladen werden können.
Es gibt verschiedene Repositorys in der Organisation und die meisten davon sind Bibliotheken, die im Pull-Request und bei der Zusammenführung im Master-Zweig selbst kompiliert, getestet und bereitgestellt werden.
Das nennt man CI/CD oder Continuous Integration/Continuous Delivery.
Aber in diesem Tutorial werden wir nur über CI sprechen.
Kontinuierliche Integration
Was ist das?
Entnommen aus Martin Fowlers Blog, was die beste Erklärung ist, die ich gelesen habe:
Kontinuierliche Integration ist eine Softwareentwicklungspraxis, bei der Mitglieder eines Teams ihre Arbeit häufig integrieren, normalerweise integriert jede Person mindestens täglich – was zu mehreren Integrationen pro Tag führt. Jede Integration wird durch einen automatisierten Build (inkl. Test) verifiziert, um Integrationsfehler schnellstmöglich zu erkennen.
Werkzeuge
Es gibt viele Tools, um Ihren Workflow mit CI/CD zu integrieren, aber für dieses Tutorial verwenden wir Github zum Speichern unseres Codes und die TravisCI Tools zum Einrichten des CI. Was die Sprache und Frameworks betrifft, werden wir C# und das neue.NET Core 3.0 verwenden.
Anforderungen
Damit dies funktioniert, benötigen Sie drei einfache Dinge:
- Neueste Version von Visual Studio 2019
- Github-Konto
- Travis-CI-Konto, verknüpft mit Ihrem Github-Konto
Projekt
Für dieses Tutorial verwenden wir einen einfachen Rechner. Wir werden eine Bibliothek, ein Befehlszeilentool und ein Testprojekt erstellen, um alles zu testen.
Dieses Testprojekt wird auch ausgeführt, wenn wir das CI einrichten. Das heißt, wenn wir in Zukunft eine Änderung am Code vornehmen und die Tests, die wir ursprünglich erstellt haben, nicht bestehen, erhalten wir eine Benachrichtigung oder können die Pull-Anfrage einfach ablehnen.
Erstellen des Github-Repositorys
Zuerst erstellen wir ein Github-Repository. Gehen Sie also zu Github, erstellen Sie das Repository und klonen Sie es in Ihre lokale Umgebung. Ich habe beschlossen, dieses neue Repository CalculatorCLI-demo zu nennen.

Die Lösung schaffen
Erstellen wir nun eine leere Lösung mit dem Namen CalculatorCLI im Stammordner des geklonten Repositorys
Kernbibliothek
Wie in einem realen Projekt werden wir unsere Logik in einem separaten Projekt speichern, das eine Bibliothek generiert, also erstellen wir sie.
Erstellen Sie ein Class Library (.NET Standard) und nennen Sie es CalculatorCLI.Core.
NET Core-VersionSobald Sie das Projekt erstellt haben, gehen Sie zu den Projekteigenschaften und ändern Sie Target framework in .NET Standard 2.1, um es mit Projekten kompatibel zu machen, die in .NET Core 3.0 erstellt wurden.
Code
Für das Tutorial erstellen wir eine einfache Klasse, die Operationen abwickelt.
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
Nachdem wir nun das Kernprojekt haben, erstellen wir die Anwendung. In diesem Fall handelt es sich um eine einfache Konsolenanwendung, die Argumente akzeptiert und ein Ergebnis anzeigt.
Machen wir also weiter und erstellen ein neues Console App (.NET Core), ich habe es CalculatorCLI.CLI genannt.
NET Core-Version
Gehen Sie wie zuvor, sobald Sie das Projekt erstellt haben, zu den Projekteigenschaften und ändern Sie den Target framework in .NET Core 3.0, falls dies nicht bereits der Fall ist.
Fügen Sie dann den Verweis auf ConsoleCLI.Core zu unserem neu erstellten Projekt hinzu.
Code
Was nun den Code betrifft, ist dies einfacher als zuvor.
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");
}
}
}
Wir werden diese Anwendung mit einem Befehl verwenden. Damit sie funktioniert, müssen wir sie mit einigen Parametern aufrufen. Zum Beispiel:
ConsoleCalculator.CLI.exe -op + -l 10 -r 20
Was übersetzt bedeutet:
ConsoleCalculator.CLI.exe -operator + -leftValue 10 -rightValue 20
Der Code dafür ist ziemlich einfach. Wenn er nicht mit einem bestimmten Regex-Muster übereinstimmt, handelt es sich um einen falschen Aufruf und es wird PrintUsage() aufgerufen. Das heißt, wenn wir etwas anderes als eine Zahl eingeben, weil es in der Regex festgelegt ist, wird nicht einmal versucht, die Berechnung durchzuführen.
Das heißt, wenn wir es so nennen:
ConsoleCalculator.CLI.exe -operator + -leftValue asdfg -rightValue ghjk
Es wird niemals in die Betriebslogik eingehen und wir ersparen uns zukünftige Prüfungen wie das TryParseEingeben der Werte.
Test
Wir haben die Kernbibliothek und die Befehlszeile, aber wir müssen sie jetzt testen, denn das ist es, was wir im CI tun wollen.
Machen wir also weiter und erstellen ein neues MSTest Test Project (.NET Core) und nennen es CalculatorCLI.Tests.
NET Core-Version
Gehen Sie wie zuvor, sobald Sie das Projekt erstellt haben, zu den Projekteigenschaften und ändern Sie den Target framework in .NET Core 3.0, falls dies nicht bereits der Fall ist.
Fügen Sie dann den Verweis auf ConsoleCLI.Core und ConsoleCLI.Core zu unserem neu erstellten Testprojekt hinzu.
Code
Wir werden den Test in zwei verschiedene Dateien aufteilen: CoreTests.cs und 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);
}
}
}
Wenn alles erstellt ist, erhalten wir am Ende eine Lösung wie diese:

Und damit können wir jetzt die Tests ausführen, also gehen Sie zu Test Explorer im Visual Studio und führen Sie sie aus!

Travis CI
Falls Sie es noch nicht wissen: TravisCI ist ein gehostetes kontinuierliches Integrations- und Bereitstellungssystem.
Hier müssen wir einige Schritte ausführen, aber zuerst verknüpfen wir unser Github-Repository, damit es von den TravisCI-Agenten abgehört werden kann, um unser Projekt zu erstellen und zu testen.
Repository aktivieren
Melden Sie sich dazu auf der Travis CI-Seite an und gehen Sie zu Ihren Repositorys. Filtern Sie dann nach dem von Ihnen erstellten Projekt und aktivieren Sie es, indem Sie auf den Schieberegler neben dem Repository-Namen klicken.

Erstellen Sie .travis.ymlWir müssen eine Datei mit dem Namen .travis.yml im Stammverzeichnis Ihres Projekts erstellen. Dies liegt daran, dass wie in der Dokumentation angegeben:
Travis führt Builds nur auf den Commits aus, die Sie pushen, nachdem Sie eine .travis.yml-Datei hinzugefügt haben.
Erstellen Sie also eine .travis.yml-Datei im Stammverzeichnis des Repositorys mit den folgenden Zeilen:
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
Ich werde nicht auf die Syntax eingehen, wie die Datei .travis.yml funktioniert, aber sehen wir uns an, was das bewirkt:
- Wir richten die Sprache als
csharpein. - Wir werden
mononicht verwenden, da.NET Core 3.0nativ unter Linux ausgeführt wird. - Wir setzen die
dotnet-Version auf3.0. - Wir legen den
osfest, standardmäßig ist erlinux, aber ich habe ihn trotzdem hinzugefügt. - Jetzt haben wir
before_script, das vor der Hauptlogik angezeigt wird. Ich habe alsodotnet restorezur Lösung ausgeführt, damit später alles perfekt geladen wird. - Jetzt führen wir im
scripteinendotnet builldund einendotnet testfür unsere Lösung durch. Dadurch wird überprüft, ob sie kompiliert wird, und dann werden die Tests ausgeführt.
Uuuund wir sind fertig!
Zum Master hochladen
Jetzt müssen wir nur noch alles vorantreiben, um es zu meistern.
git add --all
git commit -m "Initial files"
git push
Überprüfen Sie die kontinuierliche Integration
Wir können den CI-Status des Pushs an master überprüfen, den wir sowohl auf der Repository-Seite als auch im TravisCI-Dashboard durchgeführt haben.
Über den Fortschritt


Fertig


Lass es uns brechen
Um nun zu sehen, wie leistungsfähig das ist, brechen wir den Code und ändern die Kernbibliothek, um ihn zum Scheitern zu bringen.
Codeänderungen
Gehen Sie also zum Operation.cs und ändern Sie etwas, das einige Tests zum Scheitern bringt.
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");
}
}
}
}
Und wenn wir den Test erneut ausführen, weil wir den Fall auf „addition“ geändert haben, schlägt er fehl:
case "+":
OperatorEnum = OperatorsEnum.SUBSTRACT;
break;
Wie erwartet schlug es im Fall ShouldAdd fehl:

Führen Sie nun ein Commit dieser Änderung durch, übertragen Sie sie an den Master und warten Sie auf die Ergebnisse des TravisCI-Agenten.
git add --all
git commit -m "Breaking changes"
git push
Bauen
Gehen wir nun zu den TravisCI-Protokollen und sehen, dass wir das Projekt erfolgreich abgebrochen haben, da der Integrationstest fehlgeschlagen ist und der Build-Status Fehler ist.

Ganz am Ende des Protokolls können wir den Fehler selbst sehen:

Lass es uns noch einmal reparieren!
Machen Sie nun das, was wir getan haben, rückgängig, übertragen Sie den Code an den Master und überprüfen Sie den Status des neuen Builds.
Tests werden erfolgreich bestanden:

Und auch der Aufbau ist gelungen:

Fazit
Es ist wirklich ziemlich leistungsfähig, CI und CD gibt es schon vor langer Zeit, aber jetzt ist es ziemlich einfach, es in jedem einzelnen Projekt zum Laufen zu bringen, egal wie klein oder einfach es ist.Aus meiner Sicht sollte jeder zumindest CI für jedes seiner Projekte einrichten, da dies eine gute Vorgehensweise ist und Ihnen letztendlich Zeit beim Debuggen und Finden von Fehlern erspart, die nicht auftreten sollten, wenn Sie tests und CI richtig eingestellt hätten.
Das ist es
Hier geht es darum, wie Sie mit TravisCI und dem Speichern des Codes in Github eine .NET Core 3.0-Lösung erstellen, die bei jedem Build eine kontinuierliche Integration bietet.
Den Quellcode für dieses Projekt finden Sie hier.