إنتقل إلى المحتوى الرئيسي

التطوير الموجه بالاختبار مع Cursor: الوحدة والتكامل والنهاية إلى النهاية

Cursor Testing Guide

الاختبارات ضرورية للحفاظ على جودة الكود والثقة في عمليات النشر. تساعد ميزات الذكاء الاصطناعي في Cursor في إنشاء الاختبارات واقتراح حالات الحافة وتحقيق تغطية أفضل، مما يجعل التطوير الموجه بالاختبار (TDD) أكثر سهولة. يغطي هذا الدليل سير عمل اختبارات الوحدة والتكامل والنهاية إلى النهاية.

المتطلبات الأساسية​

قبل البدء، ستحتاج إلى:

  • تثبيت Cursor (الإصدار الأحدث)
  • تثبيت Node.js
  • فهم أساسي لمفاهيم الاختبار

اختبارات الوحدة باستخدام Jest​

إعداد Jest​

تثبيت Jest والحزم ذات الصلة:

npm install --save-dev jest @types/jest ts-jest

تكوين jest.config.js:

module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.test.ts'],
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/**/*.test.ts',
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};

إنشاء اختبارات الوحدة باستخدام Cursor​

حدد دالة واطلب من Cursor إنشاء اختبارات:

// src/utils/calculator.ts
export const calculateDiscount = (
price: number,
discountPercent: number,
maxDiscount?: number
): number => {
if (price < 0 || discountPercent < 0 || discountPercent > 100) {
throw new Error('Invalid input');
}

const discount = (price * discountPercent) / 100;
const finalDiscount = maxDiscount ? Math.min(discount, maxDiscount) : discount;

return Number((price - finalDiscount).toFixed(2));
};

المطالبة:

اكتب اختبارات وحدة Jest شاملة لهذه الدالة. يجب أن تشمل:
- اختبارات المسار السعيد
- حالات الحافة (0، سالب، قيم الحدود)
- حالات الخطأ
- اختبارات لمعامل maxDiscount

النتيجة التي أنشأها Cursor:

// src/utils/__tests__/calculator.test.ts
import { calculateDiscount } from '../calculator';

describe('calculateDiscount', () => {
describe('happy path', () => {
it('should calculate 10% discount correctly', () => {
expect(calculateDiscount(100, 10)).toBe(90);
});

it('should calculate 50% discount correctly', () => {
expect(calculateDiscount(200, 50)).toBe(100);
});
});

describe('maxDiscount parameter', () => {
it('should apply maxDiscount when discount exceeds it', () => {
expect(calculateDiscount(1000, 50, 100)).toBe(900);
});

it('should ignore maxDiscount when discount is smaller', () => {
expect(calculateDiscount(100, 10, 50)).toBe(90);
});
});

describe('edge cases', () => {
it('should return original price with 0% discount', () => {
expect(calculateDiscount(100, 0)).toBe(100);
});

it('should return 0 with 100% discount', () => {
expect(calculateDiscount(100, 100)).toBe(0);
});

it('should handle decimal prices', () => {
expect(calculateDiscount(99.99, 10)).toBe(89.99);
});
});

describe('error cases', () => {
it('should throw error for negative price', () => {
expect(() => calculateDiscount(-10, 10)).toThrow('Invalid input');
});

it('should throw error for negative discount', () => {
expect(() => calculateDiscount(100, -5)).toThrow('Invalid input');
});

it('should throw error for discount over 100%', () => {
expect(() => calculateDiscount(100, 101)).toThrow('Invalid input');
});
});
});

اختبار مكونات React​

// src/components/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
it('renders with correct text', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});

it('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});

it('is disabled when loading', () => {
render(<Button isLoading>Loading</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
});

Vitest للمشاريع الحديثة​

تكوين Vitest​

// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
thresholds: {
lines: 80,
functions: 80,
branches: 80,
statements: 80,
},
},
},
});

مثال على اختبار Vitest​

// src/utils/formatDate.test.ts
import { describe, it, expect } from 'vitest';
import { formatDate } from './formatDate';

describe('formatDate', () => {
it('formats ISO date to readable string', () => {
const result = formatDate('2024-01-15');
expect(result).toBe('January 15, 2024');
});

it('handles invalid dates gracefully', () => {
expect(() => formatDate('invalid')).toThrow();
});
});
Vitest مقابل Jest

يوفر Vitest دعمًا أصليًا لـ TypeScript وتنفيذًا أسرع وتوافقًا أفضل مع ESM. استخدم Vitest للمشاريع الجديدة، واحتفظ بـ Jest إذا كان لديك مجموعة اختبارات موجودة.

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

اختبارات تكامل واجهة برمجة التطبيقات​

// tests/integration/users.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createApp } from '../../src/app';
import { setupTestDB, teardownTestDB } from '../helpers/database';

const app = createApp();

describe('Users API Integration', () => {
beforeAll(async () => {
await setupTestDB();
});

afterAll(async () => {
await teardownTestDB();
});

describe('POST /api/users', () => {
it('creates a user and persists to database', async () => {
const response = await app.request('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Integration Test',
password: 'password123',
}),
});

expect(response.status).toBe(201);
const body = await response.json();
expect(body.data.email).toBe('[email protected]');
expect(body.data.id).toBeDefined();
});
});
});

إعداد تكامل قاعدة البيانات​

// tests/helpers/database.ts
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export async function setupTestDB() {
// تشغيل الترحيلات
// بذر بيانات الاختبار
}

export async function teardownTestDB() {
// تنظيف بيانات الاختبار
await prisma.$executeRaw`TRUNCATE TABLE users CASCADE`;
await prisma.$disconnect();
}

اختبارات النهاية إلى النهاية​

إعداد Cypress​

npm install --save-dev cypress
npx cypress open

كتابة اختبارات E2E باستخدام Cursor​

// cypress/e2e/auth.cy.js
describe('Authentication Flow', () => {
beforeEach(() => {
cy.visit('/login');
});

it('allows user to log in with valid credentials', () => {
cy.get('[data-testid="email-input"]').type('[email protected]');
cy.get('[data-testid="password-input"]').type('password123');
cy.get('[data-testid="login-button"]').click();

cy.url().should('include', '/dashboard');
cy.get('[data-testid="welcome-message"]').should('contain', 'Welcome');
});

it('shows error for invalid credentials', () => {
cy.get('[data-testid="email-input"]').type('[email protected]');
cy.get('[data-testid="password-input"]').type('wrongpassword');
cy.get('[data-testid="login-button"]').click();

cy.get('[data-testid="error-message"]')
.should('be.visible')
.and('contain', 'Invalid credentials');
});
});

بديل Playwright​

// tests/e2e/checkout.spec.ts
import { test, expect } from '@playwright/test';

test('user can complete checkout', async ({ page }) => {
await page.goto('/products');

// إضافة عنصر إلى السلة
await page.click('[data-testid="add-to-cart-1"]');
await page.click('[data-testid="cart-icon"]');

// المتابعة إلى الدفع
await page.click('[data-testid="checkout-button"]');

// ملء معلومات الشحن
await page.fill('[name="address"]', '123 Test St');
await page.fill('[name="city"]', 'Test City');
await page.selectOption('[name="country"]', 'US');

// إكمال الطلب
await page.click('[data-testid="place-order"]');

await expect(page.locator('[data-testid="order-confirmation"]')).toBeVisible();
});

التغطية وجودة الاختبار​

تقارير التغطية​

إنشاء وعرض التغطية:

# Jest
npm test -- --coverage

# Vitest
npx vitest --coverage

تحسين التغطية بالذكاء الاصطناعي​

شارك تقرير التغطية الخاص بك مع Cursor:

تغطية الاختبارات الخاصة بي عند 65%. الأسطر غير المغطاة هي:
[الصق تقرير التغطية]

اقترح اختبارات لتغطية حالات الحافة هذه.

أفضل ممارسات الاختبار​

الممارسةالوصف
نمط AAAالترتيب والتنفيذ والتأكيد - هيكلة جميع الاختبارات بوضوح
تأكيد واحد لكل اختبارالحفاظ على الاختبارات مركزة وسهلة القراءة
أسماء وصفيةيجب أن تقرأ أسماء الاختبارات كوثائق
محاكاة التبعيات الخارجيةعزل الوحدات بمحاكاة قواعد البيانات وواجهات برمجة التطبيقات
اختبار السلوك وليس التنفيذالاختبارات الآمنة لإعادة البناء تتحقق من النتائج
استخدام data-testidإضافة محددات مستقرة لاختبارات E2E

أدوات مساعدة للاختبار​

// src/test/factories.ts
import { faker } from '@faker-js/faker';

export const createUserFixture = (overrides = {}) => ({
id: faker.string.uuid(),
email: faker.internet.email(),
name: faker.person.fullName(),
createdAt: new Date(),
...overrides,
});

export const createProductFixture = (overrides = {}) => ({
id: faker.string.uuid(),
name: faker.commerce.productName(),
price: Number(faker.commerce.price()),
...overrides,
});

تشغيل الاختبارات في Cursor​

التكامل مع الطرفية​

# تشغيل جميع الاختبارات
npm test

# التشغيل في وضع المشاهدة
npm test -- --watch

# تشغيل ملف محدد
npm test -- src/utils/calculator.test.ts

# التشغيل مع التغطية
npm test -- --coverage

التكامل المستمر​

مثال مع GitHub Actions​

# .github/workflows/test.yml
name: Tests
on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm test
- run: npm run test:e2e

مرجع سريع​

المهمةمطالبة Cursor
إنشاء اختبارات وحدة"Write Jest tests for this function with edge cases"
محاكاة واجهة برمجة تطبيقات خارجية"Create a mock for this API client using MSW"
إصلاح اختبار فاشل"This test is failing with [error]. Fix it."
تحسين التغطية"Suggest tests to improve coverage for this file"
اختبار E2E للتدفق"Write a Cypress test for the user checkout flow"

موارد ذات صلة​

آخر تحديث: يوليو 2025