كيف ترسم الحدّ بين 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. الجواب أوّلاً
بصياغة فضفاضة لكن صالحة للعمل:
- المنطق الصافي unit test
- الاتّصال والتوصيل والتحويل واختلاف البيئة integration test
- إن أمكن التحقّق بأيّ منهما، فابدأ بـ unit test
- ضيّق حدود 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 أطول من المتن
- لا يُرى ما تريد التحقّق منه
فالغالب أحد الأمرين:
- الصنف مثقل بالمسؤوليّات
- أنت تحشر في 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.
طريقة التقدّم الموصى بها كالتالي.
- أوّلاً، عدِّد حدود التطبيق
- قرّب المنطق إلى شكل يمكن قطعه عن العالم الخارجي
- لكلّ حدّ ضع «مساراً سعيداً واحداً على الأقل» و«مسار إخفاق تمثيلياً»
- ضيّق المسار الكلّي المتتابع في العدد
- إذا ظهر خلل، أضف الاختبار في الطبقة القادرة على إعادة إنتاج ذلك الخلل بأقلّ تكلفة
الخامسة الأخيرة مهمّة.
- إن كان خطأ في القاعدة، أضف unit test
- إن كان خطأ في SQL / binding / الإعدادات / الصلاحيّات / التسجيل، أضف integration test
- إن كان عطلاً يشمل البدء أو التوزيع، أضف smoke أو E2E
بهذه طريقة الزيادة يصعب أن تتذبذب مسؤوليّة الاختبار.
8. خمسة أسئلة أخيرة عند التردّد
أخيراً نلخّص في خمسة أسئلة للتحقّق عند التردّد.
- هل يبقى المعنى الذي تريد التحقّق منه إذا استُبدل بـ in-memory fake
- إن بقي، فأقرب إلى unit test.
- عند الكسر، أليس المشتبه هو الاتّصال أو الإعدادات لا المنطق
- إن كان كذلك، فأقرب إلى integration test.
- أليس الموضوع قاعدة البيانات / الملفّ / serializer / DI / المسار / model binding / نظام التشغيل / الصلاحيّات / bitness / الخيط
- إن كان كذلك، فأقرب إلى integration test.
- هل تريد تشغيل أنماط إدخال كثيرة بسرعة
- إن كان كذلك، فأقرب إلى unit test.
- إذا سقط هذا الاختبار، هل تعرف فوراً ما الذي ينبغي إصلاحه
- إن لم تعرف، فطبقات الاختبار مختلطة.
بهذا الترتيب بالأسئلة الخمسة يسهل تجنّب الحكم الفضفاض من نوع «أقرب إلى الحقيقة إذن 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. مقالات ذات صلة
- قائمة تحقّق للحدّ الأدنى من الأمان في تطوير تطبيقات Windows
- إلى أيّ مدى يمكن لتطبيق Windows أن يكون فعلاً single binary - ما الذي يندرج في EXE واحد وما الذي يبقى معتمداً على Windows
- متى يصبح Windows admin privilege ضرورياً - UAC والمناطق المحميّة وكيفيّة التمييز على مستوى التصميم
- ما هو Reg-Free COM - كيف يعمل Registration-Free COM وأين يلائم وأين لا يلائم
11. روابط مرجعية
-
Microsoft Learn, Integration tests in ASP.NET Core ↩
-
Microsoft Learn, Unit testing best practices for .NET ↩
-
Martin Fowler, The Practical Test Pyramid ↩ ↩2
-
Martin Fowler, Mocks Aren’t Stubs ↩
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
كيف نسرّع فحص تطبيقات Windows بـ Windows Sandbox
نرتّب كيف يسرّع Windows Sandbox عزل مشكلات صلاحيات المسؤول، وإعادة الإنتاج في بيئة نظيفة، وفحص نقص الصلاحيات أو الموارد، بما في ذلك الفصل...
جدول قرار: إنهاء أم استمرار بعد استثناء غير متوقّع
عند وقوع استثناء غير متوقّع، هل تُنهي التطبيق أم تستمر؟ المقالة ترتّب الحكم من تلف الحالة والآثار الجانبيّة الخارجيّة والخيوط والحدود الأ...
كيفيّة فصل «المعالجات التي تحتاج امتيازات المسؤول فقط» في تطبيقات Windows
نرتّب هنا تصميماً ملموساً يُبقي واجهة تطبيق Windows عند asInvoker ويفصل المعالجات التي تحتاج امتيازات المسؤول إلى helper EXE، شاملاً UAC ...
حفظ أسرار تطبيقات Windows - تجنّب إعداد النصّ الصريح بـ DPAPI
نرتّب هنا فكرة DPAPI و ProtectedData، والفرق بين CurrentUser و LocalMachine، ونقاط التنفيذ، كي لا تُحفَظ معلومات الاتّصال ورموز API بالنص...
قائمة التحقّق الدنيا لأمان تطبيقات Windows
ننظّم في صورة قائمة تحقّق أساسيات الصلاحيات والتوقيع والتحديث والأسرار و HTTPS والتحقّق من الإدخال وتحميل DLL والسجلات لتطبيقات أعمال WPF...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
الاستشارات التقنية ومراجعة التصميم
لأنّ تحديد موضع الحدّ الفاصل بين اختبارات الوحدة واختبارات التكامل يسهل ترتيبه ضمن مراجعة التصميم قبل التنفيذ أو ضمن استشارة حول استراتيجيّة الاختبار.
تطوير تطبيقات ويندوز
لأنّ حدود الملفّات والصلاحيات وCOM و32bit / 64bit في تطبيقات Windows تتّصل مباشرة بطبقات الاختبار أيضاً، فهي تتلاءم مع ترتيب سياسة التنفيذ.
الأسئلة الشائعة
أسئلة شائعة حول موضوع هذه المقالة.
- كيف تفرّق بين 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 إلى مسار سعيد واحد على الأقل لكلّ حدّ ومسار إخفاق تمثيلي. إذا ظهر خلل، أضف الاختبار في الطبقة القادرة على إعادة إنتاجه بأقلّ تكلفة.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.