Get a quote

اختبار API في Go لأنظمة SaaS الإنتاجية: الأنماط التي تكتشف الأخطاء الحقيقية

اختبارات الوحدة مع التبعيات المحاكاة لا تكتشف تقريباً أياً من الأخطاء التي تكسر فعلاً خلفيات SaaS في الإنتاج. الأخطاء التي تسبب الحوادث هي إخفاقات التكامل: استعلامات SQL خاطئة، حالات قاعدة بيانات غير متوقعة، عدم تطابق في التسلسل بين الخدمات.

اختبارات الوحدة مع التبعيات المحاكاة لا تكتشف تقريباً أياً من الأخطاء التي تكسر فعلاً خلفيات SaaS في الإنتاج. الأخطاء التي تسبب الحوادث هي إخفاقات التكامل: استعلامات SQL خاطئة، حالات قاعدة بيانات غير متوقعة، عدم تطابق في التسلسل بين الخدمات، وظروف تسابق لا تظهر إلا تحت حمل متزامن.

استراتيجيات الاختبار التي تجد هذه الأخطاء تبدو مختلفة جداً عن اختبارات الوحدة كما تُشرح في الكتب المدرسية. هذا هو نهج الاختبار الذي تستخدمه Voxire لـ RTYLR، الخادمة لعمليات المطاعم والتجزئة في لبنان ودول الخليج.

لماذا تفوت الاختبارات الغنية بالمحاكاة أخطاء الإنتاج

اختبار نموذجي بمحاكاة كثيفة لنقطة نهاية تأكيد الطلب:

func TestConfirmOrder_Success(t *testing.T) {
    mockDB := &MockOrderRepository{}
    mockDB.On("GetOrder", orderID).Return(pendingOrder, nil)
    mockDB.On("UpdateStatus", orderID, "confirmed").Return(nil)
    svc := NewOrderService(mockDB)
    err := svc.ConfirmOrder(ctx, orderID)
    assert.NoError(t, err)
}

هذا الاختبار يجتاز. سيجتاز أيضاً لو كان في استعلام SQL خطأ إملائي، أو كان قيد قاعدة بيانات يمنع الانتقال في الحالة، أو كان تسلسل JSONB غير صحيح. المحاكاة تطبق عقد الواجهة بشكل مثالي. لكن الأخطاء ليست في عقد الواجهة. الأخطاء في تطبيق العقد.

اختبار التكامل مع قاعدة بيانات حقيقية

البديل هو الاختبار مقابل نسخة PostgreSQL حقيقية، تُشغَّل في Docker لكل جولة اختبار:

func TestMain(m *testing.M) {
    pool, err := dockertest.NewPool("")
    // ...
    resource, err := pool.Run("postgres", "16", []string{
        "POSTGRES_PASSWORD=test",
        "POSTGRES_DB=testdb",
    })
    // ...
    testDB = setupSchema()
    code := m.Run()
    pool.Purge(resource)
    os.Exit(code)
}

هذا الاختبار يُشغِّل ترحيلات SQL الفعلية مقابل قاعدة بيانات حقيقية. أي خطأ في المخطط أو فهرس مفقود أو انتهاك قيد يظهر فوراً أثناء تنفيذ الاختبار، لا أثناء نشر الإنتاج.

عزل الاختبارات: المعاملات لا الحذف

النهج الشائع لعزل اختبارات التكامل هو حذف محتوى جميع الجداول قبل كل اختبار. هذا يعمل لكنه بطيء.

نهج أسرع: التفاف كل اختبار في معاملة والتراجع عنها في النهاية.

func withTestTx(t *testing.T, fn func(tx *sql.Tx)) {
    t.Helper()
    tx, err := testDB.Begin()
    if err != nil {
        t.Fatalf("begin tx: %v", err)
    }
    defer tx.Rollback()
    fn(tx)
}

التراجع في النهاية يترك قاعدة البيانات في حالة نظيفة للاختبار التالي دون تكلفة حذف الجداول.

اختبار عزل المستأجرين

في نظام متعدد المستأجرين، أهم فئة اختبار هي العزل: التأكد من أن طلبات المستأجر أ لا تستطيع قراءة أو تعديل بيانات المستأجر ب.

func TestMenuItemQuery_TenantIsolation(t *testing.T) {
    withTestTx(t, func(tx *sql.Tx) {
        tenantA := insertTestTenant(tx)
        tenantB := insertTestTenant(tx)
        itemA := insertTestMenuItem(tx, tenantA.ID)
        _ = insertTestMenuItem(tx, tenantB.ID)

        repo := NewMenuRepository(tx)
        items, err := repo.ListMenuItems(ctx, tenantA.ID)
        assert.NoError(t, err)
        assert.Len(t, items, 1)
        assert.Equal(t, itemA.ID, items[0].ID)
    })
}

هذا الصنف من الاختبارات وجد ثلاثة أخطاء حقيقية في كود RTYLR حيث كان فلتر tenant_id مفقوداً من استعلام يُصلح عبر جدولين.

اختبار سيناريوهات المزامنة

لنظام المزامنة دون اتصال في RTYLR، اختبارات التكامل تحاكي سيناريوهات إعادة اتصال الطرفية:

func TestSync_OfflineOrdersApplied(t *testing.T) {
    withTestTx(t, func(tx *sql.Tx) {
        terminal := insertTestTerminal(tx)
        offlineOrders := []Event{
            makeOrderCreatedEvent(terminal.ID),
            makeOrderCreatedEvent(terminal.ID),
        }
        syncSvc := NewSyncService(tx)
        resp, err := syncSvc.ProcessSync(ctx, SyncRequest{
            TerminalID:    terminal.ID,
            PendingEvents: offlineOrders,
        })
        assert.NoError(t, err)
        orders, _ := listOrdersByTerminal(tx, terminal.ID)
        assert.Len(t, orders, 2)
    })
}

اختبار التحميل والتزامن

الأخطاء التي تتخلل اختبارات التكامل تظهر تحت حمل متزامن. شغِّل الكشف عن التعارض في مجموعة الاختبار: go test -race ./.... هذا يكتشف ظروف التسابق التي لا تتجلى كإخفاقات اختبار في جولات أحادية الخيط لكن تسبب أعطالاً أو تلفاً للبيانات تحت الحمل.

دروس من اختبارات RTYLR

ثلاثة أشياء غيرت أكثر الأخطاء اكتشافاً لكل ساعة هندسية:

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

ثانياً، مجموعة اختبارات عزل المستأجرين تعمل كبوابة إلزامية في CI. أي استعلام يلمس بيانات مؤطرة بمستأجر دون فلتر tenant_id يُكتشف قبل أن يصل الكود إلى الإنتاج.

ثالثاً، بيانات اختبار محفوظة في ملفات SQL أفضل من دوال مصنع Go لسيناريوهات البيانات المعقدة. تثبيت SQL يمثل مطعماً بخمسة عناصر قائمة وحسابَي موظف وثلاثة طلبات معلقة قابل للقراءة وإعادة الاستخدام عبر حالات الاختبار.

هل تريد استراتيجية اختبار تجد الأخطاء الحقيقية؟

إذا كنت تبني خلفيات Go SaaS في لبنان أو MENA وتريد استراتيجية اختبار تجد الأخطاء الحقيقية قبل أن تصل إلى الإنتاج، Voxire يمكنها مساعدتك في تصميم النهج الصحيح لبنية نظامك.

https://voxire.com/get-a-quote/

العودة إلى المدونة
Chat on WhatsApp