اختبار TypeScript: الوحدة، التكامل، e2e من الألف إلى الياء

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة
```html

مقدمة: لماذا يعتبر الاختبار في TypeScript أمرًا لا غنى عنه

TypeScript هي أداة قوية تُضيف الكتابة الثابتة (static typing) إلى JavaScript. ومع ذلك، حتى أكثر أنظمة الأنواع صرامة لا تضمن أن الكود الخاص بك سيعمل بشكل صحيح في وقت التشغيل (runtime). الأخطاء البرمجية (bugs)، الأخطاء المنطقية، والمعالجة غير الصحيحة للحالات الحدودية (edge cases) — كل هذا يبقى خارج نطاق عملية الترجمة (compilation). وهنا يأتي دور الاختبارات لإنقاذ الموقف.

في هذه المقالة، سنستعرض ثلاثة مستويات رئيسية للاختبار في TypeScript: unit (اختبار الوحدات)، integration (اختبار التكامل) و e2e (الاختبار الشامل). ستتعرف على الفروقات بينها، ومتى تستخدم كل منها، وسترى أمثلة عملية على الكود. بعد القراءة، ستتمكن من إعداد خط أنابيب اختبار (test pipeline) متكامل لمشروعك في TypeScript.

1. اختبار الوحدات (Unit-testing): التحقق من الوحدات البرمجية الذرية

اختبارات الوحدات (Unit tests) هي أسرع وأرخص مستوى من الاختبار. فهي تعزل دالة واحدة، أو طريقة (method)، أو كلاس (class) وتختبره بمعزل عن التبعيات الخارجية (قواعد البيانات، واجهات API، نظام الملفات).

1.1 اختيار الأداة والإعداد

الإطار الأكثر شيوعًا لـ TypeScript هو Jest. وهو يدعم TypeScript بشكل مدمج عبر ts-jest أو @swc/jest.

// التثبيت
npm install --save-dev jest @types/jest ts-jest

// تهيئة الإعدادات
npx ts-jest config:init

بعد ذلك، سيظهر ملف jest.config.js في المجلد الجذر مع الإعدادات.

1.2 مثال على اختبار وحدة لدالة

لنفترض أن لدينا دالة بسيطة لحساب الخصم:

// discount.ts
export function applyDiscount(price: number, discountPercent: number): number {
  if (discountPercent < 0 || discountPercent > 100) {
    throw new Error('نسبة الخصم غير صالحة');
  }
  return price - (price * discountPercent) / 100;
}

سنكتب لها اختبار وحدة:

// discount.test.ts
import { applyDiscount } from './discount';

describe('applyDiscount', () => {
  it('يجب أن تحسب خصم 10% بشكل صحيح', () => {
    expect(applyDiscount(1000, 10)).toBe(900);
  });

  it('يجب أن تعيد السعر الأصلي عند خصم 0%', () => {
    expect(applyDiscount(500, 0)).toBe(500);
  });

  it('يجب أن ترمي خطأ عند خصم أكبر من 100%', () => {
    expect(() => applyDiscount(100, 150)).toThrow('نسبة الخصم غير صالحة');
  });
});

نقوم بتشغيل: npx jest. يجب أن تنجح الاختبارات الثلاثة جميعها.

1.3 الموكات (Mocks) والستاب (Stubs)

إذا كانت الدالة تتصل بخدمة خارجية (مثل قاعدة بيانات)، فإننا نستبدلها بـ mock:

// userService.test.ts
import { getUser } from './userService';
import { db } from './database';

jest.mock('./database'); // Mock تلقائي

test('getUser تعيد المستخدم بناءً على المعرف', async () => {
  (db.findUser as jest.Mock).mockResolvedValue({ id: 1, name: 'إيفان' });
  const user = await getUser(1);
  expect(user.name).toBe('إيفان');
});

2. اختبار التكامل (Integration-testing): التحقق من تفاعل الوحدات

اختبارات التكامل (Integration tests) تتحقق من كيفية عمل الأجزاء المختلفة من النظام معًا: المتحكمات (controllers)، الخدمات (services)، المستودعات (repositories)، وقواعد البيانات. إنها أبطأ من اختبارات الوحدات، لكنها تمنح ثقة أكبر في صحة الترابطات.

2.1 اختبار نقطة نهاية API مع Express

لاختبار طلبات HTTP، نستخدم مكتبة Supertest مع Jest.

// التثبيت
npm install --save-dev supertest @types/supertest

لنفترض أن لدينا تطبيق Express:

// app.ts
import express from 'express';
import { createUser } from './controllers/userController';

export const app = express();
app.use(express.json());
app.post('/api/users', createUser);

اختبار التكامل:

// app.test.ts
import request from 'supertest';
import { app } from './app';
```

المدونات

توصيات الكتب