سلسلة net. للمحترفين اختبار الوحدات unit testing في dot NET


رضوى العربي

يُنصح بتعزيز المشروع الخاص بك بتقنية اختبار الوَحْدَات (unit testing)، حيث يُوفِّر ذلك العديد من المزايا منها:

  • سهولة إضافة خاصيات جديدة مع ضمان استمرارية عمل الشيفرة القديمة بطريقة سليمة.
  • توفير توثيق برمجي لخاصيات المشروع.
  • كتابة شيفرة أفضل من خلال تعزيز اِستخدَام الواجهات.

نصائح لكتابة اختبار الوحدات

  1. ينبغي أن يتكون اسم الاختبار من ثلاثة أجزاء:
  • اسم التابع تحت الاختبار.
  • وصف للموقف المُراد اختباره.
  • وصف التصرف المُتوقع عند حدوث هذا الموقف.

مثال لاسم سئ:

public void Test_Single()
{
    var stringCalculator = new StringCalculator();

    var actual = stringCalculator.Add("0");

    Assert.Equal(0, actual);
}

مثال لاسم جيد:

public void Add_SingleNumber_ReturnsSameNumber()
{
    //
}
  1. يَتكون أي اختبار من ثلاث خطوات:
  • خطوة الإِعداد (arrange): يتم خلالها الإِعداد للاختبار بتجهيز الكائنات والنُسخ المُزيَّفة (mocks) وتوابعها بحيث يقتصِر الاختبار على ما نُريد فَحْصه فقط بمَعزَل عن بقية التَبَعيّات (dependencies) وغيره مما لا يَشمَله الاختبار.
  • خطوة التَّنْفيذ (act): يتم خلالها الاستدعاء الفعلّي للتابع تحت الاختبار.
  • خطوة الفَحْص (assert): يتم خلالها إِجراء الفُحوصات المَطلوبة.

انظر المثال التالي:

public void Add_EmptyString_ReturnsZero()
{
    // Arrange
    var stringCalculator = new StringCalculator();

    // Act
    var actual = stringCalculator.Add("");

    // Assert
    Assert.Equal(0, actual);
}
  1. تجنب كتابة اختبارات تحتوي على شروط منطقية مثل if و for و while ..إلخ؛ لأن ذلك سيزيد من احتمالية وجود أخطاء برمجية بالاختبار، وهذا آخر ما تود أن يحدث. لابُدّ لاختبارات الوحدة أن تكون خالية تمامًا من الأخطاء حتى تكون محلًا للثقة.

مثال سئ:

[Fact]
public void Add_MultipleNumbers_ReturnsCorrectResults()
{
    var stringCalculator = new StringCalculator();
    var expected = 0;
    var testCases = new[]
    {
        "0,0,0",
        "0,1,2",
        "1,2,3"
    };

    foreach (var test in testCases)
    {
        Assert.Equal(expected, stringCalculator.Add(test));
        expected += 3;
    }
}

مثال أفضل:

[Theory]
[InlineData("0,0,0", 0)]
[InlineData("0,1,2", 3)]
[InlineData("1,2,3", 6)]
public void Add_MultipleNumbers_ReturnsSumOfNumbers(string input, int expected)
{
    var stringCalculator = new StringCalculator();

    var actual = stringCalculator.Add(input);

    Assert.Equal(expected, actual);
}
  1. لابُدّ أن يحتوي كل اختبار على جملة فحص وحيدة. إذا كنت تريد إجراء أكثر من فحص لموقف معين، يُمكنك إنشاء اختبار منفصل لكل جملة فحص، مما سيُعطيك تصور أوضح لسبب فشل الاختبار.

مثال سئ:

[Fact]
public void Add_EdgeCases_ThrowsArgumentExceptions()
{
    Assert.Throws<ArgumentException>(() => stringCalculator.Add(null));
    Assert.Throws<ArgumentException>(() => stringCalculator.Add("a"));
}

مثال أفضل:

[Theory]
[InlineData(null)]
[InlineData("a")]
public void Add_InputNullOrAlphabetic_ThrowsArgumentException(string input)
{
    var stringCalculator = new StringCalculator();

    Action actual = () => stringCalculator.Add(input);

    Assert.Throws<ArgumentException>(actual);
}
  1. افحص التوابع العامة (public) فقط، وتجنب فحص التوابع الخاصة (private) فهي بالنهاية من المُفترض أن تُنفَّذ أثناء تَّنْفيذ احدى التوابع العامة. علاوة على ذلك فإنها تُعدّ مجرد تفصيلة صغيرة ضمن تَّنفيذ معين (implementation). قد يُغيّر المُبرمج في الواقع من طريقة التنفيذ بدون أن يؤثر على الوظيفة الفعلية الواقعة تحت الاختبار. لذا لا تُقيد اختباراتك على تَّنْفيذ بعينه.

  2. عند الحاجة لتهيئة نفس الكائنات لجميع الاختبارات، استخدم توابع مُساعدة (helper methods) بدلًا من اِستخدَام التابعين setup و teardown. يلجأ الكثير من المُبرمجين إلى اضافة شيفرات التهيئة بهما نظرًا لأنهما يُستدعيان تلقائيا قبل بدء تنفيذ أي اختبار وبعد انتهاء تنفيذه على الترتيب، لكن يؤدي ذلك إلى صعوبة قراءة الاختبار كما قد يؤدي أحيانًا إلى تهيئة كائنات غير مطلوبة لبعض الاختبارات.

مثال للطريقة غير السليمة:

private readonly StringCalculator stringCalculator;
public StringCalculatorTests()
{
    stringCalculator = new StringCalculator();
}

// more tests...

[Fact]
public void Add_TwoNumbers_ReturnsSumOfNumbers()
{
    var result = stringCalculator.Add("0,1");

    Assert.Equal(1, result);
}

مثال للطريقة الأفضل:

[Fact]
public void Add_TwoNumbers_ReturnsSumOfNumbers()
{
    var stringCalculator = CreateDefaultStringCalculator();

    var actual = stringCalculator.Add("0,1");

    Assert.Equal(1, actual);
}

// more tests...

private StringCalculator CreateDefaultStringCalculator()
{
    return new StringCalculator();
}

إضافة مشروع اختبار الوحدات إلى حل موجود مسبقًا

  • انقر بزر الفأرة الأيمن على الحل (solution) واختر أَضِف مشروع جديد.
  • اختر نموذج مشروع اختبار الوَحْدَات (Unit Test Project) من جزء الاختبارات.
  • اختر اسم للمكتبة (assembly). مثلًا إذا كان اسم المشروع المُختبَر Foo، فرُبما تَستخدِم الاسم Foo.Tests.
  • أَضِف مَرجِع (reference) المشروع المُختبَر ضِمْن مَراجِع مشروع اختبار الوَحْدَات.

إضافة تابع بغرض الاختبار

تَتطلَّب MSTest (بيئة العمل الافتراضية للفَحْص) زَخرفة (decoration) الأصناف بغرض الاختبار (test classes) باستخدام السمة TestClass، وكذلك أن تَكون التوابع بغرض الاختبار (test methods) مُزخرَفة باستخدَام السمة TestMethod وأن تَكون عَلنية (public).

في المثال التالي، يَختبِر التابع Test1 كَوْن قيمة المُتغيّر result مُساوِية للقيمة 1.

[TestClass]
public class FizzBuzzFixture
{
    [TestMethod]
    public void Test1()
    {
        //arrange
        var solver = new FizzBuzzSolver();
        //act
        var result = solver.FizzBuzz(1);
        //assert
        Assert.AreEqual("1",result);
    }
}

تتوفَّر الكثير من التوابع لإجراء الفحوصات مثل التابع AreEqual و AreNotEqual و IsTrue وغيرها.

مثال آخر:

[TestClass]
public class UnitTest1
{
    private const string Expected = "Hello World!";

    [TestMethod]
    public void TestMethod1()
    {
        using (var sw = new StringWriter())
        {
            Console.SetOut(sw);
            HelloWorldCore.Program.Main();

            var result = sw.ToString().Trim();
            Assert.AreEqual(Expected, result);
        }
    }
}

مثال آخر باستخدام بيئة عمل NUnit للفَحْص (لاحظ اختلاف السمات المُستخدَمة):

using NUnit.Framework;
using System.IO;
using System;

namespace HelloWorldTests
{
    public class Tests
    {
        private const string Expected = "Hello World!";

        [SetUp]
        public void Setup()
        {
        }
        [Test]
        public void TestMethod1()
        {
            using (var sw = new StringWriter())
            {
                Console.SetOut(sw);
                HelloWorldCore.Program.Main();

                var result = sw.ToString().Trim();
                Assert.AreEqual(Expected, result);
            }
        }
    }
}

تشغيل اختبار الوحدات

افتح نافذة Test Explorer من قائمة Test بفيجوال ستوديو، ثم اُنقر على زر "تشغيل الكل (Run All)" مما سيؤدي إلى بدء تَّنْفيذ جميع الاختبارات.

بعد انتهاء تَّنْفيذ أي اختبار، ستجد علامة بجانب اسمه إما باللون الأخضر أو الأحمر. يُشير اللون الأخضر إلى نجاح الاختبار أما اللون الأحمر فيُشير إلى فشله.

نتائج حية لاختبار الوحدات (Live Unit Testing)

يُوفِّر فيجوال ستوديو بدايةً من الإصدار 2017 خاصية عَرْض نتائج اختبار الوَحْدَات بصورة حيّة. يُمكنك تفعيل هذه الخاصية من خلال فتح قائمة Test ثم النقر على زر "اختبار حيّ للوَحْدَات Live Unit Testing" ثم على زر "ابدأ Start" بالقائمة الفرعية.

ستجد -في حالة وجود اختبار مُناظِر- علامة صح أو خطأ إلى جانب كل تابع بنافذة مُحرِّر الشيفرة (code editor) لتُشير إلى نجاح أو فشل الاختبارات الخاصة به، كما أنها تُحدَّث تلقائيًا بينما تقوم بتعديل الشيفرة.

ترجمة -وبتصرف- للفصل Unit Testing من كتاب ‎.NET Framework Notes for Professionals





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن