TravisCI를 사용하여 .NET Core 3.0 프로젝트에 대한 지속적인 통합
지난 주말에 저는 다른 저장소에서 해왔던 스크레이퍼-체커-다운로더 프로젝트를 제대로 시작하기로 결정했습니다.
또 다른 프로젝트를 시작한 후에는 CI/CD, 끌어오기 요청, 문서, Readme의 배지 등 제가 본 모든 것이 멋지고 실제로 모범 사례인 것을 사용하여 정말 멋지듯이 멋져야 했습니다.
그리고 좋은 주말을 보낸 후 저는 결국 다양한 소스에서 프로그램을 스크랩하고 다운로드하는 도구 세트인 Dramarr를 만들었습니다.
조직에는 다양한 리포지토리가 있으며 대부분은 풀 요청 및 마스터 브랜치에 병합될 때 자체적으로 컴파일, 테스트 및 배포되는 라이브러리입니다.
이것이 바로 CI/CD 또는 지속적 통합/지속적 전달입니다.
하지만 이 튜토리얼에서는 CI에 대해서만 이야기하겠습니다.
지속적인 통합
그게 뭐죠?
Martin Fowler의 블로그에서 발췌한 내용입니다. 제가 읽은 설명 중 가장 좋은 설명은 다음과 같습니다.
지속적 통합은 팀 구성원이 자신의 작업을 자주 통합하는 소프트웨어 개발 방식으로, 일반적으로 각 사람은 적어도 매일 통합하여 하루에 여러 번 통합하게 됩니다. 각 통합은 자동화된 빌드(테스트 포함)를 통해 검증되어 통합 오류를 가능한 한 빨리 감지합니다.
도구
작업 흐름을 CI/CD와 통합하는 도구는 많지만 이 튜토리얼에서는 Github를 사용하여 코드를 저장하고 TravisCI 도구를 사용하여 CI를 설정합니다. 언어와 프레임워크는 C#과 new.NET Core 3.0을 사용하겠습니다.
요구사항
이 작업을 수행하려면 세 가지 간단한 사항이 필요합니다.
- Visual Studio 2019 최신 버전
- Github 계정
- Github 계정에 연결된 Travis-CI 계정
프로젝트
이 튜토리얼에서는 간단한 계산기를 사용하겠습니다. 우리는 모든 것을 테스트하기 위해 라이브러리, 명령줄 도구 및 테스트 프로젝트를 만들 것입니다.
이 테스트 프로젝트는 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에 빌드된 프로젝트와 호환되게 만듭니다.
코드
튜토리얼을 위해 작업을 처리하는 간단한 클래스를 만들어 보겠습니다.
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
이는 작업 논리 내부에 입력되지 않으며 값 TryParseing과 같은 향후 검사를 저장합니다.
테스트
핵심 라이브러리와 명령줄이 있지만 지금 테스트가 필요합니다. 왜냐하면 그것이 우리가 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);
}
}
}
모든 것이 생성되면 다음과 같은 솔루션이 생성됩니다.

이제 테스트를 실행할 수 있으므로 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 파일이 어떻게 작동하는지에 대한 구문을 다루지는 않겠지만 이것이 무엇을 하는지 검토해 보겠습니다.
- 언어를
csharp로 설정했습니다. .NET Core 3.0는 Linux에서 기본적으로 실행되므로mono를 사용하지 않습니다.dotnet버전을3.0로 설정했습니다.os를 설정했습니다. 기본적으로linux이지만 어쨌든 추가했습니다.- 이제 여기에서 주요 논리보다 먼저 실행될
before_script가 있으므로 솔루션에dotnet restore를 실행하여 나중에 모든 것이 완벽하게 로드되도록 했습니다. - 이제
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 솔루션을 만드는 방법에 대한 내용입니다.
이 프로젝트의 소스 코드는 여기에서 찾을 수 있습니다.