TravisCI を使用した .NET Core 3.0 プロジェクトの継続的統合

· 5分で読める

先週末、私はさまざまなリポジトリで行ってきたスクレイパー・チェッカー・ダウンローダー・プロジェクトを適切に開始したいと決心しました。

さらに別のプロジェクトを開始した後、これは、CI/CD、プル リクエスト、ドキュメント、Readme のバッジなど、私がこれまで見てきたクールで実際にベスト プラクティスであるすべてを使用して、本当にクール のようにクールでなければなりませんでした。

そして充実した週末を過ごした後、最終的に Dramarr というさまざまなソースから番組をスクラップしてダウンロードするツール セットを作成することになりました。

組織内にはさまざまなリポジトリがあり、それらのほとんどは、プル リクエストやマスター ブランチにマージされるときにコンパイル、テスト、デプロイされるライブラリです。

それが CI/CD または継続的インテグレーション/継続的デリバリーと呼ばれるものです。

ただし、このチュートリアルでは CI についてのみ説明します。

継続的インテグレーション

##それは何ですか?

Martin Fowler の ブログ から引用したもので、これが私が読んだ中で最も優れた説明です。

継続的インテグレーションは、チームのメンバーが自分の作業を頻繁に統合するソフトウェア開発手法であり、通常は各人が少なくとも毎日統合するため、1 日に複数の統合が行われます。 統合エラーをできるだけ早く検出するために、各統合は自動ビルド (テストを含む) によって検証されます。

ツール

ワークフローを CI/CD と統合するためのツールは多数ありますが、このチュートリアルでは、Github を使用してコードを保存し、TravisCI ツールを使用して CI をセットアップします。言語とフレームワークに関しては、C# と新しい .NET Core 3.0 を使用します。

要件

これを機能させるには、次の 3 つの簡単なことが必要です。

  1. Visual Studio 2019の最新バージョン 2.Githubアカウント
  2. Github アカウントにリンクされた Travis-CI アカウント

プロジェクト

このチュートリアルでは、簡単な計算機を使用します。すべてをテストするためのライブラリ、コマンド ライン ツール、およびテスト プロジェクトを作成します。

このテスト プロジェクトは、CI のセットアップ時にも実行されます。つまり、将来コードに変更を加え、最初に作成したテストが合格しなかった場合は、通知を受け取るか、単純にプル リクエストを拒否することができます。

Github リポジトリの作成

まず、Github リポジトリを作成するので、Github にアクセスしてリポジトリを作成し、ローカル環境にクローンを作成します。この新しいリポジトリを CalculatorCLI-demo と呼ぶことにしました。

ソリューションの作成

次に、クローンされたリポジトリのルート フォルダーに CalculatorCLI という名前の空のソリューションを作成しましょう。

コアライブラリ

実際のプロジェクトと同様に、ライブラリを生成する別のプロジェクトにロジックを保存するので、それを作成しましょう。

Class Library (.NET Standard) を作成し、CalculatorCLI.Core という名前を付けます。

NET Core のバージョンプロジェクトを作成したらすぐに、プロジェクトのプロパティに移動し、.NET Core 3.0 でビルドされたプロジェクトと互換性を持たせるために、Target framework を .NET Standard 2.1 に変更します。

コード

チュートリアルのために、操作を処理する簡単なクラスを作成しましょう。

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

コアプロジェクトができたので、アプリケーションを作成しましょう。この場合、引数を受け取り、結果を出力する単純なコンソール アプリケーションになります。

それでは、新しい 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 への参照を、新しく作成したテスト プロジェクトに追加します。

コード

テストを 2 つの異なるファイル 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);
        }
    }
}

すべてを作成すると、最終的に次のようなソリューションになります。

これでテストを実行できるようになりました。Visual Studio の Test Explorer に移動してテストを実行してください。

#トラヴィスCI

まだお持ちでない場合は、TravisCI はホスト型の継続的統合および展開システムです。

ここで行う必要がある手順がいくつかありますが、最初に、プロジェクトをビルドしてテストするために、TravisCI エージェントがリッスンできるように Github リポジトリをリンクします。

リポジトリを有効にする

これを行うには、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. .NET Core 3.0 は Linux でネイティブに実行されるため、mono は使用しません。
  3. dotnet バージョンを 3.0 に設定します。
  4. os を設定します。デフォルトでは linux ですが、とにかく追加しました。
  5. これで、主要なロジックの前に実行される before_script ができました。ここで、私が設定したのは、ソリューションに対して dotnet restore を実行して、後ですべてが完全に読み込まれるようにすることでした。
  6. script では、ソリューションに対して dotnet builld および dotnet test を実行します。これにより、コンパイルが完了したことを確認してからテストを実行します。

ああ、これで終わりです!

マスターにアップロード

あとは、すべてをマスターするだけです。

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

継続的インテグレーションをチェックする

リポジトリ ページまたは TravisCI ダッシュボードの両方で実行した master へのプッシュの CI ステータスを確認できます。

進行中



完了しました



#壊してみよう

これがどれほど強力であるかを確認するために、コードを分割し、コア ライブラリを変更して失敗させてみましょう。

コードの変更

したがって、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 を設定していれば発生しないはずのデバッグやエラーの発見にかかる時間を最終的に節約できるからです。

#それだけです

TravisCI を使用し、コードを Github に保存することで、すべてのビルドで継続的統合を行う .NET Core 3.0 ソリューションを作成する方法については以上です。

このプロジェクトのソース コードは ここ で見つけることができます。