DEV Community

Cover image for كيفية اختبار واجهات برمجة تطبيقات راست (Rust API)؟
Yusuf Khalidd
Yusuf Khalidd

Posted on • Originally published at apidog.com

كيفية اختبار واجهات برمجة تطبيقات راست (Rust API)؟

لغة Rust تمنحك خادم HTTP سريعًا وآمنًا من حيث الأنواع بسرعة، لكنها لا تمنحك دائمًا حلقة تغذية راجعة سريعة لاختبار عقد الـ API. cargo test مهم، لكنه يدور حول الكود لا حول HTTP الفعلي. لاختبار أكواد الحالة، شكل JSON، الرؤوس، JWT، وطلبات الواجهة الأمامية قبل اكتمال المعالج، تحتاج أداة تعمل خارج سلسلة أدوات Rust وتتحدث مع الخادم قيد التشغيل.

جرّب Apidog اليوم

هذا الدليل يوضح سير عمل عملي لاختبار واجهة برمجة تطبيقات Rust داخل Apidog: توصيل Apidog بخادم Axum أو Actix، إنشاء طلبات، التحقق من JSON المُسلسل بواسطة Serde، التعامل مع JWT، إنشاء mocks للواجهة الأمامية، ثم تشغيل كل ذلك في CI. إذا كنت تستخدم Postman أو curl، فستحصل أيضًا على مواصفات OpenAPI، عناوين mock قابلة للمشاركة، وبيئات عمل جماعية. يمكنك قراءة قصة ترحيل Postman لاحقًا؛ هنا نركز على Rust.

ملخص سريع

  • شغّل خادم Rust محليًا باستخدام cargo run.
  • أضف http://localhost:3000 كـ baseUrl في بيئة Apidog.
  • أنشئ طلب GET /healthz كاختبار دخان.
  • اختبر JSON القادم من Serde باستخدام assertions بعد كل طلب.
  • خزّن JWT في متغير {{token}} وطبّق Bearer Auth على مستوى المجلد.
  • استخدم Mock endpoints حتى تعمل الواجهة الأمامية قبل اكتمال المعالج.
  • احفظ الطلبات كسيناريو اختبار وشغّلها في CI باستخدام apidog-cli.

لماذا تختبر Rust API خارج cargo test؟

cargo test ممتاز لاختبارات الوحدة والتكامل داخل Rust، لكنه ليس دائمًا أفضل طبقة لاختبار عقد HTTP العام.

إذا أردت التأكد من أن المعالج يعيد:

  • Status code صحيحًا
  • JSON بالشكل المتوقع
  • Headers صحيحة
  • رسالة خطأ واضحة عند الإدخال غير الصحيح
  • سلوكًا ثابتًا أمام الواجهة الأمامية

فستحتاج عادة إلى كتابة اختبارات HTTP داخل Rust أو استخدام curl يدويًا. هذا يعمل، لكنه يصبح مكلفًا مع تغيّر المعالجات.

Apidog يضيف طبقة عقد فوق الخادم قيد التشغيل:

  1. الطلبات محفوظة وقابلة لإعادة التشغيل.
  2. التأكيدات موجودة بجانب الطلب.
  3. الفريق كله يرى نفس العقود.
  4. يمكن تشغيل نفس السيناريو في CI.
  5. يمكن توليد OpenAPI من الطلبات المحفوظة.

الفكرة: اترك cargo test لاختبار كود Rust، واستخدم Apidog لاختبار واجهة HTTP كما يراها العملاء.

الخطوة 1: أضف خادم Rust كبيئة في Apidog

ابدأ بخادم Axum بسيط:

use axum::{routing::get, Router};
use tokio::net::TcpListener;

#[tokio::main]
async fn main() {
    let app = Router::new().route("/healthz", get(|| async { "ok" }));

    let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();

    axum::serve(listener, app).await.unwrap();
}
Enter fullscreen mode Exit fullscreen mode

شغّله:

cargo run
Enter fullscreen mode Exit fullscreen mode

في Apidog:

  1. أنشئ مشروعًا جديدًا.
  2. افتح إدارة البيئات.
  3. أضف بيئة باسم Rust Local.
المتغير القيمة
baseUrl http://localhost:3000
token اتركها فارغة الآن
apiVersion v1

أضف بيئة ثانية باسم Rust Staging واستخدم عنوان URL التجريبي. بهذه الطريقة يمكنك التبديل بين المحلي والتجريبي بدون تعديل كل طلب يدويًا.

الخطوة 2: أنشئ أول طلب

داخل المشروع، أنشئ مجلدًا باسم Rust API، ثم أضف طلبًا جديدًا:

  • Method: GET
  • URL: {{baseUrl}}/healthz

اضغط Send.

النتيجة المتوقعة:

200 OK
ok
Enter fullscreen mode Exit fullscreen mode

احفظ الطلب باسم:

health-check
Enter fullscreen mode Exit fullscreen mode

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

إذا ظهر خطأ connection refused، تحقق من:

  • أن الخادم يعمل.
  • أن المنفذ صحيح.
  • أن الخادم مربوط بـ 0.0.0.0:3000 وليس فقط 127.0.0.1:3000، خصوصًا إذا كنت تستخدم Docker أو بيئة محلية مختلفة.

الخطوة 3: اختبر طلب واستجابة JSON باستخدام Serde

أضف مسارًا لإنشاء مستخدم:

use axum::{extract::Json, routing::post, Router};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct CreateUser {
    name: String,
    email: String,
}

#[derive(Serialize)]
struct User {
    id: u64,
    name: String,
    email: String,
}

async fn create_user(Json(payload): Json<CreateUser>) -> Json<User> {
    Json(User {
        id: 1,
        name: payload.name,
        email: payload.email,
    })
}

let app = Router::new().route("/users", post(create_user));
Enter fullscreen mode Exit fullscreen mode

في Apidog، أنشئ طلبًا جديدًا:

  • Method: POST
  • URL: {{baseUrl}}/users
  • Body: JSON
{
  "name": "Ada Lovelace",
  "email": "ada@example.com"
}
Enter fullscreen mode Exit fullscreen mode

أرسل الطلب واحفظه باسم:

create-user
Enter fullscreen mode Exit fullscreen mode

أضف assertions في تبويب Tests:

pm.test("Status is 200", () => {
  pm.expect(pm.response.code).to.eql(200);
});

pm.test("Body has id, name, email", () => {
  const body = pm.response.json();

  pm.expect(body).to.have.property("id");
  pm.expect(body.name).to.eql("Ada Lovelace");
  pm.expect(body.email).to.match(/^[^@]+@[^@]+$/);
});
Enter fullscreen mode Exit fullscreen mode

الآن إذا تغيّر شكل الاستجابة لاحقًا بسبب serde، مثل إضافة:

#[serde(rename_all = "camelCase")]
Enter fullscreen mode Exit fullscreen mode

فسيفشل اختبار العقد قبل أن يصل التغيير إلى الإنتاج.

الخطوة 4: غطِّ حالات رفض Serde

لا تختبر المسار السعيد فقط. اختبر المدخلات الخاطئة أيضًا.

أنشئ الطلبات التالية في Apidog:

الطلب Body المتوقع
create-user-missing-email { "name": "Ada" } 422، مع رسالة عن الحقل المفقود
create-user-extra-field { "name": "Ada", "email": "a@b.c", "admin": true } 200 إذا لم تستخدم deny_unknown_fields، أو 422 إذا استخدمته
create-user-wrong-type { "name": 1, "email": "a@b.c" } 422، مع رسالة عن النوع غير الصحيح

مثال assertion:

pm.test("Status is 422", () => {
  pm.expect(pm.response.code).to.eql(422);
});
Enter fullscreen mode Exit fullscreen mode

هذه الاختبارات توثق سياسة التحقق الفعلية. إذا قررت لاحقًا إضافة:

#[serde(deny_unknown_fields)]
Enter fullscreen mode Exit fullscreen mode

فسيتغير سلوك extra-field، وسيظهر ذلك فورًا في Apidog وCI.

الخطوة 5: اختبر المسارات المحمية بواسطة JWT

غالبًا ما تكون واجهات Rust الإنتاجية محمية بطبقة مصادقة. مثال مبسط باستخدام JWT:

use axum::{Json, http::StatusCode};
use axum_extra::extract::cookie::PrivateCookieJar;
use jsonwebtoken::{decode, DecodingKey, Validation};

async fn me(jar: PrivateCookieJar) -> Result<Json<User>, StatusCode> {
    let token = jar
        .get("token")
        .ok_or(StatusCode::UNAUTHORIZED)?;

    let claims = decode::<Claims>(
        token.value(),
        &DecodingKey::from_secret(b"secret"),
        &Validation::default(),
    )
    .map_err(|_| StatusCode::UNAUTHORIZED)?;

    Ok(Json(User {
        id: claims.claims.sub,
        name: "Ada".into(),
        email: "ada@example.com".into(),
    }))
}
Enter fullscreen mode Exit fullscreen mode

بدل إنشاء JWT يدويًا لكل طلب، أضف Pre-Request Script على مستوى المجلد في Apidog:

const jwt = require("jsonwebtoken");

const token = jwt.sign(
  {
    sub: 1,
    exp: Math.floor(Date.now() / 1000) + 3600
  },
  "secret"
);

pm.environment.set("token", token);
Enter fullscreen mode Exit fullscreen mode

ثم من إعدادات المجلد:

  • Auth Type: Bearer Token
  • Token: {{token}}

الآن ترث كل الطلبات داخل المجلد نفس إعداد المصادقة، ويتم توليد JWT جديد قبل التشغيل.

للمزيد حول اختبار JWT، راجع كيفية اختبار مصادقة JWT في واجهات برمجة التطبيقات.

الخطوة 6: اختبر Streaming و Server-Sent Events

يدعم Axum وActix التدفق كجزء أساسي من HTTP. في Axum، يمكن أن يعيد المعالج Sse مبنيًا على futures::Stream.

تنسيق SSE عادة يكون:

data: { ... }

data: { ... }

event: done
data: {}
Enter fullscreen mode Exit fullscreen mode

في Apidog:

  1. أنشئ طلب GET لنقطة SSE.
  2. أرسله.
  3. إذا كان Content-Type هو text/event-stream، ستظهر الاستجابة في وضع streaming.
  4. راقب كل frame مع زمن وصوله.

ما يجب اختباره:

  • أول event يصل ضمن الزمن المتوقع.
  • event معين مثل event: done يصل قبل إغلاق الاتصال.
  • الاتصال لا يبقى مفتوحًا للأبد.
  • يمكن ضبط timeout للطلب حتى يفشل الاختبار عند التدفق غير المنتهي.

إذا كنت تستخدم WebSocket بدل SSE، استخدم نوع طلب WebSocket في Apidog واحفظ تسلسل الرسائل والاستجابات بنفس الفكرة.

الخطوة 7: أنشئ Mock API لتطوير الواجهة الأمامية بالتوازي

الواجهة الأمامية لا تحتاج انتظار اكتمال كل معالج Rust. يمكنها العمل ضد mock ثابت طالما أن العقد واضح.

في Apidog:

  1. افتح طلب create-user.
  2. اختر Smart Mock.
  3. فعّله.

سيُنشئ Apidog endpoint مثل:

https://mock.apidog.com/m1/<projectId>/users
Enter fullscreen mode Exit fullscreen mode

ويمكن للواجهة الأمامية إرسال POST إليه بنفس شكل الطلب الحقيقي.

لـ mock ديناميكي، استخدم Advanced Mock:

return {
  id: Math.floor(Math.random() * 10000),
  name: body.name,
  email: body.email,
  createdAt: new Date().toISOString()
};
Enter fullscreen mode Exit fullscreen mode

النتيجة: الواجهة الأمامية تحصل على id وcreatedAt واقعيين بدون انتظار معالج Rust.

عندما يصبح المعالج جاهزًا، تغيّر الواجهة الأمامية baseUrl من mock إلى:

http://localhost:3000
Enter fullscreen mode Exit fullscreen mode

ولا تحتاج لتغيير شكل الطلب.

لنفس النمط في بيئات أخرى، راجع بناء واختبار واجهة برمجة تطبيقات Spring Boot وسير عمل اختبار واجهة برمجة التطبيقات العام.

الخطوة 8: احفظ الطلبات كسيناريو CI

حوّل الطلبات إلى سيناريو اختبار قابل للتشغيل بدون واجهة رسومية.

مثال تسلسل:

  1. health-check

    تأكيد 200.

  2. create-user

    تأكيد 200، ثم خزّن body.id في متغير.

  3. create-user-missing-email

    تأكيد 422.

  4. me

    يستخدم JWT من Pre-Request Script، ثم يؤكد 200.

  5. طلب SSE

    يؤكد أن التدفق يكتمل خلال 5 ثوانٍ.

مثال لتخزين id بعد إنشاء المستخدم:

const body = pm.response.json();

pm.environment.set("userId", body.id);
Enter fullscreen mode Exit fullscreen mode

ثم يمكن استخدامه لاحقًا:

pm.test("Returned id matches created user", () => {
  const body = pm.response.json();

  pm.expect(String(body.id)).to.eql(String(pm.environment.get("userId")));
});
Enter fullscreen mode Exit fullscreen mode

صدّر السيناريو كملف JSON وضعه في المستودع:

tests/apidog/contract.json
Enter fullscreen mode Exit fullscreen mode

مثال GitHub Actions:

- name: Run API contract tests
  run: |
    cargo build --release
    ./target/release/myserver &
    sleep 2
    apidog-cli run tests/apidog/contract.json --env "Rust Local"
Enter fullscreen mode Exit fullscreen mode

الآن كل Pull Request يلمس معالجًا يحصل على اختبار عقد فعلي ضد binary قيد التشغيل.

الخطوة 9: أنشئ OpenAPI من الطلبات المحفوظة

بعد استقرار الطلبات:

  1. افتح قائمة Export في Apidog.
  2. اختر OpenAPI 3.1.
  3. صدّر المواصفة.

ستحصل على ملف مواصفات مبني من الطلبات والأمثلة التي اختبرتها فعليًا.

يمكنك أيضًا تشغيل التصدير من CI:

apidog-cli export --format openapi --output openapi.json
Enter fullscreen mode Exit fullscreen mode

ثم أضف openapi.json إلى المستودع حتى يستخدمه من يحتاج إلى توليد عملاء TypeScript أو Swift أو Kotlin أو Python.

الأسئلة الشائعة

هل يعمل Apidog مع Axum وActix-web؟

نعم. Apidog يتعامل مع HTTP، لذلك يعمل مع Axum وActix-web وRocket وWarp وPoem وLoco. النقطة المهمة في التطوير المحلي هي ربط الخادم بـ 0.0.0.0 عند الحاجة.

كيف أختبر المعالجات التي تتعطل؟

يمكنك وضع CatchPanicLayer من tower-http أمام الـ router لتحويل panic إلى 500. بعدها أنشئ طلبًا في Apidog يؤدي إلى هذا المسار وتأكد من status code.

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

هل يمكن تشغيل Apidog ضد binary داخل Docker؟

نعم. اجعل baseUrl يشير إلى المنفذ المكشوف للحاوية. إذا كنت تستخدم Docker Compose، تأكد أن مشغل Apidog يستطيع الوصول إلى نفس الشبكة أو استخدم المنفذ المربوط بالمضيف.

ماذا عن gRPC؟

Apidog يدعم نوع طلب gRPC. استورد ملفات .proto، اختر service وmethod، املأ request body، ثم أرسل. نفس مفاهيم البيئات والمصادقة وسيناريوهات الاختبار تنطبق أيضًا.

هل يغني ذلك عن cargo test؟

لا. استخدم cargo test لاختبارات منطق Rust الداخلي. استخدم Apidog لاختبار العقد التشغيلي عبر HTTP.

الطبقتان تكملان بعضهما:

  • cargo test يكتشف دالة مكسورة.
  • Apidog يكتشف JSON خاطئًا، status code مختلفًا، header مفقودًا، أو سلوك مصادقة غير متوقع.

هل Apidog مجاني لمشاريع Rust مفتوحة المصدر؟

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

الخلاصة

واجهات Rust تحتاج اختبارًا لا ينتظر المترجم في كل مرة. باستخدام Apidog يمكنك بناء مجموعة طلبات تتحقق من HTTP الحقيقي، JSON الحقيقي، JWT، mocks للواجهة الأمامية، وسيناريو CI يعمل ضد binary فعلي.

ابدأ بـ GET /healthz، أضف طلبات JSON، غطِّ حالات Serde الفاشلة، ثم شغّل السيناريو في CI. بعد ذلك يصبح كل تغيير في Axum أو Actix اختبار عقد واضحًا بدل مفاجأة في وقت التشغيل.

قم بتنزيل Apidog ووجهه إلى خادم Rust الخاص بك. الإعداد يستغرق دقائق، والنتيجة عقد API مستقل عن cargo وقابل للمشاركة مع الفريق.

Top comments (0)