لماذا اختبار E2E هو بوليصة التأمين الخاصة بك
\n\nتخيل: لقد كتبت خدمة مصغرة رائعة بلغة Go. اختبارات الوحدة خضراء، واختبارات التكامل أيضًا. تنشرها في الإنتاج، و... يشتكي المستخدمون من أن سلة التسوق لا تُفرغ بعد الدفع. مألوف؟ المشكلة هي أن اختبار E2E لتطبيقات Go غالبًا ما يُؤجل "لوقت لاحق". وهذا خطأ.
\n\nالاختبارات الشاملة (End-to-End) هي الطريقة الوحيدة للتحقق من أن نظامك يعمل كوحدة متكاملة: من طلب HTTP إلى الكتابة في قاعدة البيانات والعودة. في هذه المقالة، لن نتحدث فقط عن النظرية - سأريكم أمثلة كود حقيقية بلغة Go يمكنكم نسخها وتشغيلها اليوم.
\n\nما هو اختبار E2E في سياق Golang؟
\n\nاختبار E2E في Go هو التحقق من سيناريو المستخدم الكامل: بدء التطبيق، تنفيذ منطق الأعمال، التفاعل مع الخدمات الخارجية (قواعد البيانات، التخزين المؤقت، قوائم الانتظار) والتحقق من النتيجة. على عكس اختبارات الوحدة، نحن لا نستخدم mock لقاعدة البيانات - بل نقوم بتشغيل PostgreSQL حقيقي في حاوية.
\n\nكيف يختلف E2E عن اختبارات التكامل؟
\n\nغالبًا ما يتم الخلط بين هذه المفاهيم. دعنا نوضح الأمور:
\n\n- \n
- اختبارات الوحدة - تختبر دالة واحدة. سريعة ومعزولة. \n
- اختبارات التكامل - تختبر ربط الوحدات (مثل: المستودع + قاعدة البيانات). \n
- اختبارات E2E - تشغل التطبيق بأكمله وتحاكي إجراءات المستخدم الحقيقي. \n
لـ الاختبار الشامل في Golang سنستخدم الحزمة القياسية testing بالإضافة إلى أدوات قوية: testcontainers-go و dockertest.
إعداد البيئة: Docker Compose و Testcontainers
\n\nأسهل طريقة لتنظيم اختبار e2e في go هي استخدام Docker. لديك طريقتان:
\n\nالخيار 1: Docker Compose (لـ CI/CD)
\n\nأنشئ ملف docker-compose.test.yml:
version: '3.8'\nservices:\n db:\n image: postgres:15-alpine\n environment:\n POSTGRES_DB: testdb\n POSTGRES_USER: test\n POSTGRES_PASSWORD: test\n ports:\n - "5432:5432"\n \n app:\n build: .\n depends_on:\n - db\n environment:\n DATABASE_URL: "postgres://test:test@db:5432/testdb?sslmode=disable"\n ports:\n - "8080:8080"\n\n\nالخيار 2: Testcontainers (للتطوير المحلي)
\n\nمكتبة testcontainers-go تسمح بتشغيل الحاويات مباشرة من الاختبارات. هذا هو المفضل لدي - الاختبارات تدير البنية التحتية بنفسها.
// setup_test.go\npackage e2e_test\n\nimport (\n "context"\n "testing"\n "time"\n\n "github.com/testcontainers/testcontainers-go"\n "github.com/testcontainers/testcontainers-go/wait"\n)\n\nfunc setupPostgres(t *testing.T) string {\n t.Helper()\n \n ctx := context.Background()\n \n req := testcontainers.ContainerRequest{\n Image: "postgres:15-alpine",\n ExposedPorts: []string{"5432/tcp"},\n Env: map[string]string{\n "POSTGRES_DB": "testdb",\n "POSTGRES_USER": "test",\n "POSTGRES_PASSWORD": "test",\n },\n WaitingFor: wait.ForLog("database system is ready to accept connections").\n WithStartupTimeout(30 * time.Second),\n }\n \n container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{\n ContainerRequest: req,\n Started: true,\n })\n if err != nil {\n t.Fatalf("failed to start container: %v", err)\n }\n \n t.Cleanup(func() {\n container.Terminate(ctx)\n })\n \n host, _ := container.Host(ctx)\n port, _ := container.MappedPort(ctx, "5432")\n \n return "postgres://test:test@" + host + ":" + port.Port() + "/testdb?sslmode=disable"\n}\n\n\n