0%
دليل شامل لاختبارات JavaScript

دليل شامل لاختبارات JavaScript

استراتيجيات وأدوات لاختبار تطبيقات JavaScript الخاصة بك بفعالية وضمان جودتها.

I

InSkillCoach

· دقيقة

دليل شامل لاختبارات JavaScript

تُعدّ الاختبارات جانبًا أساسيًا في تطوير البرمجيات الاحترافي. فهي تضمن أن شيفرتك تعمل كما هو متوقع، وتسهّل الصيانة، وتتيح اكتشاف حالات الانحدار (régressions). يستعرض هذا المقال مختلف المقاربات والأدوات لاختبار تطبيقات JavaScript الخاصة بك بفعالية.

لماذا تختبر شيفرتك؟

  • الثقة: التأكد من أن شيفرتك تعمل بشكل صحيح
  • التوثيق: تعمل الاختبارات كتوثيق حيّ
  • إعادة هيكلة آمنة (Refactoring): تعديل الشيفرة دون الخوف من كسر بعض الوظائف
  • الجودة: تقليل الأخطاء وتحسين التصميم
  • التعاون: تسهيل العمل ضمن الفريق
  • النشر المستمر: أتمتة عمليات التحقق قبل النشر

أنواع الاختبارات

الاختبارات الوحدوية

تتحقق الاختبارات الوحدوية من حسن عمل أصغر وحدات الشيفرة (الدوال، والأساليب، والأصناف) بمعزل عن غيرها.

// Fonction à tester
function somme(a, b) {
  return a + b;
}

// Test unitaire avec Jest
test('additionne correctement deux nombres', () => {
  expect(somme(2, 3)).toBe(5);
  expect(somme(-1, 1)).toBe(0);
  expect(somme(0, 0)).toBe(0);
});

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

تتحقق اختبارات التكامل من أن أجزاء مختلفة من النظام تعمل معًا بشكل صحيح.

// Test d'intégration avec Jest
describe('Service utilisateur', () => {
  test('crée un utilisateur et le récupère de la base de données', async () => {
    // Créer un utilisateur
    const userId = await userService.createUser({
      name: 'Alice',
      email: 'alice@example.com'
    });
    
    // Récupérer l'utilisateur
    const user = await userService.getUserById(userId);
    
    // Vérifier que l'utilisateur est correctement récupéré
    expect(user).toEqual({
      id: userId,
      name: 'Alice',
      email: 'alice@example.com'
    });
  });
});

الاختبارات الوظيفية / الاختبارات الشاملة (End-to-End, E2E)

تتحقق اختبارات E2E من سلوك التطبيق من وجهة نظر المستخدم النهائي.

// Test E2E avec Cypress
describe('Formulaire de connexion', () => {
  it('permet à un utilisateur de se connecter', () => {
    cy.visit('/login');
    
    cy.get('input[name="email"]').type('utilisateur@exemple.fr');
    cy.get('input[name="password"]').type('motdepasse123');
    cy.get('button[type="submit"]').click();
    
    // Vérifier que l'utilisateur est redirigé vers le tableau de bord
    cy.url().should('include', '/dashboard');
    cy.contains('Bienvenue, Utilisateur').should('be.visible');
  });
});

اختبارات المكوّنات

في التطبيقات القائمة على المكوّنات (React وVue وAngular)، تتحقق اختبارات المكوّنات من عرض مكوّنات واجهة المستخدم وسلوكها.

// Test de composant React avec React Testing Library
import { render, screen, fireEvent } from '@testing-library/react';
import Compteur from './Compteur';

test('incrémente le compteur lorsque le bouton est cliqué', () => {
  render(<Compteur />);
  
  // Vérifier l'état initial
  expect(screen.getByText('Compteur: 0')).toBeInTheDocument();
  
  // Cliquer sur le bouton
  fireEvent.click(screen.getByText('Incrémenter'));
  
  // Vérifier que le compteur a été incrémenté
  expect(screen.getByText('Compteur: 1')).toBeInTheDocument();
});

اختبارات الأداء

تتحقق اختبارات الأداء من أن تطبيقك يلبّي متطلبات السرعة والكفاءة.

// Test de performance avec Lighthouse CI
module.exports = {
  ci: {
    collect: {
      url: ['http://localhost:3000/'],
      numberOfRuns: 3,
    },
    assert: {
      assertions: {
        'first-contentful-paint': ['warn', { maxNumericValue: 2000 }],
        'interactive': ['error', { maxNumericValue: 3000 }],
        'max-potential-fid': ['error', { maxNumericValue: 100 }],
      },
    },
    upload: {
      target: 'temporary-public-storage',
    },
  },
};

اختبارات الأمان

تحدد اختبارات الأمان الثغرات المحتملة في تطبيقك.

// Exemple avec OWASP ZAP (via CLI)
// zap-cli quick-scan --self-contained --start-options "-config api.disablekey=true" https://example.com

أطر العمل والأدوات الشائعة للاختبار

Jest

Jest هو إطار عمل متكامل لاختبار JavaScript بحد أدنى من الإعدادات.

// Installation
// npm install --save-dev jest

// Configuration minimale (package.json)
{
  "scripts": {
    "test": "jest"
  }
}

// Exemple de test
const { somme } = require('./math');

describe('Fonctions mathématiques', () => {
  test('somme additionne deux nombres', () => {
    expect(somme(1, 2)).toBe(3);
  });
  
  test('somme gère les nombres négatifs', () => {
    expect(somme(-1, -2)).toBe(-3);
  });
  
  test('somme avec zéro retourne l\'autre nombre', () => {
    expect(somme(0, 5)).toBe(5);
  });
});

Mocha + Chai

Mocha هو إطار عمل مرن للاختبار، ويُستخدم غالبًا مع Chai لكتابة التأكيدات (assertions).

// Installation
// npm install --save-dev mocha chai

// Exemple de test
const { expect } = require('chai');
const { somme } = require('./math');

describe('Fonctions mathématiques', () => {
  it('somme additionne deux nombres', () => {
    expect(somme(1, 2)).to.equal(3);
  });
  
  it('somme gère les nombres négatifs', () => {
    expect(somme(-1, -2)).to.equal(-3);
  });
});

Cypress

Cypress هي أداة حديثة لاختبارات E2E تعمل داخل المتصفح.

// Installation
// npm install --save-dev cypress

// Exemple de test
describe('Page d\'accueil', () => {
  beforeEach(() => {
    cy.visit('/');
  });
  
  it('affiche le titre correct', () => {
    cy.get('h1').should('contain', 'Bienvenue sur notre site');
  });
  
  it('navigue vers la page À propos', () => {
    cy.get('nav').contains('À propos').click();
    cy.url().should('include', '/about');
    cy.get('h1').should('contain', 'À propos de nous');
  });
});

Playwright

Playwright هي أداة لاختبارات E2E تدعم عدة متصفحات.

// Installation
// npm install --save-dev @playwright/test

// Exemple de test
const { test, expect } = require('@playwright/test');

test('page d\'accueil a le bon titre', async ({ page }) => {
  await page.goto('https://example.com');
  
  // Vérifier le titre
  const title = await page.title();
  expect(title).toBe('Example Domain');
  
  // Vérifier le contenu
  const heading = await page.textContent('h1');
  expect(heading).toBe('Example Domain');
});

Testing Library

Testing Library هي عائلة من المكتبات لاختبار واجهات المستخدم بطريقة تتمحور حول المستخدم.

// Installation pour React
// npm install --save-dev @testing-library/react @testing-library/jest-dom

// Exemple de test
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import Formulaire from './Formulaire';

test('soumet le formulaire avec les valeurs correctes', () => {
  const handleSubmit = jest.fn();
  render(<Formulaire onSubmit={handleSubmit} />);
  
  // Remplir le formulaire
  fireEvent.change(screen.getByLabelText(/nom/i), {
    target: { value: 'Alice' },
  });
  
  fireEvent.change(screen.getByLabelText(/email/i), {
    target: { value: 'alice@example.com' },
  });
  
  // Soumettre le formulaire
  fireEvent.click(screen.getByRole('button', { name: /soumettre/i }));
  
  // Vérifier que la fonction onSubmit a été appelée avec les bonnes valeurs
  expect(handleSubmit).toHaveBeenCalledWith({
    nom: 'Alice',
    email: 'alice@example.com',
  });
});

Storybook

يتيح Storybook تطوير مكوّنات واجهة المستخدم واختبارها بمعزل عن غيرها.

// Installation
// npx sb init

// Exemple de story
// Button.stories.js
import React from 'react';
import { Button } from './Button';

export default {
  title: 'Components/Button',
  component: Button,
  argTypes: {
    backgroundColor: { control: 'color' },
  },
};

const Template = (args) => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
  label: 'Button',
};

مقاربات الاختبار

التطوير المدفوع بالاختبارات (Test-Driven Development, TDD)

يتبع TDD دورة من ثلاث خطوات:

  1. Red: كتابة اختبار يفشل
  2. Green: كتابة الحد الأدنى من الشيفرة لجعل الاختبار ينجح
  3. Refactor: تحسين الشيفرة دون تغيير سلوكها
// Exemple de TDD
// 1. Red: Écrire un test qui échoue
test('convertit les degrés Celsius en Fahrenheit', () => {
  expect(celsiusToFahrenheit(0)).toBe(32);
  expect(celsiusToFahrenheit(100)).toBe(212);
});

// 2. Green: Écrire le code minimal pour faire passer le test
function celsiusToFahrenheit(celsius) {
  return celsius * 9/5 + 32;
}

// 3. Refactor: Améliorer le code (si nécessaire)
function celsiusToFahrenheit(celsius) {
  const factor = 9/5;
  const freezingPointDiff = 32;
  return celsius * factor + freezingPointDiff;
}

التطوير المدفوع بالسلوك (Behavior-Driven Development, BDD)

يركّز BDD على السلوك المتوقع للنظام من وجهة نظر المستخدم.

// Exemple avec Cucumber.js
// Feature: Connexion utilisateur
//   En tant qu'utilisateur enregistré
//   Je veux pouvoir me connecter
//   Afin d'accéder à mon compte

// Scénario: Connexion réussie
//   Étant donné que je suis sur la page de connexion
//   Quand je saisis mon email "utilisateur@exemple.fr"
//   Et je saisis mon mot de passe "motdepasse123"
//   Et je clique sur le bouton "Se connecter"
//   Alors je devrais être redirigé vers la page "Tableau de bord"
//   Et je devrais voir "Bienvenue, Utilisateur"

// Implémentation des étapes
const { Given, When, Then } = require('@cucumber/cucumber');

Given('je suis sur la page de connexion', async function() {
  await this.navigateTo('/login');
});

When('je saisis mon email {string}', async function(email) {
  await this.fillField('email', email);
});

// ... autres étapes

الكائنات المحاكية (Mocks) والبدائل (Stubs) والمراقِبات (Spies)

Mocks

تستبدل الـ mocks التبعيات الحقيقية بنسخ محاكية.

// Exemple avec Jest
test('envoie un email de bienvenue lors de l\'inscription', () => {
  // Mock du service d'email
  const emailService = {
    sendWelcomeEmail: jest.fn()
  };
  
  // Système sous test
  const userService = new UserService(emailService);
  
  // Action
  userService.registerUser('alice@example.com', 'password123');
  
  // Assertion
  expect(emailService.sendWelcomeEmail).toHaveBeenCalledWith('alice@example.com');
});

Stubs

توفّر الـ stubs ردودًا محددة مسبقًا على استدعاءات الدوال.

// Exemple avec Sinon.js
const sinon = require('sinon');
const database = require('./database');

test('récupère les données utilisateur', async () => {
  // Créer un stub pour la méthode query
  const queryStub = sinon.stub(database, 'query');
  
  // Configurer le stub pour retourner des données spécifiques
  queryStub.withArgs('SELECT * FROM users WHERE id = ?', [42])
    .resolves([{ id: 42, name: 'Alice' }]);
  
  // Système sous test
  const userService = new UserService(database);
  
  // Action
  const user = await userService.getUserById(42);
  
  // Assertion
  expect(user).toEqual({ id: 42, name: 'Alice' });
  
  // Restaurer le stub
  queryStub.restore();
});

Spies

تراقب الـ spies استدعاءات الدوال دون تعديل سلوكها.

// Exemple avec Jest
test('enregistre les erreurs dans la console', () => {
  // Créer un spy sur console.error
  const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
  
  // Action qui devrait générer une erreur
  const calculator = new Calculator();
  calculator.divide(10, 0);
  
  // Assertion
  expect(errorSpy).toHaveBeenCalledWith('Division par zéro');
  
  // Restaurer le spy
  errorSpy.mockRestore();
});

الاختبارات غير المتزامنة

Promises

// Test avec Jest
test('récupère les données utilisateur', () => {
  return userService.getUserById(42)
    .then(user => {
      expect(user.name).toBe('Alice');
    });
});

// Ou avec async/await
test('récupère les données utilisateur', async () => {
  const user = await userService.getUserById(42);
  expect(user.name).toBe('Alice');
});

Callbacks

// Test avec Jest
test('lit un fichier', done => {
  fs.readFile('data.txt', 'utf8', (err, data) => {
    if (err) {
      done(err);
      return;
    }
    
    try {
      expect(data).toContain('Hello, world');
      done();
    } catch (error) {
      done(error);
    }
  });
});

تغطية الشيفرة

تقيس تغطية الشيفرة (code coverage) النسبة التي تُنفَّذ من شيفرتك أثناء الاختبارات.

// Configuration Jest pour la couverture
// package.json
{
  "jest": {
    "collectCoverage": true,
    "collectCoverageFrom": [
      "src/**/*.{js,jsx}",
      "!**/node_modules/**",
      "!**/vendor/**"
    ],
    "coverageThreshold": {
      "global": {
        "branches": 80,
        "functions": 80,
        "lines": 80,
        "statements": 80
      }
    }
  }
}

التكامل المستمر (CI)

ينفّذ التكامل المستمر اختباراتك تلقائيًا عند كل تعديل على الشيفرة.

# Exemple avec GitHub Actions
# .github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Set up Node.js
      uses: actions/setup-node@v2
      with:
        node-version: '16'
        
    - name: Install dependencies
      run: npm ci
      
    - name: Run tests
      run: npm test
      
    - name: Upload coverage
      uses: codecov/codecov-action@v2

أفضل الممارسات

  1. اختبر الوظائف الحرجة أولًا: ركّز على الشيفرة الأكثر أهمية
  2. اجعل الاختبارات بسيطة وسهلة القراءة: يجب أن يكون الاختبار سهل الفهم
  3. اختبار واحد، تأكيد واحد: ينبغي أن يتحقق كل اختبار من أمر واحد فقط
  4. اعزل اختباراتك: يجب ألا تعتمد الاختبارات على بعضها البعض
  5. استخدم بيانات اختبار واقعية: تجنّب البيانات المبسّطة أكثر من اللازم
  6. تجنّب الاختبارات الهشّة: يجب ألا تفشل الاختبارات لأسباب سطحية
  7. نفّذ الاختبارات بانتظام: ويُفضّل عند كل تعديل على الشيفرة
  8. حافظ على اختباراتك: الاختبارات هي شيفرة وتتطلب صيانة

أخطاء شائعة يجب تجنبها

  1. اختبار التنفيذ بدلاً من السلوك: ركّز على ما يفعله الكود، لا على كيفية قيامه بذلك
  2. تجاهل الحالات الحدّية: اختبر القيم القصوى وحالات الخطأ
  3. اختبار المسار السعيد فقط: لا تنسَ اختبار حالات الفشل
  4. اختبارات شديدة الارتباط بالكود: الاختبارات المفرطة في التحديد تنكسر بسهولة عند إعادة الهيكلة (refactoring)
  5. إهمال اختبارات الانحدار: أضف اختبارات للأخطاء التي تم إصلاحها
  6. الاختبارات البطيئة: تؤدي الاختبارات البطيئة إلى العزوف عن تنفيذها بشكل متكرر

الخاتمة

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

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

وباعتماد ثقافة الاختبار داخل فريقك واتباع أفضل الممارسات المعروضة في هذا المقال، يمكنك تحسين جودة تطبيقات JavaScript الخاصة بك وقابليتها للصيانة بشكل كبير.

InSkillCoach

نبذة عن InSkillCoach

خبير في التدريب والتقنيات

مدرّب متخصص في التقنيات المتقدمة والذكاء الاصطناعي، تدعمه شركة GNeurone Inc.

الشهادات:

  • AWS Certified Solutions Architect – Professional
  • شهادات Google Cloud
  • Microsoft Certified: DevOps Engineer Expert
  • Certified Kubernetes Administrator (CKA)
  • CompTIA Security+
852
292

التعليقات

التعليقات مدعومة عبر GitHub Discussions

سجّل الدخول باستخدام GitHub للمشاركة في النقاش

تم نسخ الرابط!