كيف ترسم الحدّ بين unit test وintegration test

· آخر تحديث: · · اختبار, unit test, integration test, تصميم الاختبار, تطوير Windows, C# / .NET

سجل التعديلات (3 تحديثات، آخر تحديث 3 Sep، 2026)

سجل بالتغييرات التي أُجريت على هذا المقال. وحيثما حُفظت نسخة سابقة، تبقى متاحة للقراءة عبر رابط دائم يحمل معرّف DOI.

أُضيفت روابط الاستشارة الموجودة في الأصل الياباني (consultation_services). ولم يتغيّر نصّ المقالة نفسه. قراءة النسخة السابقة لهذا التحديث (DOI: 10.5281/zenodo.22240897)
أُعيدَت الترجمة العربية كترجمة كاملة عن النص الياباني الأصلي، وأُضيفَت خريطة المعرفة.
أعيدت الترجمة كترجمة كاملة عن النص الياباني الأصلي. كانت النسخة العربية السابقة مختصراً يسقط أبواباً وجداول ورسوم Mermaid وتعليقات الأشكال وFAQ. أُعيدت هذه العناصر وفق الأصل الياباني، والادّعاءات التقنية مطابقة للنسخة اليابانية.
النشر الأول
الاستشهاد بهذا المقال(DOI: 10.5281/zenodo.21621467)

هذا المقال محفوظ على Zenodo. يرد أدناه معرّف DOI الذي يشير دائمًا إلى أحدث نسخة، ومعرّف DOI المثبَّت على النسخة التي تقرؤها.

小村 豪 (2026). كيف ترسم الحدّ بين unit test وintegration test. شركة كومورا سوفت ذ.م.م.. https://doi.org/10.5281/zenodo.21621467 https://comcomponent.com/ar/blog/2026/03/25/004-unit-test-vs-integration-test-boundary-guide/

DOI (أحدث نسخة)
10.5281/zenodo.21621467
DOI (هذه النسخة)
10.5281/zenodo.22279905

في حديث تصميم الاختبار، يبقى صعباً بهدوء تحديد إلى أين تُدفع الأمور داخل unit test، ومن أين تُرفع إلى integration test.

الخطر هنا هو الطرفان:

  • جعل كلّ شيء unit test لأنك تريد تشغيلاً سريعاً
  • جعل كلّ شيء integration test لأنك تريد قرباً من الشيء الحقيقي

الأوّل يميل إلى أن يمتلئ بالـ mock فيُغفل موضع الكسر في الإنتاج، والثاني يميل إلى مجموعة اختبارات بطيئة هشّة. في العمل اليومي، المحاور التي ينبغي النظر إليها أوضح قليلاً.

  • هل ما نتحقّق منه منطقنا، أم الربط بالخارج؟
  • هل يبقى المعنى إذا استُبدل بـ in-memory fake؟
  • هل موضوع الحديث سلوك قاعدة البيانات / الملفّ / HTTP / DI / الإعدادات / الإطار / نظام التشغيل؟
  • هل نريد تشغيل أنماط إدخال كثيرة بسرعة؟

ما إن تظهر هذه الأربعة حتى يسهل رسم الحدّ بين unit test وintegration test كثيراً.

كُتبت هذه المقالة على افتراض تصميم الاختبار الآلي في C# / .NET. أمثلة الشيفرة بـ xUnit، لكن معيار الحكم نفسه لا يعتمد على الإطار. صيغت بحيث تُقرأ كما هي مع JUnit أو pytest.

المحتوى يستند إلى «Integration tests in ASP.NET Core» في Microsoft Learn كما هو متاح في مارس 2026،1 وإلى «Unit testing best practices for .NET» كذلك،2 وإلى «The Practical Test Pyramid» لـ Martin Fowler.3 وحرصاً على ألّا تبقى الإحالات أرقاماً فقط، نكتب اسم المصدر في المتن أيضاً. عناوين المصادر الأصلية مجمّعة في روابط المقالة المرجعية في نهايتها.

1. الجواب أوّلاً

بصياغة فضفاضة لكن صالحة للعمل:

  1. المنطق الصافي unit test
  2. الاتّصال والتوصيل والتحويل واختلاف البيئة integration test
  3. إن أمكن التحقّق بأيّ منهما، فابدأ بـ unit test
  4. ضيّق حدود integration test بدلاً من توسيعه وتثقيله

باختصار، unit test هو اختبار الحكم، وintegration test هو اختبار الاتّصال.

ما يكتمل معناه بلا مورد خارجي، مثل حساب المبلغ وانتقال الحالة والتحقّق من الإدخال وشروط الموافقة وتصنيف الاستثناءات، أسرع وأقلّ هشاشة وأكثر قدرة على تشغيل أنماط الإدخال بكثافة إذا قُرِّب إلى unit test. أمّا ما «يخون في لحظة الاتّصال»، مثل تنفيذ SQL وتسلسل JSON / CSV والتوجيه وmodel binding وتسجيل DI وقفل الملفّات والصلاحيّات وتسجيل COM و32bit / 64bit وSTA / MTA، فأأمن أن يُوضع في جانب integration test.

وترتّب Integration tests in ASP.NET Core في Microsoft Learn أيضاً اختبار التكامل على سيناريوهات البنية المهمّة، وتدعو إلى اختيار unit test إن كفى.

في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 18، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle

2. ما نعنيه بـ unit test وintegration test في هذه المقالة

هنا نفرّق المصطلحات كالتالي.

المستوى ما نتحقّق منه التكوين النموذجي
unit test صحّة مسؤوليّة واحدة معزولة قطع الموارد الخارجية بـ fake / mock / stub
integration test اتّصال مكوّنات متعدّدة، والسلوك شاملاً البنية والإطار قاعدة بيانات حقيقية، ملفّ حقيقي، serializer حقيقي، host حقيقي، pipeline حقيقي، إلخ
E2E / اختبار وظيفي تدفّق المستخدم عبر التطبيق كلّه تطبيق منشور، خدمات متعدّدة، متصفّح حقيقي أو عمليّة حقيقية

في ترتيب unit test لـ .NET، يُوصف الاختبار الجيّد بأنّه fast / isolated / repeatable، وبأنّه لا يعتمد على عوامل خارجية مثل نظام الملفّات أو قاعدة البيانات. التفاصيل أوضح في Unit testing best practices for .NET.

كذلك، ليس integration test مقصوراً على «اختبار ثقيل يستخدم دائماً عمليّة أخرى أو خادماً آخر». حتى داخل العمليّة نفسها، إن ربطت مكوّنات حقيقية متعدّدة وتحقّقت من السلوك الحقيقي للإطار أو البنية، فذلك أقرب إلى integration test.

مثلاً، عند إجراء unit test لـ controller action في ASP.NET Core، يُضيَّق الموضوع إلى حكم الـ action نفسه، ويُترك التفاعل مع الإطار مثل routing وmodel binding وfilters لـ integration test، وهذا الفصل مبيّن رسمياً أيضاً. يسهل الترتيب بالنظر إلى Unit test controller logic in ASP.NET Core.

2.1. الفرق بين fake / mock / stub

صففنا في الجدول أعلاه fake / mock / stub، لكن الثلاثة ليست الشيء نفسه. كتصنيف لبدائل الاختبار (test doubles) نستخدمها في هذه المقالة بالمعاني التالية. التفريق على ترتيب «Mocks Aren’t Stubs» لـ Martin Fowler.4

التسمية ماذا تفعل موضع الاستخدام النموذجي
stub بديل يعيد قيمة محدّدة فقط. لا يتحقّق من طريقة الاستدعاء حين تريد تثبيت شرط الإدخال، مثل «المخزون دائماً 3»
mock بديل يتحقّق من طريقة الاستدعاء نفسها. يُجري assert على عدد الاستدعاءات أو الوسائط حين تريد التحقّق من أنّ الحفظ استُدعي مرّة واحدة فقط
fake بديل يسلك سلوك الشيء الحقيقي بتنفيذ خفيف مستودع in-memory، أو موضع ملفّات على دليل مؤقّت، إلخ

باختصار، stub بديل للإدخال، وmock تحقّق من الاستدعاء، وfake تنفيذ مبسّط. يظهر أثر هذا الفرق في الفصل 4. علامة «أحتاج إلى سبعة mocks» تعني بدقّة أن أهداف التحقّق من الاستدعاء سبعة، فتصير إشارة إلى أنّ الاختبار لا يتحقّق من حكم واحد بل من طريقة ربط مكوّنات متعدّدة.

2.2. كيف نعامل E2E في هذه المقالة

موضوع المقالة هو الحدّ بين unit test وintegration test فحسب. أُدخل E2E / الاختبار الوظيفي في الجدول أعلاه للمقابلة، لكن المتن لا يتعمّق فيه.

لتثبيت الموضع فقط: في فكرة هرم الاختبار يصير المثلّث كلّما نزلت الطبقة زاد العدد وزادت السرعة، وكلّما صعدت قلّ العدد وزاد البطء. «The Practical Test Pyramid» لـ Martin Fowler نقطة انطلاق هذا الترتيب.3 أي أنّ النسبة هي جعل unit test قاعدة، ووضع integration test عند كلّ حدّ، وتضييق E2E على التدفّقات الرئيسة. موضع الوضع الملموس ملخّص في التكوين الثلاثي للفصل 7.

3. جدول قرار في صفحة واحدة

أوّلاً نضع الجدول الأكثر فائدة في العمل.

ما نتحقّق منه الاختبار الذي نجعله رئيساً ملاحظة
حساب المبلغ، الخصم، انتقال الحالة، التحقّق من الإدخال unit test تريد تشغيل أنماط الإدخال بكثافة
تصنيف الاستثناءات، اختيار رسالة الخطأ، الحكم على إعادة المحاولة unit test المعنى يكتمل بلا I/O حقيقي
تحويل SQL / ORM في Repository، وtransaction integration test سلوك قاعدة البيانات الحقيقية أو الموفّر الحقيقي هو الموضوع
serialize / deserialize لـ JSON / XML / CSV integration test انحراف تنسيق السلك يصعب التقاطه بـ fake
التوجيه، model binding، المرشّحات، middleware integration test التحقّق من الاتّصال بالإطار
انتقال حالة ViewModel أو Presenter في WPF / WinForms unit test له معنى دون تشغيل واجهة المستخدم
Binding الفعلي، Dispatcher، دورة حياة التحكّم، حلقة الرسائل integration test أو اختبار واجهة سلوك الإطار والخيط هو الموضوع
مسار الملفّ، الصلاحيّات، القفل، المجلّد المشترك، رمز السطر الجديد، ترميز الأحرف integration test يلزم السلوك الحقيقي لنظام التشغيل ونظام الملفّات
تسجيل COM، 32bit / 64bit، STA / MTA، تحميل DLL integration test اختلاف البيئة وحدّ العمليّة هما الموضوع
بدء التطبيق كلّه، والتحقّق المتتابع من حالات الاستخدام الرئيسة E2E / smoke يجوز أن يكون العدد قليلاً

حيلة القراءة هي أيّ اختبار أقرب إلى سبب الكسر في الإنتاج. أثبت من موضع الشيفرة أن تقرّر بحسب عدم اليقين الذي تريد تقليله.

4. ما ينبغي إبقاؤه في unit test

ما يلائم unit test هو المسؤوليّة التي يبقى لها معنى بعد نزع العالم الخارجي.

مثلاً:

  • قواعد الأعمال
  • التفرّع
  • انتقال الحالة
  • التحقّق من الإدخال
  • تصنيف الأخطاء
  • تقرير سياسة إعادة المحاولة
  • تغيّر حالة ViewModel / Presenter
  • منطق التحويل نفسه

وخصوصاً، كلّما كثرت التركيبات ارتفعت قيمة تقريبه إلى unit test.

مثلاً:

  • بوجود قسيمة / بدونها
  • بوجود مخزون / بدونه
  • طلب أوّل / طلب متكرّر
  • مدير / مستخدم عادي
  • قيمة سليمة / قيمة حدّية / قيمة غير سليمة

كلّما زادت شروط التفرّع، ثقل تشغيل الكلّ في integration test. هنا أعقل أن تُقطَّع دقّة في unit test.

كذلك مهمّ في unit test أن تُبقى العوامل الخارجية قابلة للضبط.

  • احقن الزمن الحالي
  • اجعل GUID أو العشوائية قابلة للاستبدال
  • لا تنتظر بـ sleep
  • لا تلمس قاعدة بيانات حقيقية ولا ملفّاً حقيقياً
  • لا تخرج إلى الشبكة الحقيقية

إذا حُفظ هذا النطاق، يستقرّ الاختبار كثيراً.

4.1. حين يتكاثر الـ mock في unit test

إذا حاولت كتابة unit test فانتهيت إلى:

  • تحتاج إلى سبعة mocks
  • الإعداد طويل
  • arrange أطول من المتن
  • لا يُرى ما تريد التحقّق منه

فالغالب أحد الأمرين:

  1. الصنف مثقل بالمسؤوليّات
  2. أنت تحشر في unit test توصيلاً ينبغي التحقّق منه في integration test

الـ mock أداة لقطع العالم الخارجي، وليست أداة لإثبات أنّ الاتّصال بالشيء الحقيقي صحيح. إن اختلط الأمران سهل أن يصير «الكلّ أخضر ثم يسقط في الإنتاج».

4.2. أصغر مثال لـ unit test

الكلام وحده يصير مجرّداً، لذلك نضع مثالاً واحداً من كلّ جانب من الحدّ بالشيفرة. نبدأ بجانب unit test. انتقال حالة ViewModel يكتمل معناه دون تشغيل واجهة المستخدم، فهو مجال unit test.

// .NET 8 / xUnit
// 対象: 外部資源にいっさい触らない ViewModel
public sealed class OrderViewModel
{
    public decimal Subtotal { get; set; }
    public bool IsMember { get; set; }

    public bool CanCheckout => Subtotal > 0m;
    public decimal Total => IsMember ? Subtotal * 0.9m : Subtotal;
}

public class OrderViewModelTests
{
    [Fact]
    public void 会員なら1割引きになる()
    {
        var viewModel = new OrderViewModel { Subtotal = 1000m, IsMember = true };

        Assert.Equal(900m, viewModel.Total);
    }

    [Theory]
    [InlineData(0, false)]
    [InlineData(1, true)]
    public void 小計が0なら確定できない(int subtotal, bool expected)
    {
        var viewModel = new OrderViewModel { Subtotal = subtotal };

        Assert.Equal(expected, viewModel.CanCheckout);
    }
}

لا تظهر هنا قاعدة بيانات ولا ملفّ ولا HTTP. لذلك سريع، وقابل للتشغيل المتوازي، ويمكنك زيادة أنماط الإدخال كما تشاء بـ [Theory]. تقريب استنفاد التفرّعات إلى هنا يعني، عملياً، هذا الشكل.

5. أربعة حدود تُرفع إلى integration test

المواضع التي ينبغي رفعها إلى integration test يمكن ترتيبها تقريباً في أربعة: التنسيق، والتوصيل، والبيئة، والزمن.

5.1. حدّ التنسيق

يدخل في التنسيق هنا أمور مثل:

  • JSON / XML / CSV
  • مخطّط قاعدة البيانات وmapping
  • nullable / precision / timezone
  • تسلسل enum والتواريخ
  • ترميز الأحرف وBOM
  • رمز السطر الجديد

ويعدّ Martin Fowler أيضاً الحدّ الذي يدخل فيه serialize / deserialize مرشّحاً لـ integration test. التفاصيل في The Practical Test Pyramid.

مثلاً:

  • اختلف اسم الحقل بعد تحويل DTO إلى JSON
  • انكسر اقتباس CSV أو السطر الجديد
  • قُرِّب decimal
  • انحرف التعامل مع DateTimeOffset في قاعدة البيانات
  • اختلف التعامل مع null والسلسلة الفارغة عمّا كان متوقّعاً

هذه الأعطال يسهل أن تفلت من unit test وحده.

5.2. حدّ التوصيل

يدخل في حدّ التوصيل مثلاً:

  • تسجيل DI
  • ربط الإعدادات
  • التوجيه
  • model binding
  • المرشّحات
  • middleware
  • بدء الـ host
  • توصيل الأحداث
  • Binding في WPF وتوصيل الأوامر

هنا الموضوع ليس «هل دالّتي صحيحة»، بل هل اتّصلت مكوّنات حقيقية متعدّدة اتّصالاً صحيحاً.

في ASP.NET Core ترتيب رسمي يضيّق unit test لـ controller action إلى حكم الـ action، ويُنظر إلى routing وmodel binding وfilters في جانب integration test. الفكرة نفسها خارج الويب: في تطبيق سطح المكتب أيضاً انتقال حالة ViewModel unit test، والسلوك الشامل لـ XAML Binding الفعلي أو Dispatcher أقرب إلى integration test.

5.3. حدّ البيئة

في تطوير Windows هذا الحدّ مهمّ جدّاً.

  • صلاحيّات الملفّات
  • المجلّد المشترك
  • قفل الملفّات
  • إعادة التسمية من ملفّ مؤقّت
  • صلاحيّات المسؤول
  • صلاحيّات بدء الخدمة
  • تسجيل COM
  • 32bit / 64bit
  • STA / MTA
  • مصدر تحميل DLL

هنا شرط نظام التشغيل أو بيئة التشغيل نفسه هو البطل. مع in-memory fake يسقط المعنى كثيراً، فأأمن أن يُضبط في integration test.

وخصوصاً في تكوين يشمل برامج Windows قائمة أو COM / ActiveX، من الشائع أن يسقط الأمر بالتسجيل أو bitness أو نموذج الخيط أو الصلاحيّات قبل المنطق. هذا الإخفاق مجال يلتقطه integration test الشامل للبيئة، لا unit test.

5.4. حدّ الزمن

حدّ آخر يسهل تفويته هو الزمن والتوازي.

  • timeout
  • cancellation
  • السلوك الفعلي لإعادة المحاولة
  • التشغيل بـ timer
  • إيقاف المعالجة الخلفية
  • race condition
  • ترتيب الإنهاء عند الإيقاف

المهمّ هنا فصل الحكم عن السلوك الفعلي.

مثلاً:

  • إلى كم مرّة نعيد المحاولة
  • أيّ استثناء نجعله هدفاً لإعادة المحاولة

يكفي في unit test. أمّا:

  • هل يعمل timeout فعلاً
  • هل تنتشر cancellation
  • هل ينكسر الأمر عند تصادم timer والمعالجة غير المتزامنة
  • هل تُغلق المقابض والمهامّ نظيفاً عند الإنهاء

فأقرب إلى integration test.

5.5. أصغر مثال لـ integration test

بالموضوع نفسه في 4.2 نكتب الجانب الآخر من الحدّ. ما نتحقّق منه هنا هو «هل المبلغ المحفوظ يبقى سليماً بعد الذهاب والعودة عبر قاعدة البيانات». نمرّ عبر ملفّ SQLite فعلي.

// .NET 8 / xUnit / Microsoft.Data.Sqlite
using System.Globalization;
using Microsoft.Data.Sqlite;

public sealed class OrderRepository(SqliteConnection connection)
{
    public void Save(int id, decimal total)
    {
        using var command = connection.CreateCommand();
        command.CommandText = "INSERT INTO orders (id, total) VALUES ($id, $total);";
        command.Parameters.AddWithValue("$id", id);
        command.Parameters.AddWithValue("$total", total.ToString(CultureInfo.InvariantCulture));
        command.ExecuteNonQuery();
    }

    public decimal FindTotal(int id)
    {
        using var command = connection.CreateCommand();
        command.CommandText = "SELECT total FROM orders WHERE id = $id;";
        command.Parameters.AddWithValue("$id", id);
        var stored = (string)command.ExecuteScalar()!;
        return decimal.Parse(stored, CultureInfo.InvariantCulture);
    }
}

public sealed class OrderRepositoryTests : IDisposable
{
    private readonly string _databasePath =
        Path.Combine(Path.GetTempPath(), $"orders-{Guid.NewGuid():N}.db");
    private readonly SqliteConnection _connection;

    public OrderRepositoryTests()
    {
        // Pooling=False にしておかないと、後始末で db ファイルを消せないことがあります
        _connection = new SqliteConnection($"Data Source={_databasePath};Pooling=False");
        _connection.Open();

        using var create = _connection.CreateCommand();
        create.CommandText = "CREATE TABLE orders (id INTEGER PRIMARY KEY, total TEXT NOT NULL);";
        create.ExecuteNonQuery();
    }

    [Fact]
    public void 保存した金額が丸められずに読み戻せる()
    {
        var repository = new OrderRepository(_connection);

        repository.Save(id: 1, total: 1234.56m);

        Assert.Equal(1234.56m, repository.FindTotal(1));
    }

    public void Dispose()
    {
        _connection.Dispose();
        File.Delete(_databasePath);
    }
}

ما يتحقّق منه هذا الاختبار ليس تفرّع OrderRepository. بل جزء الاتّصال: هل يمرّ SQL، وهل يتوافق نوع العمود مع decimal، وهل تُحفظ القيمة بعد الذهاب والعودة. ليس في SQLite نوع decimal، لذلك «بأيّ نوع نحفظ حتى لا ينكسر الذهاب والعودة» مسألة تصميم الاتّصال لا مسألة تنفيذ. هذا لا يظهر في in-memory fake.

في هذا المثال يُنشأ ملفّ قاعدة مؤقّت واحد لكلّ صنف اختبار، ويُحذف في Dispose. لأنّ integration test يحمل حالة، يصير توضيح أين تُنشأ وأين تُرمى في كلّ مرّة أهمّ منه في unit test.

6. أخطاء حكم شائعة

6.1. الاكتفاء بجعل Repository على هيئة mock

حتى إن مرّت طبقة Repository كلّها عبر mock، فأنت لا تعلم:

  • هل SQL صحيح
  • هل تعمل transaction
  • هل تتوافق مع المخطّط
  • هل ينحرف mapping
  • هل ينكسر ترميز الأحرف أو precision

Repository في الغالب نقطة اتّصال على حدّ أكثر ممّا هو هدف اختبار للمنطق. في تلك الحالة أنسب للواقع رفع وزن integration test فوق unit test.

6.2. محاولة رؤية الإطار أيضاً من unit test لـ Controller / Endpoint

ما تريد رؤيته في unit test لـ controller action هو في حدود:

  • التفرّع الشرطي
  • اختيار القيمة المعادة
  • تفريق استدعاء الخدمات المعتمدة

أمّا:

  • هل يُصاب المسار
  • هل يمرّ model binding
  • هل يعمل المرشّح
  • كيف يبدو الناتج بعد المرور عبر middleware

ففي جانب integration test. إن خُلط الاثنان صار أصعب معرفة ما الذي انكسر.

6.3. استنفاد أنماط الإدخال في integration test

integration test أقرب إلى الشيء الحقيقي، فهو أبطأ لا محالة. لذلك أجدى أن تفرّق: استنفاد التفرّعات في unit test، والحالات التمثيلية للحدّ في integration test.

وفي شرح اختبار التكامل في Microsoft Learn أيضاً يُنصح، تجاه قاعدة البيانات أو نظام الملفّات، لا بتشغيل كلّ الأنماط في integration test، بل بالتضييق على سيناريوهات تمثيلية مثل read / write / update / delete.

6.4. ضرب أنظمة الإنتاج للخدمات الخارجية من CI كما هي

هذا أجدر بالتجنّب حرصاً على السلامة.

يهمّ في integration test «شبه الحقيقة»، لكن ذلك لا يعني ضرورة ضرب SaaS الإنتاج أو API الإنتاج في كلّ مرّة. ويدعو Fowler أيضاً إلى إقامة الخدمة الخارجية محلياً، أو وضع fake، أو استخدام test instance مخصّص.

في العمل يسهل التعامل مع مزيج من:

  • قاعدة بيانات محلية
  • دليل مؤقّت
  • test host
  • بيئة اختبار مخصّصة
  • خدمة fake بعقد ثابت

7. تكوين موصى به في العمل

لا نسبة صحيحة مطلقة. لكن قابلاً لإعادة الاستخدام على نطاق واسع هو الطبقات الثلاث التالية.

الطبقة الرئيس ماذا تضع
طبقة اللبّ unit test كثيف قواعد الأعمال، انتقال الحالة، التحقّق من الإدخال، تصنيف الأخطاء
طبقة الحدود integration test ضيّق قاعدة البيانات، الملفّات، HTTP، serializer، DI، الإعدادات، COM، الصلاحيّات
طبقة الكلّ عدد قليل من smoke / E2E التحقّق من البدء، التدفّقات الرئيسة، منع تكرار العطل الجسيم

حسياً، ما يثخن بالعدد هو unit test، وما يثخن بكثافة الحدّ هو integration test.

طريقة التقدّم الموصى بها كالتالي.

  1. أوّلاً، عدِّد حدود التطبيق
  2. قرّب المنطق إلى شكل يمكن قطعه عن العالم الخارجي
  3. لكلّ حدّ ضع «مساراً سعيداً واحداً على الأقل» و«مسار إخفاق تمثيلياً»
  4. ضيّق المسار الكلّي المتتابع في العدد
  5. إذا ظهر خلل، أضف الاختبار في الطبقة القادرة على إعادة إنتاج ذلك الخلل بأقلّ تكلفة

الخامسة الأخيرة مهمّة.

  • إن كان خطأ في القاعدة، أضف unit test
  • إن كان خطأ في SQL / binding / الإعدادات / الصلاحيّات / التسجيل، أضف integration test
  • إن كان عطلاً يشمل البدء أو التوزيع، أضف smoke أو E2E

بهذه طريقة الزيادة يصعب أن تتذبذب مسؤوليّة الاختبار.

8. خمسة أسئلة أخيرة عند التردّد

أخيراً نلخّص في خمسة أسئلة للتحقّق عند التردّد.

  1. هل يبقى المعنى الذي تريد التحقّق منه إذا استُبدل بـ in-memory fake
    • إن بقي، فأقرب إلى unit test.
  2. عند الكسر، أليس المشتبه هو الاتّصال أو الإعدادات لا المنطق
    • إن كان كذلك، فأقرب إلى integration test.
  3. أليس الموضوع قاعدة البيانات / الملفّ / serializer / DI / المسار / model binding / نظام التشغيل / الصلاحيّات / bitness / الخيط
    • إن كان كذلك، فأقرب إلى integration test.
  4. هل تريد تشغيل أنماط إدخال كثيرة بسرعة
    • إن كان كذلك، فأقرب إلى unit test.
  5. إذا سقط هذا الاختبار، هل تعرف فوراً ما الذي ينبغي إصلاحه
    • إن لم تعرف، فطبقات الاختبار مختلطة.

بهذا الترتيب بالأسئلة الخمسة يسهل تجنّب الحكم الفضفاض من نوع «أقرب إلى الحقيقة إذن integration test» أو «أسرع إذن unit test».

9. خلاصة

الحدّ بين unit test وintegration test أعملي أن يُقرَّر لا بموضع الشيفرة، بل بـأيّ عدم يقين تريد تقليله.

النقاط تؤول إلى هذه الخمس.

  • unit test اختبار الحكم
  • integration test اختبار الاتّصال
  • استنفاد التفرّعات في unit test
  • التنسيق والتوصيل والبيئة والزمن في integration test
  • التحقّق المتتابع للكلّ يُضبط بعدد قليل من smoke / E2E

أكثر ما ينبغي تجنّبه:

  • الإحساس بأنّ الـ mock أثبت الاتّصال بالشيء الحقيقي أيضاً
  • محاولة تشغيل كلّ التفرّعات في integration test
  • خلط مسؤوليّة unit test وintegration test

هذه الثلاثة. عند التردّد، انظر أوّلاً: هل ذلك العطل يكسر «الحكم»، أم يكسر «الاتّصال». بهذا السؤال الواحد يُرتَّب عدد كبير من الحالات.

10. مقالات ذات صلة

11. روابط مرجعية

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.

الأسئلة الشائعة

أسئلة شائعة حول موضوع هذه المقالة.

كيف تفرّق بين unit test وintegration test؟
باختصار، unit test هو اختبار الحكم، وintegration test هو اختبار الاتّصال. ما يكتمل معناه بلا مورد خارجي، مثل حساب المبلغ وانتقال الحالة والتحقّق من الإدخال وتصنيف الاستثناءات، يُقرَّب إلى unit test. وما «يخون في لحظة الاتّصال»، مثل تنفيذ SQL وتسلسل JSON/CSV والتوجيه وتسجيل DI وقفل الملفّات والصلاحيّات وتسجيل COM و32bit/64bit، يُوضع في جانب integration test. إن أمكن التحقّق بأيّ منهما، فابدأ بـ unit test.
ما الذي ينبغي رفعه إلى integration test؟
يمكن ترتيبه تقريباً في أربعة حدود: التنسيق، والتوصيل، والبيئة، والزمن. التنسيق يشمل JSON/CSV وmapping قاعدة البيانات وترميز الأحرف. التوصيل هو ربط المكوّنات الحقيقية مثل تسجيل DI والتوجيه وmodel binding. البيئة هي السلوك الفعلي لنظام التشغيل مثل صلاحيّات الملفّات وتسجيل COM و32bit/64bit وSTA/MTA. الزمن يشمل timeout وcancellation وrace condition. هذه تفقد معناها مع in-memory fake، لذا أأمن أن تُضبط في integration test.
ما المشكلة إذا تكاثر عدد الـ mock في unit test؟
إن احتجت إلى سبعة mocks، أو طال الإعداد، أو غاب ما تريد التحقّق منه، فإمّا أن الصنف مثقل بالمسؤوليّات، وإمّا أنك تحشر في unit test توصيلاً ينبغي التحقّق منه في integration test. الـ mock أداة لقطع العالم الخارجي، وليست أداة لإثبات أنّ الاتّصال بالشيء الحقيقي صحيح. إن اختلط الأمران سهل أن يصير «الكلّ أخضر ثم يسقط في الإنتاج».
أيّ تكوين ونسبة للاختبارات مناسبة؟
التكوين الثلاثي الطبقات قابل لإعادة الاستخدام. طبقة اللبّ تثخّن قواعد الأعمال وانتقال الحالة بـ unit test، وطبقة الحدود تضع integration test ضيّقاً على قاعدة البيانات والملفّات وserializer وDI، وطبقة الكلّ تضبط بدء التشغيل والتدفّقات الرئيسة بعدد قليل من smoke/E2E. استنفاد التفرّعات يُدار في unit test، ويُضيَّق integration test إلى مسار سعيد واحد على الأقل لكلّ حدّ ومسار إخفاق تمثيلي. إذا ظهر خلل، أضف الاختبار في الطبقة القادرة على إعادة إنتاجه بأقلّ تكلفة.

الملف الشخصي للمؤلف

صفحة الملف الشخصي لمؤلف المقالة.

غو كومورا

مؤسّس شركة كومورا سوفت ذ.م.م.

يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.

العودة إلى المدونة