Непрерывная интеграция проекта .NET Core 3.0 с использованием TravisCI

· 9 мин чтения

В прошлые выходные я решил, что хочу правильно запустить свой проект скрапер-проверки-загрузчика, который я делал в разных репозиториях.

После запуска еще одного проекта это должно было быть круто, на самом деле круто, использование CI/CD, пул-реквест, документация, значки в файле readme, все, что я видел, это круто и действительно является лучшими практиками.

И после хороших выходных я создал Dramarr, набор инструментов для удаления и загрузки шоу из разных источников.

У него есть разные репозитории в организации, и большинство из них представляют собой библиотеки, которые компилируются, тестируются и развертываются самостоятельно в запросе на включение и при слиянии в основной ветке.

Это то, что называется CI/CD или непрерывной интеграцией/непрерывной доставкой.

Но в этом уроке мы просто поговорим о CI.

Непрерывная интеграция

Что это?

Взято из [блога] Мартина Фаулера (https://martinfowler.com/articles/continuousIntegration.html), это лучшее объяснение, которое я читал:

Непрерывная интеграция — это практика разработки программного обеспечения, при которой члены команды часто интегрируют свою работу, обычно каждый человек интегрируется как минимум ежедневно, что приводит к нескольким интеграциям в день. Каждая интеграция проверяется автоматической сборкой (включая тестирование) для максимально быстрого обнаружения ошибок интеграции.

Инструменты

Существует множество инструментов для интеграции вашего рабочего процесса с CI/CD, но в этом руководстве мы будем использовать Github для хранения нашего кода и инструменты TravisCI для настройки CI. Что касается языка и фреймворков, мы будем использовать C# и новый .NET Core 3.0.

Требования

Для того, чтобы сделать эту работу, вам нужны три простые вещи:

  1. Последняя версия Visual Studio 2019.
  2. Аккаунт на Гитхабе
  3. Учетная запись 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, но давайте рассмотрим, что он делает:

  1. Настраиваем, что язык будет csharp.
  2. Мы не будем использовать mono, потому что .NET Core 3.0 будет работать в Linux.
  3. Устанавливаем версию dotnet на 3.0.
  4. Устанавливаем os, по умолчанию это linux, но я все равно его добавил.
  5. Теперь у нас есть before_script который будет работать перед основной логикой .here, поэтому я поставил задачу запустить dotnet restore для решения, чтобы потом все загружалось идеально.
  6. Теперь в 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.

Вы можете найти исходный код этого проекта здесь.