التكامل المستمر لمشروع .NET Core 3.0 باستخدام TravisCI
قررت في نهاية الأسبوع الماضي أنني أريد أن أبدأ بشكل صحيح مشروع أداة تنزيل الكاشطة والمدقق الذي كنت أقوم به في مستودعات مختلفة.
بعد البدء بمشروع آخر، يجب أن يكون هذا رائعًا، مثل الرائع الحقيقي، باستخدام CI/CD، وطلب السحب، والوثائق، والشارات في الملف التمهيدي، وكل ما رأيته رائعًا وهو بالفعل أفضل الممارسات.
وبعد عطلة نهاية أسبوع جيدة، انتهيت من إنشاء Dramarr، وهي مجموعة من الأدوات التي تقوم بإلغاء العروض وتنزيلها من مصادر مختلفة.
يحتوي على مستودعات مختلفة في المؤسسة ومعظمها عبارة عن مكتبات يتم تجميعها واختبارها ونشرها في طلب السحب وعند دمجها في الفرع الرئيسي.
وهذا ما يطلق عليه CI/CD أو التكامل المستمر/التسليم المستمر.
ولكن في هذا البرنامج التعليمي سنتحدث فقط عن CI.
التكامل المستمر
ما هو؟
مأخوذة من [مدونة] مارتن فاولر(https://martinfowler.com/articles/continuousIntegration.html)، وهو أفضل شرح قرأته:
التكامل المستمر هو ممارسة لتطوير البرمجيات حيث يقوم أعضاء الفريق بدمج عملهم بشكل متكرر، وعادةً ما يتكامل كل شخص يوميًا على الأقل - مما يؤدي إلى عمليات تكامل متعددة يوميًا. يتم التحقق من كل تكامل من خلال إنشاء تلقائي (بما في ذلك الاختبار) لاكتشاف أخطاء التكامل في أسرع وقت ممكن.
الأدوات
هناك العديد من الأدوات لدمج سير العمل الخاص بك مع CI/CD، ولكن في هذا البرنامج التعليمي سنستخدم Github لتخزين التعليمات البرمجية الخاصة بنا وأدوات TravisCI لإعداد CI. فيما يتعلق باللغة والأطر، سوف نستخدم C# وnew.NET Core 3.0.
#المتطلبات
لإنجاز هذا العمل، تحتاج إلى ثلاثة أشياء بسيطة:
- أحدث إصدار من فيجوال ستوديو 2019
- حساب جيثب
- حساب Travis-CI مرتبط بحساب Github الخاص بك
#مشروع
من أجل هذا البرنامج التعليمي، سنقوم بعمل آلة حاسبة بسيطة. سنقوم بإنشاء مكتبة وأداة سطر أوامر ومشروع اختبار لاختبار كل شيء.
سيتم أيضًا تشغيل مشروع الاختبار هذا عندما نقوم بإعداد CI، مما يعني أنه إذا قمنا في المستقبل بإجراء تغيير على الكود ولم تنجح الاختبارات التي أنشأناها في البداية، فسنتلقى إشعارًا أو يمكننا ببساطة رفض طلب السحب.
إنشاء مستودع جيثب
أولاً، سنقوم بإنشاء مستودع Github، لذا تواصل مع Github وقم بإنشاء المستودع واستنساخه في بيئتك المحلية. قررت استدعاء هذا المستودع الجديد CalculatorCLI-demo.

خلق الحل
لنقم الآن بإنشاء حل فارغ يسمى 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
رمز ذلك بسيط جدًا، إذا لم يتطابق مع نمط regex معين، فهو استدعاء خاطئ ويستدعي 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);
}
}
}
مع كل شيء تم إنشاؤه سننتهي بحل مثل هذا:

وبهذا يمكننا الآن إجراء الاختبارات، لذا انتقل إلى Test Explorer في Visual Studio وقم بتشغيلها!

#ترافيس سي.آي
إذا لم تقم بذلك بالفعل، فإن 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والذي سيتم تشغيله قبل المنطق الرئيسي .هنا، لذلك ما وضعته هو تشغيل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.
يمكنك العثور على الكود المصدري لهذا المشروع هنا.