آزمایش Web APIs توسط Postman - قسمت چهارم - نوشتن آزمون‌ها و اسکریپت‌ها
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: پنج دقیقه

اسکریپت و آزمون نویسی مقدماتی با Postman را در قسمت دوم بررسی کردیم. در این قسمت نکات و امکانات پیشرفته‌تری را در این مورد مرور خواهیم کرد.


بررسی اجزای اولین آزمایش با Postman

آزمون‌های Postman، عموما یک سری Assertion هستند؛ به این معنا که در آن‌ها، مقداری از Response دریافتی از سرور را با مقداری مشخص و مورد انتظار، مقایسه می‌کنیم:
pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
});
برای مثال اگر اجرای درخواستی موفقیت آمیز باشد، در بسیاری از موارد status code مساوی 200 از سمت سرور بازگشت داده می‌شود. بنابراین در آزمونی، مقدار status code دریافتی را با مقدار مورد انتظار 200، مقایسه می‌کنیم. در کدهای فوق، سطر pm.response‌، یک Assertion نامیده می‌شود.

چند نکته:
- آزمون‌های Postman، با متد pm.test شروع می‌شوند. هدف از آن‌، نوشتن بدنه‌ی یک آزمون متشکل از چندین Assertion است. اولین پارامتر آن، نام رشته‌ای آزمون است. پارامتر دوم آن، یک callback function است که پس از پایان درخواست جاری، اجرا می‌شود.
- روش اجرای آزمون‌ها در اینجا non-blocking است. یعنی آزمون‌های نوشته شده، به موازات هم اجرا شده و نتایج و حتی خطاهای آن‌ها بر روی یکدیگر تاثیرگذار نیستند.
- برای یک Response و اجزای مختلف آن، می‌توان چندین آزمون را نوشت و هر آزمون می‌تواند چندین Assertion را داشته باشد.
- در Postman، آزمون‌ها تنها پس از پایان اجرای درخواست‌ها، اجرا می‌شوند.
- به شیء pm.response در اینجا، response assertion API می‌گویند. توسط آن می‌توان به اجزای مختلف response مانند status code، هدرها و یا بدنه‌ی بازگشتی از سمت سرور، دسترسی یافت و برای آن‌ها یک Assertion را نوشت.


لیستی از بررسی‌های متداول، در حین نوشتن آزمون‌های Postman

تا اینجا روش بررسی status code را در حین نوشتن آزمون‌های Postman بررسی کردیم. در جدول زیر، مهم‌ترین حالاتی را که جهت بررسی خروجی یک API می‌توان مدنظر داشت، برشمرده شده‌اند:
  نوع بررسی    Response assertions 
  بررسی مقدار status code دریافتی از سرور با مقدار مورد انتظار
pm.response.to.have.status(200);
  آیا status code دریافتی، معادل یکی از مقادیر مشخص شده‌است؟ 
pm.expect(pm.response.code).to.be.oneOf([201,202]);
  آیا status name دریافتی از سرور، معادل عبارت مشخصی است؟ 
response.to.have.status("Created");
  مقایسه‌ی مقدار responseTime با مقدار مورد انتظار 
pm.expect(pm.response.responseTime).to.be.below(200);
  آیا هدر تنظیم شده‌ی توسط response، دارای کلید مورد انتظار است؟ 
pm.response.to.have.header("X-Cache");
  آیا هدر تنظیم شده‌ی توسط response، دارای کلید و مقدار مشخصی است؟ 
pm.response.to.have.header("X-Cache", "HIT");
  آیا نام یکی از کوکی‌های تنظیم شده‌ی توسط response، معادل مقدار مورد انتظار است؟ 
pm.expect(pm.cookies.has("sessionId")).to.be.true;
  آیا نام و مقدار یکی از کوکی‌های تنظیم شده‌ی توسط response، معادل مقادیر مشخصی هستند؟ 
pm.expect(pm.cookies.get("sessionId")).to.equal("abcb9s");
  آیا بدنه‌ی response، دقیقا معادل مقدار مشخص است؟ 
pm.response.to.have.body("OK");
آیا بدنه‌ی response، حاوی مقدار مشخصی است؟ 
pm.expect(pm.response.text()).to.include("Order placed.");

یک نکته: در رابط کاربری Postman، زمانیکه برگه‌ی Tests را انتخاب می‌کنیم، کنار آن لیستی از code snippets نیز قرار دارند که با کلیک بر روی آن‌ها، می‌توان حالت عمومی اکثر موارد فوق را به صورت خودکار تولید کرد:



روش بررسی اجزای خروجی با فرمت JSON از سرور

فرض کنید API شما یک چنین خروجی JSON ای را بازگشت می‌دهد:
{
  "id": 12,
  "name": "DNT",
  "isDeleted": false,
  "prefs": {
     "comments": "members",
     "voting": "disabled"
  }
}
برای نوشتن آزمون‌های Postman مخصوص به آن، می‌توان به صورت زیر عمل کرد:
pm.test("Your test name", function () {
    var jsonData = pm.response.json();
    pm.expect(jsonData.name).to.eql("DNT");
    pm.expect(jsonData.prefs.voting).to.eql("disabled");
    pm.expect(jsonData.isDeleted).to.eql(false);
});
متد ()pm.response.json، بدنه‌ی response را تبدیل به یک شیء جاوا اسکریپتی می‌کند که پس از آن از طریق دسترسی به خواص و یا کلیدهای آن، می‌توان مقادیر هر یک را بررسی کرد. برای مثال jsonData.name به مقدار خاصیت name اشاره می‌کند و یا jsonData.prefs.voting، روش دسترسی به خاصیت یک شیء تو در تو را بیان کرده‌است.


ساماندهی بهتر آزمون‌های نوشته شده

البته می‌توان تمام Response assertions مدنظر را داخل یک callback function نیز قرار داد، اما بهتر است هر کدام را و یا گروهی از آن‌ها را که به هم مرتبط هستند، توسط یک pm.test جدید تعریف کرد تا بتوان به ساماندهی بهتر رسید و همچنین زمانیکه این آزمون‌ها بررسی می‌شوند، گزارش بهتری را نیز مشاهده نمود. به همین جهت برای نمونه می‌توان آزمایش فوق را به دو آزمایش مجزا تبدیل کرد که در یکی ایجاد مطلب جدید و در دیگری، ویژگی‌های آن مطلب بررسی شده‌اند:
pm.test("Post should be created", function () {
    var jsonData = pm.response.json();
    pm.expect(jsonData.name).to.eql("DNT");
    pm.expect(jsonData.isDeleted).to.eql(false);
});

pm.test("Post's voting feature should be disabled", function () {
    var jsonData = pm.response.json();
    pm.expect(jsonData.prefs.voting).to.eql("disabled");
});

ساده سازی و همچنین بهبود کارآیی آزمون‌های نوشته شده

چون در اینجا چندین‌بار از ()var jsonData = pm.response.json داخل هر آزمایش استفاده شده‌است و در عمل یک شیء response نیز بیشتر وجود ندارد، می‌توان جهت کاهش این تکرار و بهبود کارآیی آزمون‌های نوشته شده، آن‌را به صورت یک ثابت، به پیش از تمام آزمایش‌ها منتقل کرد:
const jsonData = pm.response.json();

pm.test("Post should be created", function () {
    pm.expect(jsonData.name).to.eql("DNT");
    pm.expect(jsonData.isDeleted).to.eql(false);
});

pm.test("Post's voting feature should be disabled", function () {
    pm.expect(jsonData.prefs.voting).to.eql("disabled");
});
  • #
    ‫۵ سال و ۴ ماه قبل، جمعه ۲۰ اردیبهشت ۱۳۹۸، ساعت ۱۴:۱۲
    یک نکته‌ی تکمیلی: روش Debug آزمون‌های postman

    اگر به status bar برنامه‌ی postman دقت کنید، یکی از آیکن‌های آن به کنسول آن اشاره می‌کند:


    در اینجا می‌توان توسط دستورات معروف console.log، تمام اشیاء و متغیرهای دریافتی از سرور را لاگ کرد و سپس مقدار آن‌ها را در کنسول postman بررسی نمود:
    const jsonData = pm.response.json();
    console.log(jsonData);
  • #
    ‫۵ سال و ۴ ماه قبل، جمعه ۲۰ اردیبهشت ۱۳۹۸، ساعت ۱۴:۱۴
    یک نکته‌ی تکمیلی: اجرای دستوراتی پیش از اجرای درخواست‌ها در postman

    همانطور که در مطلب جاری نیز عنوان شد، آزمون‌های Postman، پس از پایان اجرای یک درخواست و بر روی محتوای response دریافتی از سرور، اجرا می‌شوند. اگر نیاز به اجرای دستوراتی پیش از اجرای یک درخواست وجود داشته باشد، می‌توان از گزینه‌ی Pre-request script استفاده کرد:


    مهم‌ترین کاربرد آن، تنظیم یکسری متغیر، پیش از اجرای درخواست اصلی است. برای مثال فرض کنید که می‌خواهید id ارسالی به سمت سرور را random کنید. برای این منظور یک متغیر محیطی جدید را در قسمت Pre-request script درخواست جاری، تنظیم می‌کنیم (در مورد متغیرهای محیطی در قسمت بعد، مفصل بحث می‌شود):
    pm.environment.set("newId", "name " + parseInt(Math.random() * 10000));
    سپس برای استفاده‌ی از آن، اینبار url را به صورت زیر تنظیم می‌کنیم:
    https://localhost:5001/users/{{newId}}
    که در اینجا متغیر {{newId}} هربار پیش از اجرای درخواست، به یک مقدار اتفاقی تنظیم شده و سپس از آن برای ساخت URL نهایی استفاده می‌شود.
  • #
    ‫۵ سال و ۴ ماه قبل، جمعه ۲۰ اردیبهشت ۱۳۹۸، ساعت ۱۶:۵۴
    یک نکته‌ی تکمیلی: آشنایی با Chai Assertion Library

    Postman به صورت پیش‌فرض به همراه کتابخانه‌ی Chai Assertion است و روش fluent ای را که برای نوشتن آزمون‌های آن مشاهده می‌کنید، توسط همین کتابخانه ارائه می‌دهد و برای استفاده‌ی از آن، نیازی به تنظیمات خاصی نیست. مثال‌های بیشتری از این کتابخانه
    pm.test("Test Name", function(){
     let a= {
      "name" : "Vahid"
     };
    
     let b= {
      "name" : "Vahid"
     };
    
     pm.expect(a).to.eql(b);
    });
    همانطور که در این مثال نیز مشاهده می‌کنید، syntax کتابخانه‌ی Chai Assertion، شروع شده‌ی با یک .pm، در postman قابل تعریف و استفاده‌است.
  • #
    ‫۵ سال و ۴ ماه قبل، جمعه ۲۰ اردیبهشت ۱۳۹۸، ساعت ۱۷:۴۰
    یک نکته‌ی تکمیلی: پردازش سایر فرمت‌های دریافتی از سرور
    در این مطلب نحوه‌ی تبدیل response دریافتی را به json بررسی کردیم. سایر حالات زیر نیز توسط postman پشتیبانی می‌شوند:
    عملکرد
     متد
     تبدیل XML به شیء JSON
    let jsonObject = xml2Json(responseBody);
     تبدیل JSON به شیء JSON
    let jsonData = pm.response.json();
     تبدیل بدنه‌ی response به متن ساده
    let text = pm.response.text();
     تبدیل HTML بدنه‌ی response به یک شیء از نوع cheerio؛ مانند:
    let titleText = html.find('h1').text();
    let html = cheerio(responseBody);
  • #
    ‫۵ سال و ۴ ماه قبل، چهارشنبه ۲۵ اردیبهشت ۱۳۹۸، ساعت ۱۴:۰۳
    یک نکته‌ی تکمیلی: بهتر است برای چه مواردی در Postman آزمایش بنویسیم؟

    یک response از سه قسمت هدر، status code و بدنه‌ی آن تشکیل می‌شود. بنابراین هر سه قسمت را باید آزمایش کرد تا بتوان توسط آن عملکرد یک Web API را بررسی نمود.
    برای مثال فرض کنید که می‌خواهید برای متد Put، آزمایش بنویسید. این آزمایش باید شامل موارد زیر باشد:

    بررسی موفقیت آمیز بودن عملیات
    - آیا هدرهای درستی در response درج شده‌اند؟
    - آیا status code دریافتی از سرور برای مثال 200 یا 204 است؟
    - آیا عملیات به روز رسانی، موجودیت مشخص و مورد انتظاری را به روز رسانی کرده‌است یا خیر؟
    - آیا تمام فیلدها به درستی به روز رسانی شده‌اند؟
    - آیا فیلدهایی که در درخواست ارسالی قید نشده‌اند، با مقدار پیش‌فرض خود مقدار دهی شده‌اند؟

    بررسی شکست عملیات
    - آیا به روز رسانی موجودیتی که وجود ندارد، خروجی 404 را به همراه دارد یا خیر؟
    - آیا اگر هدر content-type ذکر نشود، شاهد status code=415 unsupported media type خواهیم بود؟
    - آیا اگر هدر content-type نامربوطی ذکر شود، شاهد status code=415 unsupported media type خواهیم بود؟
    - آیا اگر بدنه‌ی درخواست خالی را ارسال کنیم، خروجی 400 bad request صادر می‌شود؟
    - آیا اگر فیلدها را طوری تنظیم کنیم که سبب مشکلات اعتبارسنجی شوند، خروجی 422 unprocessable entity صادر می‌شود؟

    بنابراین در حالت کلی:
    - هر دو وضعیت موفقیت آمیز بودن و شکست عملیات باید بررسی شوند.
    - مشکلات اعتبارسنجی ورودی‌ها باید بررسی شوند.
    - ورودی‌های مورد انتظار را کم و زیاد کنید. اگر درخواستی قرار است کوئری استرینگی را بپذیرد، آن‌را قید نکنید یا برعکس.
  • #
    ‫۵ سال و ۴ ماه قبل، چهارشنبه ۲۵ اردیبهشت ۱۳۹۸، ساعت ۱۵:۲۸
    یک نکته‌ی تکمیلی: نمونه‌هایی از آزمایش‌های Postman


    بررسی status code دریافتی از سرور
    pm.test("Status code is 200", function () {
        pm.response.to.have.status(200);
    });
    
    pm.test("Status code is 200", function () {
        pm.expect(pm.response.code).to.equal(200);
    });
    
    pm.test("Request is successful", function () {
        pm.response.to.be.succes;
    }); // Status code is in the 2XX range
    
    pm.test("Request results in a client error", function () {
        pm.response.to.be.clientError;
    }); // Status code is in the 4XX range
    
    pm.test("Request results in a Not Found error", function () {
        pm.response.to.be.notFound;
    }); // 404
    
    pm.test("Status code is 200 or 204", function () {
        pm.expect([200, 204]).to.include(pm.response.code);
    });

    بررسی هدرهای دریافتی از سرور
    pm.test("Response has Content-Type header", function () {
        pm.response.to.have.header("Content-Type");
    });
    
    pm.test("Response has Content-Type header with application/json; charset = utf - 8 as value", function () {
        pm.response.to.have.header(
            'Content-Type',
            'application/json; charset=utf-8');
    });

    بررسی بدنه‌ی درخواست
    pm.test("Response has a non-empty body", function () {
        pm.expect(pm.response.text()).not.empty;
    });
    
    pm.test("Response has a non-empty body", function () {
        pm.expect(pm.response.json()).not.empty;
    });
    
    pm.test("Response has a non-empty body", function () {
        pm.response.to.have.body();
    });
    
    pm.test("Response has a non-empty body", function () {
        pm.response.to.have.jsonBody();
    });

    بررسی خواص اشیاء دریافتی از سرور
    var updatedAuthor = pm.response.json();
    pm.test("Author properties have been updated", function () {
        pm.expect(updatedAuthor.firstName).to.equal("Vahid");
        pm.expect(updatedAuthor.lastName).to.equal("N");
    });