使用 TravisCI 持续集成 .NET Core 3.0 项目
上周末,我决定要正确启动我一直在不同存储库中执行的 scraper-checker-downloader 项目。
在开始另一个项目之后,这必须很酷,就像真正的酷,使用 CI/CD、拉取请求、文档、自述文件中的徽章,我所看到的一切都很酷,而且确实是最佳实践。
经过一个愉快的周末后,我最终创建了 Dramarr,这是一套可以从不同来源抓取和下载节目的工具。
它在组织中有不同的存储库,其中大多数是在拉取请求中以及在合并到主分支中时正在编译、测试和部署自身的库。
这就是所谓的 CI/CD 或持续集成/持续交付。
但在本教程中我们只讨论 CI。
持续集成
这是什么?
摘自 Martin Fowler 的 博客,这是我读过的最好的解释:
持续集成是一种软件开发实践,团队成员经常集成他们的工作,通常每个人至少每天集成 - 导致每天多次集成。 每个集成都通过自动构建(包括测试)进行验证,以尽快检测集成错误。
工具
有许多工具可将您的工作流程与 CI/CD 集成,但在本教程中,我们将使用 Github 来存储我们的代码,并使用 TravisCI 工具来设置 CI。关于语言和框架,我们将使用C#和新的.NET Core 3.0。
要求
为了使这项工作成功,您需要三件简单的事情:
1.Visual Studio 2019最新版本 2.Github账号 3. Travis-CI 帐户链接到您的 Github 帐户
项目
在本教程中,我们将使用一个简单的计算器。我们将创建一个库、一个命令行工具和一个测试项目来测试一切。
当我们设置 CI 时,这个测试项目也将运行,这意味着如果将来我们对代码进行更改并且我们最初创建的测试未通过,我们将收到通知,或者我们可以简单地拒绝拉取请求。
创建 Github 存储库
首先,我们将创建一个 Github 存储库,因此请访问 Github 并创建存储库并将其克隆到本地环境。我决定将这个新存储库命名为 CalculatorCLI-demo。
<imgalign=“中心”src=“https://i.gyazo.com/0657eb2bdeb3c331b9e4585d7deed5ef.png">
创建解决方案
现在让我们在克隆存储库的根文件夹中创建一个名为 CalculatorCLI 的空解决方案
核心库
就像在现实世界的项目中一样,我们将把逻辑存储在一个生成库的单独项目中,所以让我们创建它。
去创建一个 Class Library (.NET Standard) 并将其命名为 CalculatorCLI.Core
NET 核心版本创建项目后,请转到项目属性并将 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");
}
}
}
}
命令行界面
现在我们已经有了核心项目,让我们创建应用程序。在本例中,它将是一个简单的控制台应用程序,它接受参数并显示输出结果。
那么让我们继续创建一个新的 Console App (.NET Core),我将其命名为 CalculatorCLI.CLI。
NET 核心版本
正如我们之前所做的那样,一旦您创建了项目,请转到项目属性并将 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 核心版本
正如我们之前所做的那样,一旦您创建了项目,请转到项目属性并将 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);
}
}
}
创建完所有内容后,我们最终将得到如下解决方案:
<imgalign=“center"src=“https://i.gyazo.com/ffecc23a14d796af9a46dbb390c0d072.png"/> />
现在我们可以运行测试了,因此请转到 Visual Studio 中的 Test Explorer 并运行它们!

特拉维斯 CI
如果您现在还不知道,TravisCI 是一个托管的持续集成和部署系统。
这里我们必须执行一些步骤,但首先我们将链接我们的 Github 存储库以供 TravisCI 代理监听,以便构建和测试我们的项目。
启用存储库
为此,请登录 Travis CI 页面并转到您的存储库,然后通过单击存储库名称旁边的滑块来过滤您创建的项目并启用它。
<imgalign=“中心”src=“https://i.gyazo.com/28b366dddd3f5caa9100ca6b6d200764.png”>
创建.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,它将在主要逻辑之前获胜。这里,所以我所做的是运行dotnet restore到解决方案,以便稍后一切都完美加载。 - 现在在
script中,我们将对我们的解决方案执行dotnet builld和dotnet test,这将检查它是否已编译,然后运行测试。
我们完成了!
上传到master
现在我们只需要把一切都推向掌握即可。
git add --all
git commit -m "Initial files"
git push
检查持续集成
我们可以在存储库页面或 TravisCI 仪表板中检查推送到 master 的 CI 状态。
进展情况
<imgalign=“中心”src=“https://i.gyazo.com/977ea42a90adccf0736464b6603867a5.png”>”
<imgalign=“中心”src=“https://i.gyazo.com/52a5f9356df5436c862b7df6fe66a9f4.png">”
完成
<imgalign=“中心”src=“https://i.gyazo.com/c3d3521925a3e20bcf55bf5f6a2a711d.png">”
<imgalign=“中心”src=“https://i.gyazo.com/8c749c2ce44837a39fc5cd3e8838a798.png">”
让我们打破它
现在为了看看它有多强大,让我们破坏代码并更改核心库以使其失败。
代码更改
因此,请转到 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 情况失败了:
<imgalign=“中心”src=“https://i.gyazo.com/a57d0a0f8c07cc1ab6e4f55a8466cbbd.png">
现在提交此更改并将其推送到 master,然后等待 TravisCI 代理的结果。
git add --all
git commit -m "Breaking changes"
git push
构建
现在让我们查看 TravisCI 日志,我们将看到我们成功地破坏了项目,因为集成测试失败并且构建状态为错误。
<imgalign=“中心”src=“https://i.gyazo.com/397befe9b7f5a32b6e97511733296b00.png">”
在日志的最后我们可以看到错误本身:
<imgalign=“中心”src=“https://i.gyazo.com/e5cbf32c5dd7a08bf6628d81edff3130.png">”
让我们再次修复它!
现在恢复我们所做的并将代码推送到 master,并检查新构建的状态。
测试成功通过:
<imgalign=“中心”src=“https://i.gyazo.com/02deb8fcb4fca618ff2d79f1c27c6df5.png">”
并且构建也成功了:
<imgalign=“中心”src=“https://i.gyazo.com/38a227f0634c6040c6608f8c51f36cd3.png">”
结论
真的很强大,CI和CD很早以前就已经存在了,但现在让它在每个项目中运行都非常简单,不管它有多小或多简单。从我的角度来看,每个人至少应该为他们的每个项目设置 CI,因为这是一个很好的实践,它最终会节省你调试和查找错误的时间,如果你设置了正确的 tests 和 CI,这些错误就不会发生。
就是这样
这就是如何创建一个 .NET Core 3.0 解决方案,该解决方案在使用 TravisCI 的每个构建上进行持续集成并将代码存储在 Github 中。
您可以在此处找到该项目的源代码。