Непрерывная интеграция проекта .NET Core 3.0 с использованием TravisCI
В прошлые выходные я решил, что хочу правильно запустить свой проект скрапер-проверки-загрузчика, который я делал в разных репозиториях.
После запуска еще одного проекта это должно было быть круто, на самом деле круто, использование CI/CD, пул-реквест, документация, значки в файле readme, все, что я видел, это круто и действительно является лучшими практиками.
И после хороших выходных я создал Dramarr, набор инструментов для удаления и загрузки шоу из разных источников.
У него есть разные репозитории в организации, и большинство из них представляют собой библиотеки, которые компилируются, тестируются и развертываются самостоятельно в запросе на включение и при слиянии в основной ветке.
Это то, что называется CI/CD или непрерывной интеграцией/непрерывной доставкой.
Но в этом уроке мы просто поговорим о CI.
Непрерывная интеграция
Что это?
Взято из [блога] Мартина Фаулера (https://martinfowler.com/articles/continuousIntegration.html), это лучшее объяснение, которое я читал:
Непрерывная интеграция — это практика разработки программного обеспечения, при которой члены команды часто интегрируют свою работу, обычно каждый человек интегрируется как минимум ежедневно, что приводит к нескольким интеграциям в день. Каждая интеграция проверяется автоматической сборкой (включая тестирование) для максимально быстрого обнаружения ошибок интеграции.
Инструменты
Существует множество инструментов для интеграции вашего рабочего процесса с CI/CD, но в этом руководстве мы будем использовать Github для хранения нашего кода и инструменты TravisCI для настройки CI. Что касается языка и фреймворков, мы будем использовать C# и новый .NET Core 3.0.
Требования
Для того, чтобы сделать эту работу, вам нужны три простые вещи:
- Последняя версия Visual Studio 2019.
- Аккаунт на Гитхабе
- Учетная запись Travis-CI, связанная с вашей учетной записью Github.
Проект
В рамках этого урока мы будем создавать простой калькулятор. Мы будем создавать библиотеку, инструмент командной строки и проект тестирования, чтобы протестировать все.
Этот проект тестирования также будет запущен, когда мы настроим CI, а это означает, что если в будущем мы внесем изменения в код и тесты, которые мы изначально создали, не пройдут, мы получим уведомление или можем просто отклонить запрос на включение.
Создание репозитория Github
Сначала мы создадим репозиторий Github, поэтому зайдите на Github, создайте репозиторий и клонируйте его в локальную среду. Я решил назвать этот новый репозиторий CalculatorCLI-demo.

Создание решения
Теперь создадим пустое решение с именем CalculatorCLI в корневой папке клонированного репозитория.
Основная библиотека
Как и в реальном проекте, мы будем хранить нашу логику в отдельном проекте, который генерирует библиотеку, поэтому давайте создадим ее.
Идите и создайте Class Library (.NET Standard) и назовите его CalculatorCLI.Core
Версия NET CoreКак только вы создадите проект, перейдите к свойствам проекта и измените Target framework на .NET Standard 2.1, чтобы сделать его совместимым с проектами, встроенными в .NET Core 3.0.
Код
В целях обучения давайте создадим простой класс, обрабатывающий операции.
[[[ТОК_12]]]
интерфейс командной строки
Теперь, когда у нас есть основной проект, давайте создадим приложение. В данном случае это будет простое консольное приложение, которое принимает аргументы и отображает результат.
Итак, давайте продолжим и создадим новый Console App (.NET Core), я назвал его CalculatorCLI.CLI.
Версия NET Core
Как мы делали раньше, как только вы создадите проект, перейдите в свойства проекта и измените Target framework на .NET Core 3.0, если это еще не так.
Затем добавьте ссылку на ConsoleCLI.Core в наш вновь созданный проект.
Код
Что касается кода, это проще, чем раньше.
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");
}
}
}
Мы будем использовать это приложение из команды вроде, поэтому, чтобы оно работало, нам нужно вызвать его, передав некоторые параметры. Например:
ConsoleCalculator.CLI.exe -op + -l 10 -r 20
Что переводится как:
ConsoleCalculator.CLI.exe -operator + -leftValue 10 -rightValue 20
Код для этого довольно прост: если он не соответствует определенному шаблону регулярного выражения, это неправильный вызов и он вызывает PrintUsage(). Это означает, что если мы введем нечто отличное от числа, поскольку оно установлено в регулярном выражении, оно даже не попытается выполнить расчет.
Это означает, что если мы назовем это так:
ConsoleCalculator.CLI.exe -operator + -leftValue asdfg -rightValue ghjk
Он никогда не войдет в логику операций, и мы сохраняем будущие проверки, такие как TryParse при проверке значений.
Тест
У нас есть основная библиотека и командная строка, но теперь нам нужно протестировать, потому что именно это мы хотим сделать в CI.
Итак, давайте продолжим и создадим новый MSTest Test Project (.NET Core) и назовем его CalculatorCLI.Tests.
Версия NET Core
Как мы делали раньше, как только вы создадите проект, перейдите в свойства проекта и измените Target framework на .NET Core 3.0, если это еще не так.
Затем добавьте ссылку на ConsoleCLI.Core и ConsoleCLI.Core в наш недавно созданный тестовый проект.
Код
Мы собираемся разделить тест на два разных файла: CoreTests.cs и 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);
}
}
}
После всего созданного мы получим такое решение:

Теперь мы можем запускать тесты, поэтому перейдите в Test Explorer в Visual Studio и запустите их!

Трэвис CI
Если вы еще этого не сделали, TravisCI — это размещенная система непрерывной интеграции и развертывания.
Здесь нам нужно сделать несколько шагов, но сначала мы собираемся связать наш репозиторий Github, чтобы его прослушивали агенты TravisCI, чтобы построить и протестировать наш проект.
Включить репозиторий
Для этого войдите на страницу Travis CI и перейдите к своим репозиториям, затем отфильтруйте созданный вами проект и включите его, щелкнув ползунок рядом с именем репозитория.

Создать .travis.ymlНам нужно создать файл с именем .travis.yml в корне вашего проекта, это потому, что как указано в документации:
Travis запускает сборки только на основе коммитов, которые вы отправляете после добавления файла .travis.yml.
Итак, создайте файл .travis.yml в корне репозитория со следующими строками:
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
Я не буду вдаваться в синтаксис работы файла .travis.yml, но давайте рассмотрим, что он делает:
- Настраиваем, что язык будет
csharp. - Мы не будем использовать
mono, потому что.NET Core 3.0будет работать в Linux. - Устанавливаем версию
dotnetна3.0. - Устанавливаем
os, по умолчанию этоlinux, но я все равно его добавил. - Теперь у нас есть
before_scriptкоторый будет работать перед основной логикой .here, поэтому я поставил задачу запуститьdotnet restoreдля решения, чтобы потом все загружалось идеально. - Теперь в
scriptмы выполнимdotnet builldиdotnet testдля нашего решения, это проверит его компиляцию, а затем запустит тесты.
Иа, мы закончили!
Загрузить в мастер
Теперь нам просто нужно все подтолкнуть к освоению.
git add --all
git commit -m "Initial files"
git push
Проверьте непрерывную интеграцию
Мы можем проверить статус CI отправки в master, которую мы сделали, как на странице репозитория, так и на панели управления TravisCI.
В процессе


Готово


Давайте сломаем это
Теперь, чтобы увидеть, насколько это мощно, давайте сломаем код и изменим основную библиотеку, чтобы она не работала.
Изменения кода
Итак, перейдите в Operation.cs и измените что-нибудь, что нарушит некоторые тесты.
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");
}
}
}
}
И если мы запустим тест еще раз, поскольку мы изменили регистр на сложение, он завершится неудачей:
case "+":
OperatorEnum = OperatorsEnum.SUBSTRACT;
break;
Как и ожидалось, в случае ShouldAdd это не удалось:

Теперь зафиксируйте это изменение, отправьте его мастеру и дождитесь результатов от агента TravisCI.
git add --all
git commit -m "Breaking changes"
git push
Сборка
Теперь давайте зайдем в логи TravisCI и увидим, что мы успешно сломали проект, потому что интеграционный тест не пройден, а статус сборки — ошибка.

В самом конце лога мы видим саму ошибку:

Давайте исправим это еще раз!
Теперь верните то, что мы сделали, отправьте код мастеру и проверьте статус новой сборки.
Тесты проходят успешно:

И сборка тоже удалась:

Заключение
Он действительно очень мощный, CI и CD существуют уже давно, но теперь его довольно просто запустить в каждом отдельном проекте, неважно, насколько он мал или прост.С моей точки зрения, каждый должен хотя бы настроить CI для каждого из своих проектов, потому что это хорошая практика, и в конечном итоге это сэкономит вам время на отладку и поиск ошибок, которые не должны возникать, если вы установили правильные tests и CI.
Вот и все
Вот и все, как создать решение .NET Core 3.0 с непрерывной интеграцией в каждой сборке с использованием TravisCI и сохранением кода в Github.
Вы можете найти исходный код этого проекта здесь.