اختبار الوحدة الصافية: أنفق مقدمًا للادخار لاحقًا
نشرت: 2022-03-11غالبًا ما يكون هناك الكثير من الالتباس والشك فيما يتعلق باختبار الوحدة عند مناقشتها مع أصحاب المصلحة والعملاء. يبدو اختبار الوحدة أحيانًا بالطريقة التي يتعامل بها استخدام الخيط مع الطفل ، "أنا بالفعل أقوم بتنظيف أسناني ، فلماذا أحتاج إلى القيام بذلك؟"
غالبًا ما يبدو اقتراح اختبار الوحدة كمصروفات غير ضرورية للأشخاص الذين يعتبرون طرق الاختبار واختبار قبول المستخدم قوية بما فيه الكفاية.
لكن اختبارات الوحدة هي أداة قوية جدًا وهي أبسط مما قد تعتقد. في هذه المقالة ، سوف نلقي نظرة على اختبار الوحدة والأدوات المتوفرة في DotNet مثل Microsoft.VisualStudio.TestTools و Moq .
سنحاول بناء مكتبة صفية بسيطة تحسب المصطلح التاسع في تسلسل فيبوناتشي. للقيام بذلك ، سنرغب في إنشاء فئة لحساب تسلسلات فيبوناتشي تعتمد على فئة الرياضيات المخصصة التي تجمع الأرقام معًا. بعد ذلك ، يمكننا استخدام .NET Testing Framework لضمان تشغيل برنامجنا كما هو متوقع.
ما هو اختبار الوحدة؟
يقوم اختبار الوحدة بتقسيم البرنامج إلى أصغر جزء من التعليمات البرمجية ، وعادة ما يكون على مستوى الوظيفة ، ويضمن أن تقوم الوظيفة بإرجاع القيمة التي يتوقعها المرء. باستخدام إطار اختبار الوحدة ، تصبح اختبارات الوحدة كيانًا منفصلاً يمكنه بعد ذلك تشغيل الاختبارات الآلية على البرنامج أثناء بنائه.
[TestClass] public class FibonacciTests { [TestMethod] //Check the first value we calculate public void Fibonacci_GetNthTerm_Input2_AssertResult1() { //Arrange int n = 2; //setup Mock<UnitTests.IMath> mockMath = new Mock<UnitTests.IMath>(); mockMath .Setup(r => r.Add(It.IsAny<int>(), It.IsAny<int>())) .Returns((int x, int y) => x + y); UnitTests.Fibonacci fibonacci = new UnitTests.Fibonacci(mockMath.Object); //Act int result = fibonacci.GetNthTerm(n); //Assert Assert.AreEqual(result, 1); } }
اختبار وحدة بسيط باستخدام اختبار المنهجية Arrange ، Act ، Assert الذي يمكن لمكتبة الرياضيات لدينا إضافة 2 + 2 بشكل صحيح.
بمجرد إعداد اختبارات الوحدة ، إذا تم إجراء تغيير على الكود ، لحساب شرط إضافي لم يكن معروفًا عند تطوير البرنامج لأول مرة ، على سبيل المثال ، ستظهر اختبارات الوحدة إذا كانت جميع الحالات تتطابق مع القيم المتوقعة الناتج عن طريق الوظيفة.
اختبار الوحدة ليس اختبار تكامل. إنه ليس اختبارًا شاملاً. في حين أن كلتا هاتين المنهجيتين قوية ، يجب أن تعمل جنبًا إلى جنب مع اختبار الوحدة - وليس كبديل.
فوائد اختبار الوحدة والغرض منه
أصعب فائدة لفهم اختبار الوحدة ، ولكن الأهم ، هو القدرة على إعادة اختبار الكود المتغير بسرعة. والسبب في صعوبة فهمه هو أن العديد من المطورين يفكرون في أنفسهم ، "لن أتطرق إلى هذه الوظيفة مرة أخرى" ، أو "سأعيد اختبارها فقط عندما أنتهي منها." ويفكر أصحاب المصلحة من منظور ، "إذا كانت هذه القطعة مكتوبة بالفعل ، فلماذا أحتاج إلى إعادة اختبارها؟"
بصفتي شخصًا كان على جانبي طيف التنمية ، فقد قلت هذين الأمرين. يعرف المطور بداخلي لماذا يتعين علينا إعادة اختباره.
يمكن أن يكون للتغييرات التي نجريها على أساس يومي تأثيرات هائلة. علي سبيل المثال:
- هل يفسر مفتاحك القيمة الجديدة التي تضعها بشكل صحيح؟
- هل تعرف عدد المرات التي استخدمت فيها هذا المفتاح؟
- هل قمت بحساب مقارنات السلاسل غير الحساسة لحالة الأحرف بشكل صحيح؟
- هل تقوم بالتحقق من القيم الخالية بشكل مناسب؟
- هل يتم التعامل مع استثناء رمي كما توقعت؟
يأخذ اختبار الوحدة هذه الأسئلة ويذكرها في رمز وعملية لضمان الإجابة على هذه الأسئلة دائمًا. يمكن إجراء اختبارات الوحدة قبل الإنشاء للتأكد من عدم إدخال أخطاء جديدة. نظرًا لأن اختبارات الوحدة مصممة لتكون ذرية ، يتم تشغيلها بسرعة كبيرة ، وعادة ما تكون أقل من 10 مللي ثانية لكل اختبار. حتى في التطبيقات الكبيرة جدًا ، يمكن إجراء مجموعة اختبار كاملة في أقل من ساعة. هل يمكن أن تتطابق عملية UAT الخاصة بك مع ذلك؟
Fibonacci_GetNthTerm_Input2_AssertResult1
وهو أول تشغيل ويتضمن وقت الإعداد ، تعمل جميع اختبارات الوحدة في أقل من 5 مللي ثانية. تم إعداد اصطلاح التسمية الخاص بي هنا للبحث بسهولة عن فئة أو طريقة داخل الفصل الدراسي الذي أريد اختباره
بصفتك مطورًا ، ربما يبدو هذا مجرد مزيد من العمل بالنسبة لك. نعم ، ستشعر براحة البال بأن الكود الذي ستطلقه جيد. لكن اختبار الوحدة يوفر لك أيضًا فرصة لمعرفة أين يكون تصميمك ضعيفًا. هل تكتب نفس اختبارات الوحدة لقطعتين من التعليمات البرمجية؟ هل يجب أن يكونوا على قطعة واحدة من التعليمات البرمجية بدلاً من ذلك؟
يعد جعل الكود الخاص بك قابلاً للاختبار في حد ذاته طريقة لتحسين تصميمك. وبالنسبة لمعظم المطورين الذين لم يسبق لهم اختبار الوحدة ، أو لم يأخذوا الكثير من الوقت للنظر في التصميم قبل الترميز ، يمكنك إدراك مدى تحسن تصميمك من خلال جعله جاهزًا لاختبار الوحدة.
هل وحدة التعليمات البرمجية الخاصة بك قابلة للاختبار؟
إلى جانب DRY ، لدينا أيضًا اعتبارات أخرى.
هل أساليبك أو وظائفك تحاول أن تفعل الكثير؟
إذا كنت بحاجة إلى كتابة اختبارات وحدة مفرطة التعقيد تعمل لفترة أطول مما تتوقع ، فقد تكون طريقتك معقدة للغاية وأكثر ملاءمة كطرق متعددة.
هل تستفيد بشكل صحيح من حقن التبعية؟
إذا كانت طريقتك قيد الاختبار تتطلب فئة أو وظيفة أخرى ، فإننا نسمي هذا التبعية. في اختبار الوحدة ، لا نهتم بما تفعله التبعية تحت الغطاء ؛ لغرض الطريقة قيد الاختبار ، هو صندوق أسود. التبعية لها مجموعتها الخاصة من اختبارات الوحدة التي ستحدد ما إذا كان سلوكها يعمل بشكل صحيح.
بصفتك مختبِرًا ، فأنت تريد محاكاة تلك التبعية وإخبارها بالقيم التي يجب إرجاعها في حالات محددة. سيعطيك هذا تحكمًا أكبر في حالات الاختبار الخاصة بك. للقيام بذلك ، سوف تحتاج إلى حقن نسخة وهمية (أو كما سنرى لاحقًا ، تم الاستهزاء بها) من تلك التبعية.
هل تتفاعل مكوناتك مع بعضها البعض كيف تتوقع؟
بمجرد تحديد التبعيات الخاصة بك وحقن التبعية ، قد تجد أنك أدخلت التبعيات الدورية في التعليمات البرمجية الخاصة بك. إذا كانت الفئة أ تعتمد على الفئة ب ، والتي بدورها تعتمد على الفئة أ ، فيجب عليك إعادة النظر في التصميم الخاص بك.
جمال حقن التبعية
لنفكر في مثالنا فيبوناتشي. يخبرك رئيسك في العمل أن لديهم فئة جديدة أكثر كفاءة ودقة من عامل الإضافة الحالي المتاح في C #.
على الرغم من أن هذا المثال بالذات ليس محتملًا جدًا في العالم الحقيقي ، إلا أننا نرى أمثلة مماثلة في مكونات أخرى ، مثل المصادقة وتخطيط الكائن وأي عملية خوارزمية تقريبًا. لغرض هذه المقالة ، دعنا نتخيل فقط أن وظيفة الإضافة الجديدة لعميلك هي الأحدث والأكبر منذ اختراع أجهزة الكمبيوتر.
على هذا النحو ، يمنحك رئيسك مكتبة الصندوق الأسود مع فصل واحد Math
، وفي هذا الفصل ، وظيفة واحدة Add
. من المرجح أن تبدو وظيفتك المتمثلة في تطبيق حاسبة فيبوناتشي على النحو التالي:
public int GetNthTerm(int n) { Math math = new Math(); int nMinusTwoTerm = 1; int nMinusOneTerm = 1; int newTerm = 0; for (int i = 2; i < n; i++) { newTerm = math.Add(nMinusOneTerm, nMinusTwoTerm); nMinusTwoTerm = nMinusOneTerm; nMinusOneTerm = newTerm; } return newTerm; }
هذا ليس بشعا. يمكنك إنشاء فئة جديدة Math
واستخدامها لإضافة المصطلحين السابقين للحصول على المصطلح التالي. يمكنك تشغيل هذه الطريقة من خلال مجموعة الاختبارات العادية ، وحساب ما يصل إلى 100 مصطلح ، وحساب الحد الألف ، والمصطلح 10000 ، وما إلى ذلك حتى تشعر بالرضا عن أن منهجيتك تعمل بشكل جيد. ثم في وقت ما في المستقبل ، يشتكي المستخدم من أن المصطلح 501 لا يعمل كما هو متوقع. تقضي المساء في البحث في التعليمات البرمجية الخاصة بك ومحاولة اكتشاف سبب عدم نجاح هذه الحالة الركنية. تبدأ في الشك في أن أحدث وأروع فصل في Math
ليس بالقدر الذي يظنه رئيسك في العمل. لكنه صندوق أسود ولا يمكنك إثبات ذلك حقًا - لقد وصلت إلى طريق مسدود داخليًا.
المشكلة هنا هي أن التبعية Math
لا تُحقن في حاسبة فيبوناتشي الخاصة بك. لذلك ، في اختباراتك ، فإنك تعتمد دائمًا على النتائج الحالية وغير المختبرة وغير المعروفة من Math
لاختبار فيبوناتشي ضدها. إذا كانت هناك مشكلة في Math
، فسيكون فيبوناتشي دائمًا على خطأ (بدون ترميز حالة خاصة للمصطلح 501).
فكرة تصحيح هذه المشكلة هي حقن فئة Math
في حاسبة فيبوناتشي الخاصة بك. ولكن الأفضل من ذلك ، هو إنشاء واجهة لفصل Math
التي تحدد الطرق العامة (في حالتنا ، Add
) وتنفيذ الواجهة في فصل Math
لدينا.
public interface IMath { int Add(int x, int y); } public class Math : IMath { public int Add(int x, int y) { //super secret implementation here } } }
بدلاً من حقن فئة Math
في فيبوناتشي ، يمكننا حقن واجهة IMath
في فيبوناتشي. الفائدة هنا هي أنه يمكننا تحديد فئة OurMath
الخاصة بنا والتي نعلم أنها دقيقة واختبار الآلة الحاسبة الخاصة بنا مقابل ذلك. والأفضل من ذلك ، باستخدام Moq ، يمكننا ببساطة تحديد ما Math.Add
. يمكننا تحديد عدد من المجاميع أو يمكننا فقط إخبار Math.Add
، إضافة لإرجاع x + y.
private IMath _math; public Fibonacci(IMath math) { _math = math; }
قم بإدخال واجهة IMath في فئة Fibonacci

//setup Mock<UnitTests.IMath> mockMath = new Mock<UnitTests.IMath>(); mockMath .Setup(r => r.Add(It.IsAny<int>(), It.IsAny<int>())) .Returns((int x, int y) => x + y);
استخدام Moq لتحديد ما ترجع Math.Add
.
الآن لدينا طريقة مجربة وصحيحة (حسنًا ، إذا كان هذا المشغل + خاطئًا في C # ، فلدينا مشكلات أكبر) لإضافة رقمين. باستخدام IMath
الجديد الخاص بنا ، يمكننا ترميز اختبار الوحدة لمصطلحنا 501 ومعرفة ما إذا كنا قد أخطأنا في تنفيذنا أو إذا كان فصل Math
المخصص يحتاج إلى مزيد من العمل.
لا تدع طريقة تحاول أن تفعل الكثير
يشير هذا المثال أيضًا إلى فكرة أن الطريقة تفعل الكثير. من المؤكد أن الإضافة عملية بسيطة إلى حد ما دون الحاجة إلى تجريد وظائفها بعيدًا عن طريقة GetNthTerm
بنا. لكن ماذا لو كانت العملية أكثر تعقيدًا؟ بدلاً من الإضافة ، ربما كان ذلك بمثابة التحقق من صحة النموذج ، أو استدعاء مصنع للحصول على كائن للعمل عليه ، أو جمع البيانات الإضافية المطلوبة من المستودع.
سيحاول معظم المطورين التمسك بفكرة أن طريقة واحدة لها غرض واحد. في اختبار الوحدة ، نحاول التمسك بالمبدأ القائل بوجوب تطبيق اختبارات الوحدة على الطرق الذرية ومن خلال إدخال عدد كبير جدًا من العمليات على طريقة ما ، فإننا نجعلها غير قابلة للاختبار. يمكننا غالبًا إنشاء مشكلة حيث يتعين علينا كتابة العديد من الاختبارات لاختبار وظيفتنا بشكل صحيح.
كل معلمة نضيفها إلى طريقة تزيد من عدد الاختبارات التي يتعين علينا كتابتها بشكل أسي وفقًا لتعقيد المعلمة. إذا أضفت منطقية إلى منطقك ، فأنت بحاجة إلى مضاعفة عدد الاختبارات للكتابة حيث تحتاج الآن إلى التحقق من حالات الصواب والخطأ جنبًا إلى جنب مع اختباراتك الحالية. في حالة التحقق من صحة النموذج ، يمكن أن يزداد تعقيد اختبارات الوحدة لدينا بسرعة كبيرة.
نحن جميعًا مذنبون بإضافة القليل إلى طريقة ما. لكن هذه الأساليب الأكبر والأكثر تعقيدًا تخلق الحاجة إلى عدد كبير جدًا من اختبارات الوحدة. وسرعان ما يتضح عند كتابة اختبارات الوحدة أن الطريقة تحاول فعل الكثير. إذا كنت تشعر أنك تحاول اختبار عدد كبير جدًا من النتائج المحتملة من معلمات الإدخال ، ففكر في حقيقة أن طريقتك تحتاج إلى تقسيمها إلى سلسلة من المعطيات الأصغر.
لا تكرر نفسك
أحد المستأجرين المفضلين لدينا في البرمجة. هذا يجب أن يكون مستقيمًا إلى حد ما. إذا وجدت نفسك تكتب نفس الاختبارات أكثر من مرة ، فقد أدخلت الكود أكثر من مرة. قد يفيدك إعادة بناء هذا العمل في فئة مشتركة يمكن الوصول إليها في كلتا الحالتين اللتين تحاولان استخدامه.
ما هي أدوات اختبار الوحدة المتوفرة؟
تقدم لنا DotNet منصة اختبار وحدة قوية للغاية خارج الصندوق. باستخدام هذا ، يمكنك تنفيذ ما يعرف بمنهجية الترتيب ، والتنفيذ ، والتأكيد. أنت ترتب اعتباراتك الأولية ، وتتصرف وفقًا لهذه الشروط مع طريقتك قيد الاختبار ، ثم تؤكد أن شيئًا ما قد حدث. يمكنك تأكيد أي شيء ، مما يجعل هذه الأداة أكثر قوة. يمكنك التأكيد على أن الطريقة كانت تسمى عددًا محددًا من المرات ، أو أن الطريقة أعادت قيمة معينة ، أو أن نوعًا معينًا من الاستثناءات تم طرحه ، أو أي شيء آخر يمكنك التفكير فيه. لأولئك الذين يبحثون عن إطار عمل أكثر تقدمًا ، تعد NUnit ونظيرتها في Java JUnit خيارات قابلة للتطبيق.
[TestMethod] //Test To Verify Add Never Called on the First Term public void Fibonacci_GetNthTerm_Input0_AssertAddNeverCalled() { //Arrange int n = 0; //setup Mock<UnitTests.IMath> mockMath = new Mock<UnitTests.IMath>(); mockMath .Setup(r => r.Add(It.IsAny<int>(), It.IsAny<int>())) .Returns((int x, int y) => x + y); UnitTests.Fibonacci fibonacci = new UnitTests.Fibonacci(mockMath.Object); //Act int result = fibonacci.GetNthTerm(n); //Assert mockMath.Verify(r => r.Add(It.IsAny<int>(), It.IsAny<int>()), Times.Never); }
اختبار أن طريقة فيبوناتشي الخاصة بنا تتعامل مع الأرقام السالبة عن طريق طرح استثناء. يمكن أن تتحقق اختبارات الوحدة من طرح الاستثناء.
للتعامل مع حقن التبعية ، يوجد كل من Ninject و Unity على منصة DotNet. هناك اختلاف بسيط للغاية بين الاثنين ، ويصبح الأمر يتعلق بما إذا كنت تريد إدارة التكوينات باستخدام Fluent Syntax أو تكوين XML.
لمحاكاة التبعيات ، أوصي Moq. قد يمثل Moq تحديًا في الحصول على يديك ، ولكن الجوهر هو إنشاء نسخة مستهزأة من تبعياتك. بعد ذلك ، تخبر التبعية بما يجب إرجاعه في ظل ظروف محددة. على سبيل المثال ، إذا كانت لديك عملية باسم Square(int x)
تربيع العدد الصحيح ، فيمكنك إخبارها عندما تكون x = 2 ، وإرجاع 4. يمكنك أيضًا إخبارها بإرجاع x ^ 2 لأي عدد صحيح. أو يمكنك إخباره بإرجاع 5 عندما x = 2. لماذا تقوم بتنفيذ الحالة الأخيرة؟ في حالة ما إذا كانت الطريقة تحت دور الاختبار هي التحقق من صحة الإجابة من التبعية ، فقد ترغب في إجبار الإجابات غير الصالحة على العودة للتأكد من أنك تكتشف الخطأ بشكل صحيح.
[TestMethod] //Test To Verify Add Called Three times on the fifth Term public void Fibonacci_GetNthTerm_Input4_AssertAddCalledThreeTimes() { //Arrange int n = 4; //setup Mock<UnitTests.IMath> mockMath = new Mock<UnitTests.IMath>(); mockMath .Setup(r => r.Add(It.IsAny<int>(), It.IsAny<int>())) .Returns((int x, int y) => x + y); UnitTests.Fibonacci fibonacci = new UnitTests.Fibonacci(mockMath.Object); //Act int result = fibonacci.GetNthTerm(n); //Assert mockMath.Verify(r => r.Add(It.IsAny<int>(), It.IsAny<int>()), Times.Exactly(3)); }
استخدام Moq لإخبار واجهة IMath
بها عن كيفية التعامل مع Add
قيد الاختبار. يمكنك تعيين الحالات الصريحة باستخدام It.Is
أو نطاق باستخدام It.IsInRange
.
أطر عمل اختبار الوحدة لـ DotNet
إطار اختبار وحدة Microsoft
يعد Microsoft Unit Testing Framework حل اختبار الوحدة الجاهز من Microsoft وهو مضمن مع Visual Studio. لأنه يأتي مع VS ، فإنه يتكامل معه بشكل جيد. عندما تبدأ مشروعًا ، سيسألك Visual Studio عما إذا كنت تريد إنشاء مكتبة اختبار وحدة بجانب التطبيق الخاص بك.
يأتي Microsoft Unit Testing Framework أيضًا مع عدد من الأدوات لمساعدتك على تحليل إجراءات الاختبار بشكل أفضل. أيضًا ، نظرًا لأنه مملوك ومكتوب من قبل Microsoft ، هناك بعض الشعور بالاستقرار في وجودها في المستقبل.
ولكن عند العمل باستخدام أدوات Microsoft ، فإنك تحصل على ما تقدمه لك. يمكن أن يكون تكامل إطار عمل اختبار الوحدة من Microsoft مرهقًا.
لا
أكبر ميزة بالنسبة لي في استخدام NUnit هي الاختبارات ذات المعلمات. في مثال فيبوناتشي أعلاه ، يمكننا إدخال عدد من حالات الاختبار والتأكد من صحة هذه النتائج. وفي حالة مشكلة 501 ، يمكننا دائمًا إضافة مجموعة معلمات جديدة لضمان تشغيل الاختبار دائمًا دون الحاجة إلى طريقة اختبار جديدة.
العيب الرئيسي لـ NUnit هو دمجه في Visual Studio. إنه يفتقر إلى الأجراس والصفارات التي تأتي مع إصدار Microsoft ويعني أنك ستحتاج إلى تنزيل مجموعة الأدوات الخاصة بك.
xUnit.Net
تحظى xUnit بشعبية كبيرة في C # لأنها تتكامل بشكل جيد مع نظام .NET البيئي الحالي. يحتوي Nuget على العديد من امتدادات xUnit المتاحة. كما أنه يتكامل بشكل جيد مع Team Foundation Server ، على الرغم من أنني لست متأكدًا من عدد مطوري .NET الذين ما زالوا يستخدمون TFS عبر تطبيقات Git المختلفة.
على الجانب السلبي ، يشتكي العديد من المستخدمين من أن وثائق xUnit تفتقر إلى حد ما. بالنسبة للمستخدمين الجدد لاختبار الوحدة ، يمكن أن يتسبب ذلك في حدوث صداع هائل. بالإضافة إلى ذلك ، فإن قابلية توسيع xUnit وقدرتها على التكيف تجعل منحنى التعلم أكثر حدة من NUnit أو إطار اختبار الوحدة من Microsoft.
اختبار التصميم / التطوير
يعد التصميم / التطوير القائم على الاختبار (TDD) موضوعًا أكثر تقدمًا قليلاً ويستحق المنشور الخاص به. ومع ذلك ، أردت تقديم مقدمة.
تكمن الفكرة في البدء باختبارات الوحدة الخاصة بك وإخبار اختبارات وحدتك بما هو صحيح. بعد ذلك ، يمكنك كتابة التعليمات البرمجية الخاصة بك حول تلك الاختبارات. من الناحية النظرية ، يبدو المفهوم بسيطًا ، ولكن من الناحية العملية ، من الصعب جدًا تدريب عقلك على التفكير في الوراء حول التطبيق. لكن هذا النهج له فائدة داخلية تتمثل في عدم مطالبتك بكتابة اختبارات الوحدة الخاصة بك بعد الحقيقة. يؤدي هذا إلى تقليل إعادة بناء الهيكل ، وإعادة الكتابة ، والارتباك الطبقي.
لطالما كانت TDD كلمة رنانة إلى حد ما في السنوات الأخيرة ولكن اعتمادها كان بطيئًا. طبيعتها المفاهيمية مربكة لأصحاب المصلحة مما يجعل من الصعب الحصول على الموافقة. لكن بصفتي مطورًا ، أشجعك على كتابة حتى تطبيق صغير باستخدام نهج TDD لتعتاد على العملية.
لماذا لا يمكنك إجراء عدد كبير جدًا من اختبارات الوحدة
يعد اختبار الوحدة أحد أقوى أدوات الاختبار التي يمتلكها المطورون. إنه لا يكفي بأي حال من الأحوال لإجراء اختبار كامل لتطبيقك ، ولكن فوائده في اختبار الانحدار وتصميم الكود وتوثيق الغرض لا مثيل لها.
لا يوجد شيء مثل كتابة الكثير من اختبارات الوحدة. يمكن أن تقترح كل حالة حافة مشاكل كبيرة أسفل الخط في برنامجك. يمكن أن يضمن إحياء ذكرى الأخطاء المكتشفة مثل اختبارات الوحدة أن تلك الأخطاء لا تجد طرقًا للتسلل مرة أخرى إلى برنامجك أثناء تغييرات التعليمات البرمجية اللاحقة. بينما يمكنك إضافة 10-20٪ إلى الميزانية الأولية لمشروعك ، يمكنك توفير أكثر من ذلك بكثير في التدريب وإصلاحات الأخطاء والوثائق.
يمكنك العثور على مستودع Bitbucket المستخدم في هذه المقالة هنا.