این راهنمای قابلیت اطمینان JavaScript & Node.js از A-Z است. دهها مورد از بهترین پستها، کتابها و ابزارهایی که بازار ارائه میدهد را برای شما خلاصه و گردآوری میکند.
به سفری بروید که بسیار فراتر از اصول اولیه به موضوعات پیشرفته ای مانند آزمایش در تولید، آزمایش جهش، آزمایش مبتنی بر دارایی و بسیاری از ابزارهای استراتژیک و حرفه ای دیگر سفر می کند. اگر تمام کلمات این راهنما را بخوانید، احتمالاً مهارت های تست زنی شما بسیار بالاتر از حد متوسط خواهد بود
با درک روشهای تست همه جا حاضر که پایه و اساس هر سطح برنامه هستند، شروع کنید. سپس، به حوزه انتخابی خود بپردازید: frontend/UI، backend، CI یا شاید همه آنها؟
- JavaScript & Node.js مشاوره
- 📗 تست کردن Node.js & JavaScript از A تا Z - دوره جامع آنلاین من با بیش از 7 ساعت ویدیو, 14 انواع تست و بیش از 40 بهترین روش
- من را در توییتر دنبال کنید
- کارگاه بعدی: ورونا، ایتالیا 🇮🇹، 20 آوریل
یک توصیه واحد که الهام بخش بقیه است (1 گلوله ویژه)
فونداسیون - تست های تمیز ساختاری (12 گلوله)
نوشتن تست های backend و میکروسرویس به طور موثر (13 گلوله)
نوشتن تست ها برای رابط کاربری وب شامل تست های کامپوننت و E2E (11 گلوله)
تماشای نگهبان - اندازه گیری کیفیت تست (4 گلوله)
دستورالعمل های CI در دنیای JS (9 گلوله)
✅ انجام دادن:
تست کردن کد صرفا نوشتن کد نیست - آن را طوری طراحی کنید که کوتاه، ساده، مسطح، و کار کردن با آن لذت بخش باشد. باید به یک تست نگاه کرد و فوراً هدف را دریافت کرد.
ببینید، ذهن ما از قبل مشغول کار اصلی ما - نوشتن کد است. برای پیچیدگی اضافی، هیچ فضایی وجود ندارد. اگر بخواهیم یک سیستم sus-system دیگر را در مغز ضعیف خود بفشاریم، سرعت تیم را کاهش میدهد که برخلاف دلیلی که ما تست میکنیم عمل میکند. عملاً اینجاست که بسیاری از تیمها تست را رها میکنند.
تست ها فرصتی برای چیز دیگری هستند - یک دستیار دوستانه، کمک خلبان، که ارزش زیادی برای یک سرمایه گذاری کوچک ارائه می دهد. علم به ما می گوید که ما دو سیستم مغزی داریم: سیستم 1 برای فعالیت های بی دردسر مانند رانندگی ماشین در یک جاده خالی و سیستم 2 که برای عملیات پیچیده و آگاهانه مانند حل یک معادله ریاضی استفاده می شود. تست خود را برای سیستم 1 طراحی کنید، زمانی که به کد تست نگاه می کنید، باید به راحتی یک سند HTML را تغییر دهید، نه مانند حل 2X (17 × 24).
این را می توان با تکنیک های انتخابی انتخاب گیلاس، ابزارها و اهداف آزمایشی که مقرون به صرفه هستند و ROI عالی ارائه می دهند، به دست آورد. فقط به اندازه نیاز تست کنید، سعی کنید آن را زیرک نگه دارید، گاهی اوقات حتی ارزش آن را دارد که برخی از تست ها را کنار بگذارید و قابلیت اطمینان را برای چابکی و سادگی معامله کنید.
بیشتر توصیه های زیر مشتقات این اصل است.
✅ انجام دادن: یک گزارش آزمایشی باید بگوید که آیا بازبینی برنامه فعلی الزامات افرادی را که لزوماً با کد آشنا نیستند برآورده می کند: تست کننده ، مهندس DevOps که در حال استقرار است و شما آینده دو سال بعد. اگر آزمون ها در سطح الزامات صحبت کنند و شامل 3 بخش باشند، می توان به بهترین وجه به این امر دست یافت:
(1) چه چیزی در حال آزمایش است؟ به عنوان مثال، متد ProductsService.addNewProduct
(2) در چه شرایط و سناریویی؟ برای مثال هیچ قیمتی به متد داده نمی شود
(3) نتیجه ی قابل انتظار چیست؟ به عنوان مثال، محصول جدید تایید نشده است
❌ در غیر این صورت: یک استقرار بهتازگی ناموفق بود، تست با نام «افزودن محصول» ناموفق بود. آیا این به شما می گوید که دقیقاً چه چیزی خراب است؟
👇 توجه داشته باشید: هر گلوله دارای نمونه کد و گاهی اوقات یک تصویر تصویری نیز می باشد. برای گسترش کلیک کنید
✏ نمونه کد
//1. واحد در حال تست
describe('خدمات محصولات', function() {
describe('افزودن محصول جدید', function() {
//2. سناریو و 3. انتظار
it('هنگامی که قیمتی مشخص نشده است، وضعیت محصول در انتظار تایید است', ()=> {
const newProduct = new ProductService().add(...);
expect(newProduct.status).to.equal('pendingApproval');
});
});
});
✅ انجام دادن: ساختار تست های خود را با 3 بخش به خوبی جدا شده مفدار دهی کنید، اجرا کنید و مقایسه کنید (AAA). پیروی از این ساختار تضمین می کند که خواننده هیچ CPU مغزی را برای درک برنامه آزمایشی خرج نمی کند:
اول A - مفدار دادن: تمام کدهای راه اندازی برای رساندن سیستم به سناریویی که تست هدف آن شبیه سازی است. این ممکن است شامل نمونه سازی واحد در حال آزمایش سازنده، اضافه کردن رکوردهای DB، mocking/stubbing اشیا و هر کد آماده سازی دیگر باشد.
دوم A - اجرا: واحد تحت تست را اجرا کنید. معمولا 1 خط کد
سوم A - مفایسه: اطمینان حاصل کنید که ارزش دریافتی انتظارات را برآورده می کند. معمولا 1 خط کد
❌ در غیر این صورت: نه تنها ساعت ها برای درک کد اصلی وقت می گذارید، بلکه چیزی که باید ساده ترین قسمت روز باشد (تست) مغز شما را کش می دهد.
✏ نمونه کد
describe("طبقه بندی مشتری", () => {
test("هنگامی که مشتری بیش از 500 دلار هزینه کرد، باید به عنوان حق بیمه طبقه بندی شود", () => {
//مفدار دهی کردن
const customerToClassify = { spent: 505, joined: new Date(), id: 1 };
const DBStub = sinon.stub(dataAccess, "getCustomer").reply({ id: 1, classification: "regular" });
//اجرا کردن
const receivedClassification = customerClassifier.classifyCustomer(customerToClassify);
//مفابسه کردن
expect(receivedClassification).toMatch("premium");
});
});
test("باید به عنوان حق بیمه طبقه بندی شود", () => {
const customerToClassify = { spent: 505, joined: new Date(), id: 1 };
const DBStub = sinon.stub(dataAccess, "getCustomer").reply({ id: 1, classification: "regular" });
const receivedClassification = customerClassifier.classifyCustomer(customerToClassify);
expect(receivedClassification).toMatch("premium");
});
✅ انجام دادن: کدنویسی تستهای خود به سبک بیانی به خواننده این امکان را میدهد تا بدون صرف حتی یک چرخه مغز - CPU، فوراً به نتیجه برسد. وقتی کدهای ضروری را می نویسید که مملو از منطق شرطی است، خواننده مجبور می شود چرخه های مغز - CPU بیشتری اعمال کند. در این صورت، انتظارات را به زبانی شبیه به انسان، به سبک BDD اعلانی با استفاده از «انتظار» یا «باید» و بدون استفاده از کد سفارشی کدنویسی کنید. اگر Chai & Jest شامل ادعای مورد نظر نیست و بسیار قابل تکرار است، در نظر بگیرید extending Jest matcher (Jest) یا نوشتن یک پلاگین Chai سفارشی
❌ در غیر این صورت: تیم تست های کمتری می نویسد و تست های مزاحم را با .skip تزئین می کند()
✏ نمونه کد
👎 مثال ضد الگو: خواننده فقط برای دریافت داستان تستی که باید کدهای نه چندان کوتاه و ضروری را مرور کند.
test("هنگام درخواست ادمین، مطمئن شوید که در نتایج فقط مدیران سفارش داده شده هستند", () => {
//با فرض اینکه ما دو ادمین "admin1"، "admin2" و "user1" را به اینجا اضافه کرده ایم.
const allAdmins = getUsers({ adminOnly: true });
let admin1Found,
adming2Found = false;
allAdmins.forEach(aSingleUser => {
if (aSingleUser === "user1") {
assert.notEqual(aSingleUser, "user1", "A user was found and not admin");
}
if (aSingleUser === "admin1") {
admin1Found = true;
}
if (aSingleUser === "admin2") {
admin2Found = true;
}
});
if (!admin1Found || !admin2Found) {
throw new Error("Not all admins were returned");
}
});
it("When asking for an admin, ensure only ordered admins in results", () => {
//با فرض اینکه ما به اینجا دو ادمین اضافه کرده ایم
const allAdmins = getUsers({ adminOnly: true });
expect(allAdmins)
.to.include.ordered.members(["admin1", "admin2"])
.but.not.include.ordered.members(["user1"]);
});
✅ اتجام دادن: آزمایش قطعات داخلی تقریباً هیچ هزینه زیادی را به همراه ندارد. اگر کد/API شما نتایج درستی را ارائه میدهد، آیا واقعاً باید 3 ساعت آینده خود را برای آزمایش نحوه عملکرد داخلی آن سرمایهگذاری کنید و سپس این تستهای شکننده را حفظ کنید؟ هر زمان که یک رفتار عمومی بررسی میشود، پیادهسازی خصوصی نیز به طور ضمنی آزمایش میشود و آزمایشهای شما تنها در صورت وجود مشکل خاصی (به عنوان مثال خروجی اشتباه) شکسته میشوند. این رویکرد به عنوان "آزمایش رفتار" نیز نامیده می شود. از طرف دیگر، اگر قطعات داخلی را آزمایش کنید (رویکرد جعبه سفید) - تمرکز شما از برنامه ریزی نتیجه کامپوننت به جزئیات دقیق تغییر می کند و ممکن است تست شما به دلیل اصلاح کننده های جزئی کد شکسته شود، اگرچه نتایج خوب هستند - این به طور چشمگیری تعمیر و نگهداری را افزایش می دهد. بار
❌ در غیر این صورت: تست های شما مانند پسری هست که عین گرگ فریاد می زند: فریاد زدن با فریادهای مثبت کاذب (مثلاً، یک آزمایش به دلیل تغییر نام یک متغیر خصوصی ناموفق بود). جای تعجب نیست که مردم به زودی اعلانهای CI را نادیده میگیرند تا اینکه روزی یک باگ واقعی نادیده گرفته شود…
✏ نمونه کد
class ProductService {
//این متذ فقط در داخل استفاده می شود
//تغییر این نام باعث می شود که آزمون ها با شکست مواجه شوند
calculateVATAdd(priceWithoutVAT) {
return { finalPrice: priceWithoutVAT * 1.2 };
//تغییر فرمت نتیجه یا نام کلید بالا باعث شکست تست ها می شود
}
//متد عمومی
getPrice(productId) {
const desiredProduct = DB.getProduct(productId);
finalPrice = this.calculateVATAdd(desiredProduct.price).finalPrice;
return finalPrice;
}
}
it("تست جعبه سفید: هنگامی که روش های داخلی 0 vat دریافت می کنند، 0 پاسخ می دهد", async () => {
//هیچ الزامی برای اجازه دادن به کاربران برای محاسبه مالیات بر ارزش افزوده وجود ندارد، فقط قیمت نهایی را نشان می دهد. با این وجود، ما به دروغ اصرار داریم که درونی کلاس را آزمایش کنیم
expect(new ProductService().calculateVATAdd(0).finalPrice).to.equal(0);
});
✅ انجام دادن: تست های دوبل یک شر ضروری هستند زیرا با اجزای داخلی برنامه همراه هستند، اما برخی از آنها ارزش بسیار زیادی را ارائه می دهند (در اینجا یک یادآوری در مورد تست دوبل بخوانید: mocks vs stubs vs spies).
قبل از استفاده از تست دوبل، یک سوال بسیار ساده بپرسید: آیا از آن برای تست عملکردی که در سند الزامات ظاهر می شود یا می تواند ظاهر شود استفاده کنم؟ اگر نه، بوی تست جعبه سفید است.
به عنوان مثال، اگر میخواهید آزمایش کنید که برنامهتان در هنگام قطع سرویس پرداخت، رفتار معقولی دارد، ممکن است سرویس پرداخت را خاموش کنید و مقداری «بدون پاسخ» را فعال کنید تا مطمئن شوید که واحد مورد آزمایش مقدار مناسب را برمیگرداند. این رفتار / پاسخ / نتیجه برنامه ما را تحت سناریوهای خاصی بررسی می کند. همچنین ممکن است از جاسوسی استفاده کنید تا ادعا کنید که ایمیلی زمانی ارسال شده است که آن سرویس از کار افتاده است———این دوباره یک بررسی رفتاری است که احتمالاً در سند الزامات ظاهر میشود («اگر پرداخت امکان ذخیره نشد» رایانامه ارسال کنید. از طرف دیگر، اگر سرویس پرداخت را مسخره میکنید و مطمئن میشوید که با انواع جاوا اسکریپت مناسب فراخوانی شده است — آزمایش شما روی چیزهای داخلی متمرکز است که هیچ ربطی به عملکرد برنامه ندارند و احتمالاً اغلب تغییر میکنند.
❌ در غیر این صورت: هر گونه بازسازي كد، جست و جوي تمامي ساختگي هاي موجود در كد و به روز كردن آن را الزامي مي كند. تست ها به جای یک دوست مفید تبدیل به یک بار می شوند
✏ نمونه کد
it("هنگامی که یک محصول معتبر در شرف حذف است، مطمئن شوید که DAL یک بار با محصول مناسب و پیکربندی مناسب تماس گرفته شده است.", async () => {
//فرض کنید قبلاً یک محصول اضافه کرده ایم
const dataAccessMock = sinon.mock(DAL);
//هومممم بد است: تست قطعات داخلی در واقع هدف اصلی ما در اینجا است، نه فقط یک side effect
dataAccessMock
.expects("deleteProduct")
.once()
.withArgs(DBConfig, theProductWeJustAdded, true, false);
new ProductService().deletePrice(theProductWeJustAdded);
dataAccessMock.verify();
});
👏مثال درست: جاسوسان بر روی تست الزامات متمرکز هستند، اما به عنوان یک side effect، به طور اجتناب ناپذیری به درونی ها دست می زنند.
it("هنگامی که یک محصول معتبر در شرف حذف است، مطمئن شوید که یک ایمیل ارسال شده است", async () => {
//فرض کنید قبلاً یک محصول را در اینجا اضافه کرده ایم
const spy = sinon.spy(Emailer.prototype, "sendEmail");
new ProductService().deletePrice(theProductWeJustAdded);
//هومممم خوب است: ما با داخلی سروکار داریم؟ بله، اما به عنوان یک side effect تست الزامات (ارسال ایمیل)
expect(spy.calledOnce).to.be.true;
});
###از دوره آنلاین من دیدن کنید Testing Node.js & JavaScript از A تا Z
✅ انجام دادن: اغلب اشکالات تولید تحت برخی ورودیهای بسیار خاص و شگفتانگیز آشکار میشوند— هرچه ورودی آزمایش واقعیتر باشد، شانس تشخیص زودهنگام باگها بیشتر میشود. از کتابخانههای اختصاصی مانند Chance یا Faker برای تولید دادههای شبه واقعی که شبیه انواع و اقسام آنها هستند، استفاده کنید. شکل داده های تولید به عنوان مثال، چنین کتابخانههایی میتوانند شماره تلفن، نام کاربری، کارت اعتباری، نام شرکت و حتی متن «lorem ipsum» واقعی را تولید کنند. همچنین میتوانید آزمایشهایی (در بالای آزمایشهای واحد، نه به عنوان جایگزین) ایجاد کنید که دادههای جعلی را تصادفی میکند تا واحد شما تحت آزمایش کشیده شود یا حتی دادههای واقعی را از محیط تولید شما وارد کند. می خواهید آن را به سطح بعدی ببرید؟ گلوله بعدی (تست مبتنی بر ویژگی) را ببینید.
❌ در غیر این صورت: وقتی از ورودی های مصنوعی مانند "Foo" استفاده می کنید، تمام آزمایش های توسعه شما به اشتباه سبز نشان داده می شوند، اما زمانی که هکر رشته بدی مانند "@3e2ddsf" را عبور می دهد، ممکن است تولید قرمز شود. ##' 1 fdsfds . fds432 AAAA”
✏ نمونه کد
const addProduct = (name, price) => {
const productNameRegexNoSpace = /^\S*$/; //space مجاز نیست
if (!productNameRegexNoSpace.test(name)) return false; //این شرط به دلیل ورودی نادرست هرگز اجرا نمی شود
//بقیه کد ها
return true;
};
test("اشتباه: هنگام افزودن محصول جدید با ویژگی های معتبر، تأیید موفقیت آمیز دریافت کنید", async () => {
//رشته "Foo" که در همه تست ها استفاده می شود، هرگز نتیجه نادرستی را ایجاد نمی کند
const addProductResult = addProduct("Foo", 5);
expect(addProductResult).toBe(true);
//مثبت-کاذب: عملیات موفقیت آمیز بود زیرا ما هرگز تلاش زیادی نکردیم
//نام محصول شامل فاصله ها
});
it("بهتر: هنگام افزودن محصول معتبر جدید، تأیید موفقیت آمیز دریافت کنید", async () => {
const addProductResult = addProduct(faker.commerce.productName(), faker.random.number());
//ورودی تصادفی ایجاد شده: {'Sleek Cotton Computer', 85481}
expect(addProductResult).to.be.true;
//تست ناموفق بود، ورودی تصادفی مسیری را آغاز کرد که ما هرگز برای آن برنامه ریزی نکرده بودیم.
//ما یک باگ را زود کشف کردیم!
});
✅ انجام دادن: معمولاً برای هر آزمون چند نمونه ورودی انتخاب می کنیم. حتی زمانی که قالب ورودی شبیه داده های دنیای واقعی باشد (به گلوله مراجعه کنید ‘گول نزن’), ما فقط چند ترکیب ورودی را پوشش میدهیم (method(''، true، 1)، روش ("string"، false، 0))، با این حال، در تولید، یک API که با 5 پارامتر فراخوانی می شود را می توان با هزاران پارامتر مختلف فراخوانی کرد. جایگشت، یکی از آنها ممکن است فرآیند ما را پایین بیاورد (تست فاز را ببینید). اگر بتوانید یک تست بنویسید که 1000 جایگشت از ورودی های مختلف را به طور خودکار ارسال می کند و کد ما برای کدام ورودی پاسخ مناسب را نمی دهد؟ تست مبتنی بر ویژگی تکنیکی است که دقیقاً همین کار را انجام می دهد: با ارسال تمام ترکیبات ورودی ممکن به واحد تحت آزمایش شما، مشکل یافتن یک باگ را افزایش می دهد. به عنوان مثال، با توجه به یک متد — addNewProduct(id, name, isDiscount)—— کتابخانه های پشتیبانی کننده این متد را با ترکیب های زیادی از (عدد، رشته، بولی) مانند (1، "iPhone"، false)، (2، "Galaxy" فراخوانی می کنند. "، درست است، واقعی). شما می توانید تست مبتنی بر ویژگی را با استفاده از برنامه آزمایشی مورد علاقه خود (موکا، جست و غیره) با استفاده از کتابخانه هایی مانند js-verify یا بررسی تست (مستندات خیلی بهتر). به روز رسانی: Nicolas Dubien در نظرات زیر بهتسویه حساب سریع که به نظر می رسد برخی از ویژگی های اضافی را ارائه می دهد و همچنین به طور فعال حفظ می شود
❌ در غیر این صورت: ناخودآگاه، ورودیهای تست را انتخاب میکنید که فقط مسیرهای کدی را پوشش میدهند که به خوبی کار میکنند. متأسفانه، این کارایی تست را به عنوان وسیله ای برای افشای اشکالات کاهش می دهد
✏ نمونه کد
import fc from "fast-check";
describe("خدمات محصول", () => {
describe("افزودن جدید", () => {
//این 100 بار با ویژگی های تصادفی مختلف اجرا می شود
it("محصول جدید با ویژگی های تصادفی و در عین حال معتبر، همیشه موفق اضافه کنید", () =>
fc.assert(
fc.property(fc.integer(), fc.string(), (id, name) => {
expect(addNewProduct(id, name).status).toEqual("approved");
})
));
});
});
✅ انجام دادن: زمانی که نیاز باشد snapshot تست کردن, فقط از عکس های فوری کوتاه و متمرکز استفاده کنید (i.e. 3-7 خط) که به عنوان بخشی از آزمون گنجانده شده است (Inline Snapshot) و نه در فایل های خارجی. رعایت این دستورالعمل تضمین میکند که تستهای شما قابل توضیح و کمتر شکننده هستند.
از سوی دیگر، آموزشها و ابزارهای «snapshot کلاسیک»، ذخیره فایلهای بزرگ (مانند نشانهگذاری رندر مؤلفه، نتیجه API JSON) را بر روی برخی رسانههای خارجی تشویق میکنند و اطمینان حاصل میکنند که هر بار هنگام اجرای تست، نتیجه دریافتشده با نسخه ذخیرهشده مقایسه شود. برای مثال، این می تواند به طور ضمنی آزمون ما را به 1000 خط با 3000 مقدار داده که نویسنده آزمون هرگز نخوانده و درباره آن استدلال نکرده است، مرتبط کند. چرا این اشتباه است؟ با انجام این کار، 1000 دلیل برای شکست تست شما وجود دارد - کافی است یک خط تغییر کند تا snapshot نامعتبر شود و این احتمالاً زیاد اتفاق می افتد. چند بار؟ برای هر فضا، نظر یا تغییر جزئی CSS/HTML. نه تنها این، نام آزمون سرنخی در مورد شکست نمی دهد، زیرا فقط بررسی می کند که 1000 خط تغییر نکرده است، همچنین نویسنده آزمون را تشویق می کند که سند طولانی را که نمی تواند بررسی کند، به عنوان واقعی مورد نظر بپذیرد. تایید کنید. همه اینها علائم آزمون مبهم و مشتاق است که متمرکز نیست و هدف آن دستیابی به بیش از حد است
شایان ذکر است که موارد کمی وجود دارد که snapshotهای طولانی و خارجی قابل قبول باشد - هنگام ادعا بر روی طرح و نه داده (استخراج مقادیر و تمرکز بر روی فیلدها) یا زمانی که سند دریافتی به ندرت تغییر میکند.
❌ در غیر این صورت: تست رابط کاربری ناموفق است. به نظر می رسد کد درست است، صفحه نمایش پیکسل های عالی را ارائه می دهد، چه اتفاقی افتاده است؟ تست snapshot شما تفاوتی بین سند مبدأ با سند دریافتی فعلی پیدا کرد - یک کاراکتر فاصله به علامت گذاری اضافه شد...
✏ نمونه کد
it("TestJavaScript.com به درستی ارائه شده است", () => {
//مفدار دهی کردن
//اجرا کردن
const receivedPage = renderer
.create(<DisplayPage page="http://www.testjavascript.com"> Test JavaScript </DisplayPage>)
.toJSON();
//مقایسه کردن
expect(receivedPage).toMatchSnapshot();
//ما اکنون به طور ضمنی یک سند 2000 خطی را حفظ می کنیم
//هر شکست خط یا comment اضافی - این تست را شکسته خواهد کرد
});
it("هنگام بازدید از صفحه اصلی TestJavaScript.com، یک منو نمایش داده می شود", () => {
//مفدار دهی کردن
//اجرا کردن
const receivedPage = renderer
.create(<DisplayPage page="http://www.testjavascript.com"> Test JavaScript </DisplayPage>)
.toJSON();
//مفایسه کردن
const menu = receivedPage.content.menu;
expect(menu).toMatchInlineSnapshot(`
<ul>
<li>Home</li>
<li> About </li>
<li> Contact </li>
</ul>
`);
});
✅ انجام دادن: تمام جزئیات لازم را که بر نتیجه آزمایش تأثیر می گذارد، وارد کنید، اما نه بیشتر. به عنوان مثال، تستی را در نظر بگیرید که باید 100 خط ورودی JSON را در نظر بگیرید - چسباندن آن در هر تست خسته کننده است. استخراج آن از خارج به transferFactory.getJSON() باعث میشود تست مبهم باقی بماند - بدون داده، ارتباط نتیجه آزمایش با علت دشوار است ("چرا قرار است وضعیت 400 را برگرداند؟"). کتاب کلاسیک الگوهای واحد x نام این الگو را «مهمان اسرارآمیز» گذاشتند - چیزی نادیده بر نتایج آزمایش ما تأثیر گذاشت، ما دقیقاً نمیدانیم چه چیزی. ما میتوانیم با استخراج قطعات طولانی قابل تکرار در خارج و به صراحت اشاره کنیم که کدام جزئیات خاص برای آزمایش مهم هستند، بهتر عمل کنیم. با استفاده از مثال بالا، آزمون میتواند پارامترهایی را پاس کند که آنچه مهم است را برجسته میکند: transferFactory.getJSON({sender: undefined}). در این مثال، خواننده باید فوراً استنباط کند که قسمت خالی فرستنده دلیلی است که آزمون باید منتظر خطای اعتبارسنجی یا هر نتیجه کافی مشابه دیگری باشد.
❌ در غیر این صورت: کپی کردن 500 خط JSON باعث میشود که تستهای شما قابل نگهداری و خواندن نباشند. جابجایی همه چیز به بیرون با آزمون های مبهمی که درک آنها سخت است به پایان می رسد
✏ نمونه کد
test("وقتی اعتباری وجود ندارد، انتقال رد می شود", async() => {
// مفدار دهی کردن
const transferRequest = testHelpers.factorMoneyTransfer() //200 خط JSON را برگردانید.
const transferServiceUnderTest = new TransferService();
// اجرا کردن
const transferResponse = await transferServiceUnderTest.transfer(transferRequest);
// مفایسه کردن
expect(transferResponse.status).toBe(409);// اما چرا ما انتظار شکست را داریم: به نظر می رسد همه در آزمون کاملاً معتبر هستند 🤔
});
test("وقتی اعتباری وجود ندارد، انتقال رد می شود", async() => {
// مفدار دهی کردن
const transferRequest = testHelpers.factorMoneyTransfer({userCredit:100, transferAmount:200}) //بدیهی است که کمبود اعتبار وجود دارد
const transferServiceUnderTest = new TransferService({disallowOvercharge:true});
// اجرا کردن
const transferResponse = await transferServiceUnderTest.transfer(transferRequest);
// مقایسه کردن
expect(transferResponse.status).toBe(409); //بدیهی است که اگر کاربر اعتباری نداشته باشد، باید شکست بخورد
});
✅ انجام دادن: هنگامی که میخواهید ادعا کنید که برخی از ورودیها باعث ایجاد خطا میشوند، ممکن است استفاده از try-catch-finally درست به نظر برسد و ادعا کند که بند catch وارد شده است. نتیجه یک مورد تست نامناسب و مفصل است (مثال زیر) که هدف آزمون ساده و انتظارات نتیجه را پنهان می کند.
یک جایگزین زیباتر استفاده از ادعای اختصاصی Chai یک خطی است: expect(method).to.throw (یا در Jest: expect(method).toThrow()). کاملاً اجباری است که اطمینان حاصل شود که استثنا دارای خاصیتی است که نوع خطا را بیان می کند، در غیر این صورت با توجه به یک خطای عمومی، برنامه نمی تواند به جای نمایش یک پیام ناامیدکننده به کاربر، کار زیادی انجام دهد.
❌ در غیر این صورت: استنتاج از گزارشهای آزمایشی (مثلاً گزارشهای CI) چالش برانگیز خواهد بود
✏ نمونه کد
it("هنگامی که نام محصول وجود ندارد، خطای 400 را نشان می دهد", async () => {
let errorWeExceptFor = null;
try {
const result = await addNewProduct({});
} catch (error) {
expect(error.code).to.equal("InvalidInput");
errorWeExceptFor = error;
}
expect(errorWeExceptFor).not.to.be.null;
//اگر این مقایسه ناموفق باشد، نتایج/گزارشهای تست فقط نشان داده میشوند
//که مقداری تهی است، کلمه ای در مورد یک استثنا وجود نخواهد داشت
});
👏 مثال درست: انتظاری قابل خواندن برای انسان که به راحتی قابل درک است، حتی ممکن است توسط QA یا PM فنی
it("هنگامی که نام محصول وجود ندارد، خطای 400 را نشان می دهد", async () => {
await expect(addNewProduct({}))
.to.eventually.throw(AppError)
.with.property("code", "InvalidInput");
});
✅ انجام دادن: تست های مختلف باید در سناریوهای مختلف اجرا شوند: دود سریع، بدون IO، تست ها باید زمانی که یک توسعهدهنده فایلی را ذخیره یا متعهد میکند، اجرا میشوند، تست های کامل پایان به انتها معمولاً هنگام ارسال درخواست کشش جدید اجرا میشوند، و غیره. با برچسب گذاری تست ها با کلمات کلیدی مانند #cold #api #sanity تا بتوانید با مهار تست خود دست بگیرید و زیر مجموعه مورد نظر را فراخوانی کنید. برای مثال، به این صورت است که فقط گروه تست سلامت عقل را با موکا فرا میخوانید: mocha — grep ‘sanity’
❌ در غیر این صورت: اجرای تمام تستها، از جمله تستهایی که دهها پرسوجو در DB را انجام میدهند، هر زمانی که یک توسعهدهنده یک تغییر کوچک ایجاد کند میتواند بسیار کند باشد و توسعهدهندگان را از اجرای تست ها دور نگه دارد.
✏ نمونه کد
👏 مثال درست: برچسب گذاری تست ها به عنوان "#cold-test" به اجراکننده آزمون اجازه می دهد فقط تست های سریع را اجرا کند (تست های سرد===سریع که هیچ IO انجام نمی دهند و حتی زمانی که توسعه دهنده در حال تایپ کردن است می توانند مکررا اجرا شوند)
//این تست سریع است (بدون DB) و ما آن را مطابق با آن برچسب گذاری می کنیم
//اکنون کاربر/CI می تواند آن را به طور مکرر اجرا کند
describe("سفارش سرویس", function() {
describe("اضافه کردن سفارش جدید #تست سرد #عقل", function() {
test("سناریو - هیچ ارزی عرضه نشد. انتظار - از ارز پیش فرض #عقل استفاده کنید", function() {
//بقیه کدها
});
});
});
✅ انجام دادن: ساختاری را در مجموعه آزمایشی خود اعمال کنید تا یک بازدیدکننده گاه به گاه بتواند به راحتی الزامات (آزمون ها بهترین مستندات هستند) و سناریوهای مختلفی که در حال آزمایش هستند را درک کند. یک روش متداول برای این کار قرار دادن حداقل 2 بلوک «توضیح» در بالای تستهایتان است: اولی برای نام واحد تحت آزمایش و دومی برای سطح اضافی طبقهبندی مانند سناریو یا دستههای سفارشی است (نمونههای کد را ببینید و چاپ کنید. صفحه زیر). انجام این کار گزارشهای آزمون را نیز بسیار بهبود میبخشد: خواننده به راحتی دستههای تستها را استنباط میکند، در بخش مورد نظر کاوش میکند و تستهای مردودی را به هم مرتبط میکند. علاوه بر این، پیمایش کد یک مجموعه با آزمایش های زیاد برای یک توسعه دهنده بسیار آسان تر خواهد شد. چندین ساختار جایگزین برای مجموعه آزمایشی وجود دارد که میتوانید آنها را مانند آنها در نظر بگیرید داده شده-وقتی-پس و RITE
❌ در غیر این صورت: وقتی به گزارشی با فهرستی مسطح و طولانی از آزمونها نگاه میکنیم، خواننده باید متنهای طولانی را به طور کامل بخواند تا سناریوهای اصلی را نتیجهگیری کند و مشترکات رد شدن در آزمونها را به هم مرتبط کند. مورد زیر را در نظر بگیرید: هنگامی که 7/100 تست شکست می خورند، نگاه کردن به یک لیست ثابت نیاز به خواندن متن تست های مردود دارد تا ببینید چگونه آنها با یکدیگر ارتباط دارند. با این حال، در یک گزارش سلسله مراتبی، همه آنها می توانند تحت یک جریان یا دسته باشند و خواننده به سرعت استنباط می کند که علت اصلی شکست چیست یا حداقل کجاست.
✏ نمونه کد
👏 مثال درست: مجموعه ساختاری با نام واحد تحت آزمایش و سناریوها به گزارش مناسبی منجر می شود که در زیر نشان داده شده است.
// واحد در حال تست
describe("سرویس انتقال", () => {
//سناریو
describe("زمانی که اعتباری وجود ندارد", () => {
//انتظار
test("سپس وضعیت پاسخ باید کاهش یابد", () => {});
//انتظار
test("سپس باید برای مدیر ایمیل ارسال شود", () => {});
});
});
👎 مثال ضد الگو: یک لیست مسطح از تست ها شناسایی داستان های کاربر و ارتباط بین تست های شکست خورده را برای خواننده سخت تر می کند.
test("سپس وضعیت پاسخ باید کاهش یابد", () => {});
test("سپس باید ایمیل بفرستد", () => {});
test("در این صورت نباید سابقه نقل و انتقالات جدیدی وجود داشته باشد", () => {});
✅ انجام دادن: این پست روی توصیههای آزمایشی متمرکز شده است، یا حداقل میتوان آن را با Node JS مثال زد. با این حال، این گلوله، چند نکته غیر مرتبط با Node را که به خوبی شناخته شده اند، گروه بندی می کند
یاد بگیرید و تمرین کنید TDD principles آنها برای بسیاری بسیار ارزشمند هستند، اما اگر با سبک شما سازگاری ندارند، نترسید، شما تنها نیستید. تست ها را قبل از کد در بنویسید red-green-refactor style, اطمینان حاصل کنید که هر تست دقیقاً یک چیز را بررسی می کند، وقتی یک باگ پیدا کردید — قبل از رفع یک تست بنویسید که این اشکال را در آینده شناسایی کند، اجازه دهید هر تست حداقل یک بار قبل از سبز شدن شکست بخورد، با نوشتن یک کد سریع و ساده، یک ماژول را شروع کنید. آزمایش را برآورده می کند - سپس به تدریج اصلاح کنید و آن را به سطح درجه تولید ببرید، از هر گونه وابستگی به محیط (مسیرها، سیستم عامل و غیره) اجتناب کنید.
❌ در غیر این صورت: دلت برای مرواریدهای خردی که برای چندین دهه جمع آوری شده اند را از دست خواهید داد
✅ انجام دادن: تست کردن هرم, اگرچه 10 سال سن دارد، اما یک مدل عالی و مرتبط است که سه نوع تست را پیشنهاد می کند و بر استراتژی تست اکثر توسعه دهندگان تأثیر می گذارد. در همان زمان، بیش از تعداد انگشت شماری از تکنیکهای آزمایشی جدید براق ظاهر شدند و در سایههای هرم آزمایش پنهان شدند. با توجه به تمام تغییرات چشمگیری که در 10 سال اخیر دیده ایم (سرویس های میکرو، ابر، بدون سرور)، آیا حتی ممکن است یک مدل کاملاً قدیمی برای همه انواع برنامه ها مناسب باشد؟ آیا دنیای تست نباید از تکنیک های تست جدید استقبال کند؟
اشتباه نکنید، در سال 2019 تست هرم، تست TDD و واحد هنوز یک تکنیک قدرتمند هستند و احتمالاً بهترین تطابق برای بسیاری از برنامه ها هستند. فقط مانند هر مدل دیگری با وجود مفید بودن, گاهی اوقات باید اشتباه باشد. به عنوان مثال، یک برنامه IoT را در نظر بگیرید که بسیاری از رویدادها را در یک گذرگاه پیام مانند Kafka/RabbitMQ وارد میکند، که سپس به انبار داده سرازیر میشود و در نهایت توسط برخی از رابطهای تحلیلی پرس و جو میشود. آیا واقعاً باید 50 درصد از بودجه تست خود را صرف نوشتن آزمون های واحد برای برنامه ای کنیم که یکپارچه محور است و تقریباً هیچ منطقی ندارد؟ با افزایش تنوع انواع برنامه ها (ربات ها، کریپتو، مهارت های الکسا) شانس بیشتری برای یافتن سناریوهایی وجود دارد که در آن هرم آزمایشی بهترین تطابق نیست.
وقت آن رسیده است که مجموعه آزمایشی خود را غنی کنید و با انواع تست های بیشتری آشنا شوید (گلوله های بعدی ایده های کمی را پیشنهاد می کنند)، مدل های ذهنی مانند هرم آزمایش، اما همچنین انواع آزمایش را با مشکلات دنیای واقعی که با آن مواجه هستید مطابقت دهید ('Hey, API ما خراب است، بیایید تست قرارداد مبتنی بر مصرفکننده بنویسیم!»)، آزمایشهای خود را مانند سرمایهگذاری که سبد سهامی را بر اساس تجزیه و تحلیل ریسک میسازد، متنوع کنید— ارزیابی کنید که در کجا ممکن است مشکلات ایجاد شوند و با برخی از اقدامات پیشگیرانه برای کاهش خطرات احتمالی مطابقت دهید.
یک کلمه احتیاط: استدلال TDD در دنیای نرم افزار یک چهره دوگانگی کاذب معمولی دارد، برخی موعظه می کنند که از آن در همه جا استفاده کنند، برخی دیگر فکر می کنند که این شیطان است. هر کس به طور مطلق صحبت می کند اشتباه می کند:]
❌ در غیر این صورت: برخی از ابزارها با ROI شگفت انگیز را از دست خواهید داد، برخی مانند Fuzz، lint و جهش می توانند در 10 دقیقه ارزش ارائه دهند.
✏ نمونه کد
👏 مثال درست: سیندی سریدهان یک نمونه کار آزمایشی غنی را در پست شگفتانگیز خود «تست کردن میکروسرویسها» پیشنهاد میکند - به همین روش.
✅ انجام دادن: هر Unit test بخش کوچکی از برنامه را پوشش میدهد و پوشش کل آن گران است، در حالی که تست E2E به راحتی زمین زیادی را پوشش میدهد، اما پوسته پوسته و کندتر است، چرا یک رویکرد متعادل را اعمال نکنیم و تستهایی بزرگتر از آن بنویسیم. Unit tests اما کوچکتر از تست E2E؟ تست کامپوننت آهنگ ناخوانده دنیای آزمایش است— آنها بهترین ها را از هر دو دنیا ارائه می دهند: عملکرد معقول و امکان اعمال الگوهای TDD + پوشش واقعی و عالی.
تستهای کامپوننت روی «واحد» میکروسرویس تمرکز میکنند، آنها بر خلاف API کار میکنند، هر چیزی را که متعلق به خود میکروسرویس است (مثلاً DB واقعی یا حداقل نسخه درون حافظه آن DB) را مسخره نمیکنند، اما هر چیزی را که خارجی است، خرد نمیکنند. مانند تماس با سایر میکروسرویس ها. با انجام این کار، آنچه را که به کار میگیریم آزمایش میکنیم، برنامه را از بیرون به داخل نزدیک میکنیم و در مدت زمان معقولی اعتماد به نفس بالایی به دست میآوریم.
ما یک راهنمای کامل داریم که صرفاً به نوشتن تست های مؤلفه به روش صحیح اختصاص دارد
❌ در غیر این صورت: ممکن است روزهای طولانی را صرف نوشتن Unit test کنید تا متوجه شوید که فقط 20 درصد پوشش سیستم را دارید
✏ نمونه کد
⚪ ️2.3 اطمینان حاصل کنید که نسخههای جدید نرم افزار , تست هایی که برای api ها نوشته شده است را خراب نمیکنند
✅ انجام دادن: بنابراین Microservice شما چندین مشتری دارد و شما چندین نسخه از این سرویس را به دلایل سازگاری اجرا می کنید (راضی نگه داشتن همه). سپس مقداری فیلد را تغییر میدهید و «بوم!»، مشتری مهمی که به این زمینه متکی است عصبانی است. این Catch-22 دنیای یکپارچه سازی است: برای طرف سرور بسیار چالش برانگیز است که تمام انتظارات مشتری چندگانه را در نظر بگیرد— از سوی دیگر، مشتریان نمی توانند هیچ آزمایشی را انجام دهند زیرا سرور تاریخ انتشار را کنترل می کند. طیفی از تکنیکها وجود دارند که میتوانند مشکل قرارداد را کاهش دهند، برخی ساده هستند، برخی دیگر دارای ویژگیهای غنیتر هستند و منحنی یادگیری تندتری را طلب میکنند. در یک رویکرد ساده و توصیهشده، ارائهدهنده API بسته npm را با تایپ API منتشر میکند (مانند JSDoc، TypeScript). سپس مصرف کنندگان می توانند این کتابخانه را دریافت کنند و از زمان هوشمندانه و اعتبارسنجی بهره مند شوند. یک رویکرد شیک تر از آن برای استفاده PACT که برای رسمی کردن این فرآیند با رویکردی بسیار مخرب متولد شدهاند—— نه سرور برنامه آزمایشی را از خود تعریف میکند، بلکه مشتری آزمایشهای سرور را تعریف میکند! PACT میتواند انتظارات مشتری را ثبت کند و در یک مکان مشترک، «کارگزار» قرار دهد، بنابراین سرور میتواند انتظارات را جمعآوری کند و با استفاده از کتابخانه PACT روی هر ساختنی اجرا کند تا قراردادهای شکسته را شناسایی کند - یک انتظار مشتری که برآورده نشده است. با انجام این کار، همه ناهماهنگی های API سرور-کلینت در مراحل اولیه ساخت/CI شناسایی می شوند و ممکن است شما را از ناامیدی زیادی نجات دهد.
❌ در غیر این صورت: گزینه های جایگزین تست دستی خسته کننده یا ترس از استقرار هستند
✅ انجام دادن: بسیاری از تست Middleware اجتناب می کنند زیرا آنها بخش کوچکی از سیستم را نشان می دهند و به یک سرور Express زنده نیاز دارند. هر دو دلیل اشتباه هستند——Middleware ها کوچک هستند اما بر همه یا بیشتر درخواست ها تأثیر می گذارند و می توانند به راحتی به عنوان توابع خالصی که اشیاء JS {req,res} را دریافت می کنند آزمایش شوند. برای آزمایش عملکرد میانافزار، فقط باید آن را فراخوانی و جاسوسی کرد (برای مثال از Sinon استفاده کنید) در تعامل با دو object {req,res} برای اطمینان از انجام عملکرد صحیح عملکرد. کتابخانه node-mock-http آن را حتی فراتر می برد و دو object {req,res} را همراه با جاسوسی از رفتار آنها فاکتور می کند. به عنوان مثال، می تواند ادعا کند که آیا وضعیت http که روی شی res تنظیم شده است با انتظارات مطابقت دارد یا خیر (به مثال زیر مراجعه کنید).
❌ در غیر این صورت: یک اشکال در میان افزار Express === یک اشکال در همه یا اکثر request ها
✏ نمونه کد
//middleware که می خواهیم تست کنیم
const unitUnderTest = require("./middleware");
const httpMocks = require("node-mocks-http");
//Jest syntax, equivelant to describe() & it() in Mocha
test("درخواست بدون هدر احراز هویت، باید وضعیت http 403 را برگرداند", () => {
const request = httpMocks.createRequest({
method: "GET",
url: "/user/42",
headers: {
authentication: ""
}
});
const response = httpMocks.createResponse();
unitUnderTest(request, response);
expect(response.statusCode).toBe(403);
});
✅ انجام دادن: استفاده از ابزارهای تجزیه و تحلیل استاتیک با ارائه روش های عینی برای بهبود کیفیت کد و حفظ کد شما کمک می کند. میتوانید ابزارهای تجزیه و تحلیل استاتیک را به ساخت CI خود اضافه کنید تا زمانی که بوی کد پیدا میشود، لغو شود. نقاط اصلی فروش آن نسبت به پردهبندی ساده، توانایی بازرسی کیفیت در زمینه فایلهای متعدد (به عنوان مثال شناسایی موارد تکراری)، انجام تجزیه و تحلیل پیشرفته (مانند پیچیدگی کد) و پیگیری تاریخچه و پیشرفت مشکلات کد است. دو نمونه از ابزارهایی که می توانید استفاده کنید عبارتند از SonarQube (4,900+ ستاره) و کد آب و هوا (2,000+ ستاره)
سازنده: Keith Holliday
❌ در غیر این صورت: با کیفیت پایین کد، اشکالات و عملکرد همیشه مشکلی است که هیچ کتابخانه جدید و براق جدیدی نمی تواند آن را برطرف کند.
✅ انجام دادن: به طور عجیبی، اکثر تستهای نرمافزار فقط در مورد منطق و دادهها هستند، اما برخی از بدترین چیزهایی که اتفاق میافتد (و واقعاً کاهش آن سخت است) مسائل زیرساختی است. به عنوان مثال، آیا تا به حال آزمایش کردهاید که چه اتفاقی میافتد وقتی حافظه پردازش شما بیش از حد بارگیری میشود، یا زمانی که سرور/فرآیند از بین میرود، یا آیا سیستم نظارت شما متوجه میشود که API 50٪ کندتر میشود؟ برای آزمایش و کاهش این نوع چیزهای بد——مهندسی آشوب توسط نتفلیکس متولد شد. هدف آن ارائه آگاهی، چارچوب ها و ابزارهایی برای آزمایش انعطاف پذیری برنامه ما در برابر مسائل آشفته است. به عنوان مثال یکی از ابزارهای معروف آن، میمون آشوب, به طور تصادفی سرورها را می کشد تا اطمینان حاصل شود که سرویس ما همچنان می تواند به کاربران سرویس دهد و به یک سرور تکیه نمی کند (نسخه Kubernetes نیز وجود دارد، میمون کوبه, که غلاف ها را می کشد). همه این ابزارها در سطح میزبانی/پلتفرم کار میکنند، اما اگر میخواهید آشفتگی Node خالص را آزمایش و ایجاد کنید، چه میکنید، مانند بررسی اینکه چگونه فرآیند Node شما با خطاهای کشف نشده، رد کردن وعدههای کنترل نشده، حافظه v8 بارگیری شده با حداکثر مجاز 1.7 گیگابایت یا اینکه آیا کنترل میشود چه میشود. زمانی که حلقه رویداد اغلب مسدود می شود، تجربه کاربری شما رضایت بخش باقی می ماند؟ برای پرداختن به این که نوشته ام، node-chaos (alpha) که انواع اعمال آشفته مرتبط با Node را فراهم می کند
❌ در غیر این صورت: اینجا راه گریزی نیست، قانون مورفی بدون رحم به تولید شما ضربه می زند
✏ نمونه کد
✅ انجام دادن: با توجه به قانون طلایی (گلوله 0)، هر تست باید مجموعه ای از ردیف های DB خود را اضافه کرده و روی آن عمل کند تا از جفت شدن جلوگیری کند و به راحتی در مورد جریان تست استدلال کند. در واقع، این امر اغلب توسط آزمایشکنندگانی که قبل از اجرای آزمایشها (که به عنوان «تست فیکسچر» نیز شناخته میشود) را با دادهها به منظور بهبود عملکرد میدانند، نقض میشود. در حالی که عملکرد در واقع یک نگرانی معتبر است. عملاً، هر مورد آزمایشی را به صراحت سوابق DB مورد نیاز خود را اضافه کنید و فقط روی آن رکوردها عمل کنید. اگر عملکرد به یک نگرانی حیاتی تبدیل شود - یک مصالحه متوازن ممکن است به شکل کاشت تنها مجموعه آزمایشهایی باشد که دادهها را تغییر نمیدهند (مثلاً درخواستها)
❌ در غیر این صورت: تعداد کمی از تست ها شکست می خورند، استقرار متوقف می شود، تیم ما اکنون زمان گرانبهایی را صرف می کند، آیا ما باگ داریم؟ بیایید بررسی کنیم، اوه نه—به نظر میرسد دو تست دادههای اولیه مشابهی را جهش میدادند
✏ نمونه کد
before(async () => {
//افزودن سایت ها و داده های مدیران به DB ما. داده ها کجاست؟ خارج از. در برخی framework های json یا مهاجرت خارجی
await DB.AddSeedDataFromJson('seed.json');
});
it("هنگام به روز رسانی نام سایت، تأیید موفقیت آمیز دریافت کنید", async () => {
//من می دانم که نام سایت "portal" وجود دارد - آن را در فایل های seed دیدم
const siteToUpdate = await SiteService.getSiteByName("Portal");
const updateNameResult = await SiteService.changeName(siteToUpdate, "newName");
expect(updateNameResult).to.be(true);
});
it("هنگام پرس و جو بر اساس نام سایت، سایت مناسب را دریافت کنید", async () => {
//من می دانم که نام سایت "portal" وجود دارد - آن را در فایل های seed دیدم
const siteToCheck = await SiteService.getSiteByName("Portal");
expect(siteToCheck.name).to.be.equal("Portal"); //شکست! تست قبلی نام را تغییر دهید:[
});
it("هنگام به روز رسانی نام سایت، تأیید موفقیت آمیز دریافت کنید", async () => {
//تست اضافه کردن یک رکورد جدید جدید و عمل کردن فقط بر روی رکوردها است
const siteUnderTest = await SiteService.addSite({
name: "siteForUpdateTest"
});
const updateNameResult = await SiteService.changeName(siteUnderTest, "newName");
expect(updateNameResult).to.be(true);
});
✅ انجام دادن: زمانی که تست ها پایگاه داده را تمیز می کنند، نحوه نگارش تست ها را تعیین می کند. دو گزینه مناسب، تمیز کردن بعد از همه آزمایش ها در مقابل تمیز کردن بعد از هر آزمایش است. با انتخاب گزینه دوم، تمیز کردن پس از هر آزمایش جداول تمیز را تضمین می کند و مزایای آزمایشی راحت را برای توسعه دهنده ایجاد می کند. هیچ رکورد دیگری هنگام شروع آزمایش وجود ندارد، می توان مطمئن بود که کدام داده مورد پرسش قرار می گیرد و حتی ممکن است وسوسه شود ردیف ها را در طول ادعاها بشمارد. این امر با معایب شدیدی همراه است: هنگام اجرا در حالت چند فرآیندی، آزمایشها احتمالاً با یکدیگر تداخل دارند. در حالی که process-1 جداول را پاک می کند، در همان لحظه پردازش-2 برای داده ها جستجو می کند و با شکست مواجه می شود (زیرا DB به طور ناگهانی توسط فرآیند-1 حذف شده است). علاوه بر این، عیبیابی تستهای شکست خورده سختتر است - بازدید از DB هیچ رکوردی را نشان نمیدهد.
گزینه دوم این است که پس از اتمام تمام فایل های تست (یا حتی روزانه!) پاکسازی کنید. این رویکرد به این معنی است که همان DB با رکوردهای موجود، تمام تست ها و فرآیندها را ارائه می دهد. برای جلوگیری از پا گذاشتن روی انگشتان یکدیگر، آزمون ها باید سوابق خاصی را که اضافه کرده اند اضافه کرده و بر اساس آنها عمل کنند. آیا باید بررسی کنید که رکوردی اضافه شده است؟ فرض کنید هزاران رکورد و پرس و جوی دیگر برای رکوردهایی وجود دارد که به صراحت اضافه شده اند. آیا باید بررسی کنید که یک رکورد حذف شده است؟ نمی توان جدول خالی را فرض کرد، بررسی کنید که این رکورد خاص وجود ندارد. این تکنیک دستاوردهای قدرتمند کمی را به همراه دارد: زمانی که یک توسعهدهنده میخواهد اتفاقی را که رخ داده است بفهمد - به طور بومی در حالت چند فرآیندی کار میکند - دادهها آنجا هستند و حذف نمیشوند. همچنین شانس یافتن باگ ها را افزایش می دهد زیرا DB پر از رکورد است و به طور مصنوعی خالی نیست. جدول مقایسه کامل را اینجا ببینید.
❌ در غیر این صورت: بدون استراتژی برای جدا کردن رکوردها یا تمیز کردن - تست ها روی انگشتان یکدیگر قرار می گیرند. استفاده از تراکنشها فقط برای DB رابطهای کار میکند و احتمالاً زمانی که تراکنشهای درونی وجود دارد، پیچیده میشود
✏ نمونه کد
👏 تمیز کردن بعد از تمام آزمایشات نه لزوما بعد از هر دویدن. هرچه دادههای بیشتری در حین انجام آزمایشها داشته باشیم - بیشتر شبیه امتیازات تولید است
//بعد از همه پاکسازی (توصیه می شود)
// global-teardown.js
module.exports = async () => {
// ...
if (Math.ceil(Math.random() * 10) === 10) {
await new OrderRepository().cleanup();
}
};
✅ انجام دادن: مؤلفه تحت آزمایش را با رهگیری هر درخواست HTTP خروجی و ارائه پاسخ مورد نظر جدا کنید تا HTTP API همکار آسیب نبیند. Nock یک ابزار عالی برای این ماموریت است زیرا یک نحو مناسب برای تعریف رفتار خدمات خارجی ارائه می دهد. جداسازی برای جلوگیری از نویز و عملکرد آهسته ضروری است، اما بیشتر برای شبیهسازی سناریوها و پاسخهای مختلف - یک شبیهساز پرواز خوب به معنای رنگآمیزی آسمان آبی شفاف نیست، بلکه ایجاد طوفان و هرجومرج امن است. این در معماری Microservice که در آن تمرکز همیشه باید بر روی یک جزء واحد بدون درگیر کردن بقیه جهان باشد، تقویت شده است. اگرچه می توان رفتار سرویس خارجی را با استفاده از تست دوبل (مسخره) شبیه سازی کرد، اما ترجیحاً کد مستقر شده را لمس نکنید و در سطح شبکه عمل کنید تا تست ها را جعبه سیاه خالص نگه دارید. نقطه ضعف جداسازی عدم تشخیص زمانی که مؤلفه همکار تغییر می کند و متوجه نشدن سوء تفاهم بین دو سرویس است - مطمئن شوید که با استفاده از چند آزمایش قرارداد یا E2E این را جبران کنید.
❌ در غیر این صورت: برخی از سرویسها یک نسخه جعلی را ارائه میکنند که میتواند توسط تماسگیرنده به صورت محلی، معمولاً با استفاده از Docker اجرا شود. برخی از سرویسها محیط «sandbox» را ارائه میکنند، بنابراین سرویس واقعی ضربه میخورد اما هیچ هزینه یا عوارض جانبی ایجاد نمیشود - این کار صدای راهاندازی سرویس شخص ثالث را کاهش میدهد، اما امکان شبیهسازی سناریوها را نیز فراهم نمیکند.
✏ نمونه کد
👏 جلوگیری از تماس شبکه با اجزای خارجی امکان شبیه سازی سناریوها و به حداقل رساندن نویز را فراهم می کند
// درخواست های API های شخص ثالث را رهگیری کنید و یک پاسخ از پیش تعریف شده را برگردانید
beforeEach(() => {
nock('http://localhost/user/').get(`/1`).reply(200, {
id: 1,
name: 'John',
});
});
✅ انجام دادن: هنگامی که ادعا کردن برای داده های خاص غیرممکن است، وجود و انواع فیلد اجباری را بررسی کنید. گاهی اوقات، پاسخ حاوی فیلدهای مهم با داده های پویا است که نمی توان آنها را هنگام نوشتن آزمون پیش بینی کرد، مانند تاریخ ها و افزایش اعداد. اگر قرارداد API وعده می دهد که این فیلدها پوچ نخواهند بود و انواع مناسب را در خود دارند، آزمایش آن ضروری است. اکثر کتابخانه های ادعا از انواع بررسی پشتیبانی می کنند. اگر پاسخ کوچک است، داده های برگشتی را بررسی کنید و با هم در همان ادعا تایپ کنید (به مثال کد مراجعه کنید). یک گزینه دیگر این است که کل پاسخ را در برابر یک سند OpenAPI (Swagger) تأیید کنید. اکثر اجراکنندگان آزمایش دارای پسوندهای جامعه هستند که پاسخ های API را در برابر اسناد آنها تأیید می کند.
❌ در غیر این صورت: اگرچه تماسگیرنده کد/API به فیلدهایی با دادههای پویا (مثلاً شناسه، تاریخ) متکی است، اما در عوض نمیآید و قرارداد را زیر پا نمیگذارد.
✏ نمونه کد
test('هنگام اضافه کردن یک سفارش معتبر جدید، سپس باید با 200 پاسخ تأیید مجدد دریافت کنید', async () => {
// ...
//مقایسه کردن
expect(receivedAPIResponse).toMatchObject({
status: 200,
data: {
id: expect.any(Number), // هر عددی این تست را برآورده می کند
mode: 'approved',
},
});
});
✅ انجام دادن: هنگام بررسی ادغام ها، از مسیرهای شاد و غم انگیز فراتر بروید. نه تنها پاسخ های خطا (به عنوان مثال، خطای HTTP 500) بلکه ناهنجاری های سطح شبکه مانند پاسخ های آهسته و به پایان رسیده را بررسی کنید. این ثابت میکند که کد انعطافپذیر است و میتواند سناریوهای مختلف شبکه را مدیریت کند، مانند انتخاب مسیر درست پس از یک بازه زمانی، هیچ شرایط مسابقه شکنندهای ندارد، و حاوی یک قطع کننده مدار برای تلاشهای مجدد است. ابزارهای رهگیر معتبر به راحتی می توانند رفتارهای مختلف شبکه مانند سرویس های گیج کننده را که گهگاه با شکست مواجه می شوند شبیه سازی کنند. حتی میتواند متوجه شود که چه زمانی مقدار زمانبندی پیشفرض مشتری HTTP از زمان پاسخ شبیهسازیشده بیشتر است و فوراً بدون انتظار، یک استثنای مهلت زمانی ایجاد کند.
❌ در عیر این صورت: تمام تستهای شما با موفقیت انجام میشود، این فقط تولید است که از کار میافتد یا وقتی شخص ثالث پاسخهای استثنایی ارسال میکند، خطاها را به درستی گزارش نمیکند.
✏ نمونه کد
test('هنگامی که سرویس کاربران یک بار با 503 پاسخ می دهد و مکانیسم امتحان مجدد اعمال می شود، سفارش با موفقیت اضافه می شود', async () => {
//مقدار دهی کردن
nock.removeInterceptor(userServiceNock.interceptors[0])
nock('http://localhost/user/')
.get('/1')
.reply(503, undefined, { 'Retry-After': 100 });
nock('http://localhost/user/')
.get('/1')
.reply(200);
const orderToAdd = {
userId: 1,
productId: 2,
mode: 'approved',
};
//اجرا کردن
const response = await axiosAPIClient.post('/order', orderToAdd);
//مقایسه کردن
expect(response.status).toBe(200);
});
✅ انجام دادن: هنگام برنامه ریزی آزمایشات خود، پنج خروجی جریان معمولی را پوشش دهید. هنگامی که آزمایش شما در حال انجام برخی اقدامات است (به عنوان مثال، فراخوانی API)، واکنشی در حال رخ دادن است، چیزی معنادار رخ می دهد و نیاز به آزمایش دارد. توجه داشته باشید که ما به نحوه کار کردن اهمیت نمی دهیم. تمرکز ما روی نتایج است، چیزهایی که از بیرون قابل توجه هستند و ممکن است بر کاربر تأثیر بگذارند. این پیامدها/واکنش ها را می توان در 5 دسته قرار داد:
• نتیحه - تست یک عمل را فراخوانی می کند (به عنوان مثال، از طریق API) و یک پاسخ دریافت می کند. اکنون به بررسی صحت داده های پاسخ، طرح و وضعیت HTTP می پردازد
• state جدید - پس از فراخوانی یک عمل، برخی از دادههای در دسترس عموم احتمالاً اصلاح میشوند
• تماس های داخلی - پس از فراخوانی یک عمل، برنامه ممکن است یک مؤلفه خارجی را از طریق HTTP یا هر انتقال دیگر فراخوانی کند. به عنوان مثال، تماس برای ارسال پیامک، ایمیل یا شارژ کارت اعتباری
• صفی از پیام ها - نتیجه یک جریان ممکن است یک پیام در یک صف باشد
• قابلیت مشاهده - برخی از چیزها مانند خطاها یا رویدادهای تجاری قابل توجه باید نظارت شوند. هنگامی که یک تراکنش با شکست مواجه می شود، نه تنها ما انتظار پاسخ مناسب را داریم، بلکه مدیریت صحیح خطا و ثبت/سنجه های مناسب را نیز انتظار داریم. این اطلاعات مستقیماً به یک کاربر بسیار مهم می رود - کاربر ops (یعنی تولید SRE/admin)
✅ انجام دادن: هنگام تمرکز بر روی تست منطق مؤلفه، جزئیات UI تبدیل به نویز می شود که باید استخراج شود، بنابراین آزمایشات شما می توانند بر روی داده های خالص تمرکز کنند. عملاً دادههای مورد نظر را از نشانهگذاری به روشی انتزاعی استخراج کنید که خیلی با پیادهسازی گرافیکی همراه نباشد، فقط روی دادههای خالص (در مقابل جزئیات گرافیکی HTML/CSS) ادعا کنید و انیمیشنهایی را که کند میشوند غیرفعال کنید. ممکن است وسوسه شوید که از رندر کردن خودداری کنید و فقط قسمت پشتی رابط کاربری (مانند سرویسها، اقدامات، فروشگاه) را آزمایش کنید، اما این منجر به آزمایشهای تخیلی میشود که به واقعیت شباهت ندارند و مواردی را که دادههای مناسب وجود ندارد را نشان نمیدهد. حتی وارد UI شوید
❌ در غیر این صورت: دادههای محاسباتی خالص آزمون شما ممکن است در 10 میلیثانیه آماده باشد، اما پس از آن کل آزمون به دلیل برخی انیمیشنهای فانتزی و نامربوط، 500 میلیثانیه (100 تست = 1 دقیقه) طول خواهد کشید.
✏ نمونه کد
test("هنگامی که لیست کاربران برای نشان دادن فقط VIP پرچم گذاری می شود، باید فقط اعضای VIP نمایش داده شود", () => {
// مقدار دهی کردن
const allUsers = [{ id: 1, name: "Yoni Goldberg", vip: false }, { id: 2, name: "John Doe", vip: true }];
// اجرا کردن
const { getAllByTestId } = render(<UsersList users={allUsers} showOnlyVIP={true} />);
// مفایسه کردن - ابتدا داده ها را از UI استخراج کنید
const allRenderedUsers = getAllByTestId("user").map(uiElement => uiElement.textContent);
const allRealVIPUsers = allUsers.filter(user => user.vip).map(user => user.name);
expect(allRenderedUsers).toEqual(allRealVIPUsers); //داده ها را با داده ها مقایسه کنید، اینجا رابط کاربری وجود ندارد
});
test("هنگام پرچم گذاری برای نمایش فقط VIP، باید فقط اعضای VIP نمایش داده شود", () => {
// مقداری دهی کردن
const allUsers = [{ id: 1, name: "Yoni Goldberg", vip: false }, { id: 2, name: "John Doe", vip: true }];
// اجرا کردن
const { getAllByTestId } = render(<UsersList users={allUsers} showOnlyVIP={true} />);
// مقابسه کردن - ترکیب ui و data در این قسمت
expect(getAllByTestId("user")).toEqual('[<li data-test-id="user">John Doe</li>]');
});
✅ انجام دادن: عناصر HTML را بر اساس ویژگی هایی که احتمالاً بر خلاف انتخابگرهای CSS و مانند برچسب های فرم، در تغییرات گرافیکی باقی می مانند، پرس و جو کنید. اگر عنصر تعیینشده چنین ویژگیهایی را ندارد، یک ویژگی تست اختصاصی مانند «test-id-submit-button» ایجاد کنید. پیمودن این مسیر نه تنها تضمین میکند که تستهای عملکردی/منطقی شما هرگز به دلیل تغییرات ظاهری و احساسی شکسته نمیشوند، بلکه برای کل تیم مشخص میشود که این عنصر و ویژگی توسط تست ها استفاده میشوند و نباید حذف شوند.
❌ در غیر این صورت: شما میخواهید عملکرد ورود به سیستم را که شامل بسیاری از مؤلفهها، منطق و سرویسها میشود، آزمایش کنید، همه چیز بهخوبی تنظیم شده است - موارد خرد، جاسوسها، تماسهای Ajax جدا شدهاند. همه عالی به نظر می رسند سپس تست شکست می خورد زیرا طراح کلاس div CSS را از "thick-border" به "thin-border" تغییر داده است.'
✏ نمونه کد
// کد نشانه گذاری (بخشی از مؤلفه React)
<h3>
<Badge pill className="fixed_badge" variant="dark">
<span data-test-id="errorsLabel">{value}</span>
<!-- به ویژگی data-test-id توجه کنید -->
</Badge>
</h3>
// این مثال از react-testing-library استفاده می کند
test("هر زمان که هیچ داده ای به متریک منتقل نمی شود، 0 را به عنوان پیش فرض نشان دهید", () => {
// مقدار دهی کردن
const metricValue = undefined;
// اجرا کردن
const { getByTestId } = render(<dashboardMetric value={undefined} />);
expect(getByTestId("errorsLabel").text()).toBe("0");
});
<!-- کد نشانه گذاری (بخشی از مؤلفه React) -->
<span id="metric" className="d-flex-column">{value}</span>
<!-- اگر طراح کلاس ها را تغییر دهد چه؟ -->
// این مثال استفاده از آنزیم است
test("هر زمان که هیچ داده ای ارسال نشود، معیار خطا صفر را نشان می دهد", () => {
// ...
expect(wrapper.find("[className='d-flex-column']").text()).toBe("0");
});
✅ انجام دادن: هر زمان که اندازه معقولی داشتید، مؤلفه خود را از خارج مانند کاربرانتان آزمایش کنید، UI را به طور کامل رندر کنید، بر اساس آن عمل کنید و ادعا کنید که رابط کاربری رندر شده مطابق انتظار عمل می کند. از هر گونه رندر تمسخر آمیز، جزئی و کم عمق خودداری کنید - این روش ممکن است به دلیل کمبود جزئیات منجر به اشکالات محفوظ نشده و تعمیر و نگهداری سخت تر شود زیرا آزمایش ها با اجزای داخلی به هم می خورند (به گلوله مراجعه کنید 'از تست جعبه سیاه حمایت کنید'). اگر یکی از اجزای کودک به طور قابل توجهی کند می شود (مثلاً انیمیشن) یا تنظیمات را پیچیده می کند - به طور واضح آن را با یک جعلی جایگزین کنید.
با تمام آنچه گفته شد، یک کلمه احتیاط لازم است: این تکنیک برای اجزای کوچک/متوسط که اندازه معقولی از اجزای کودک را در خود جای می دهند، کار می کند. رندر کردن کامل یک مؤلفه با تعداد زیاد فرزندان، استدلال در مورد شکست تست (تحلیل علت ریشه) را دشوار می کند و ممکن است بسیار کند شود. در چنین مواردی، فقط چند آزمایش بر روی آن جزء والد چاق بنویسید و آزمایش های بیشتری را بر روی فرزندان آن بنویسید
❌ در غیر این صورت: هنگامی که با فراخوانی روشهای خصوصی و بررسی وضعیت درونی یک کامپوننت به درونی آن وارد میشوید - باید همه آزمایشها را هنگام بازسازی اجرای مؤلفهها مجدداً تغییر دهید. آیا واقعاً برای این سطح از نگهداری ظرفیت دارید؟?
✏ نمونه کد
class Calendar extends React.Component {
static defaultProps = { showFilters: false };
render() {
return (
<div>
A filters panel with a button to hide/show filters
<FiltersPanel showFilter={showFilters} title="Choose Filters" />
</div>
);
}
}
//برای مثال از React & Enzyme استفاده می شود
test("رویکرد واقع گرایانه: هنگامی که برای نمایش فیلترها کلیک کنید، فیلترها نمایش داده می شوند", () => {
// مقدار دهی کردن
const wrapper = mount(<Calendar showFilters={false} />);
// اجرا کردن
wrapper.find("button").simulate("click");
// مقابسه کردن
expect(wrapper.text().includes("Choose Filter"));
// به این صورت است که کاربر به این عنصر نزدیک می شود: توسط متن
});
test("رویکرد کم عمق/ مسخره شده: وقتی برای نمایش فیلترها کلیک کنید، فیلترها نمایش داده می شوند", () => {
// مقدار دهی کردن
const wrapper = shallow(<Calendar showFilters={false} title="Choose Filter" />);
// اجرا کردن
wrapper
.find("filtersPanel")
.instance()
.showFilters();
// Tap into the internals, bypass the UI and invoke a method. White-box approach
// مقایسه کردن
expect(wrapper.find("Filter").props()).toEqual({ title: "Choose Filter" });
// اگر نام پروپوزال را تغییر دهیم یا چیزی مربوطه را پاس نکنیم چه؟
});
⚪ ️ 3.4 نخوابید، از frameworkهای داخلی پشتیبانی برای رویدادهای همگام استفاده کنید. همچنین سعی کنید کارها را تسریع کنید
✅ انجام دادن: در بسیاری از موارد، واحد تحت آزمایش زمان کامل ناشناخته است (مثلاً انیمیشن ظاهر عنصر را به حالت تعلیق در میآورد) - در این صورت، از خوابیدن خودداری کنید (مثلاً setTimeOut) و روشهای قطعیتری را که اکثر پلتفرمها ارائه میدهند ترجیح دهید. برخی از کتابخانه ها امکان انتظار برای عملیات را فراهم می کنند (e.g. Cypress cy.request('url')), سایر API برای انتظار مانند @testing-library/dom method wait(expect(element)). گاهی اوقات یک راه ظریف تر، خرد کردن منبع کند است، مانند API، و سپس زمانی که لحظه پاسخ قطعی شد، مولفه می تواند به صراحت دوباره ارائه شود. هنگامی که به برخی از اجزای خارجی که می خوابند وابسته می شوند، ممکن است مفید باشد hurry-up the clock. خوابیدن الگویی است که باید از آن اجتناب کنید زیرا باعث می شود آزمون شما آهسته یا پرخطر باشد (زمانی که برای مدت کوتاهی منتظر می مانید). هر زمان که خواب و نظرسنجی اجتناب ناپذیر است و هیچ پشتیبانی از چارچوب تست وجود ندارد، برخی از کتابخانه های npm مانند wait-for-expect می تواند با یک راه حل نیمه قطعی کمک کند
❌ در غیر این صورت: هنگام خواب طولانی مدت، آزمایش ها یک مرتبه کندتر خواهند بود. هنگامی که سعی می کنید برای تعداد کمی بخوابید، زمانی که واحد تحت آزمایش به موقع پاسخ ندهد، آزمایش با شکست مواجه می شود. بنابراین به یک مبادله بین پوسته پوسته شدن و عملکرد بد خلاصه می شود
✏ نمونه کد
// استفاده كردن از Cypress
cy.get("#show-products").click(); // navigate
cy.wait("@products"); // صبر کنید تا مسیر ظاهر شود
// این خط تنها زمانی اجرا می شود که مسیر آماده باشد
// @testing-library/dom
test("movie title appears", async () => {
// عنصر در ابتدا وجود ندارد ...
//منتظر ظهور باشید
await wait(() => {
expect(getByText("the lion king")).toBeInTheDocument();
});
// منتظر ظاهر شدن باشید و عنصر را برگردانید
const movie = await waitForElement(() => getByText("the lion king"));
});
test("movie title appears", async () => {
// عنصر در ابتدا وجود ندارد ...
// منطق انتظار سفارشی (احتیاط: ساده، بدون مهلت زمانی)
const interval = setInterval(() => {
const found = getByText("the lion king");
if (found) {
clearInterval(interval);
expect(getByText("the lion king")).toBeInTheDocument();
}
}, 100);
// منتظر ظاهر شدن باشید و عنصر را برگردانید
const movie = await waitForElement(() => getByText("the lion king"));
});
✅ انجام دادن: برخی از مانیتورهای فعال را اعمال کنید که تضمین میکند بارگذاری صفحه در شبکه واقعی بهینه شده است - این شامل هر گونه نگرانی UX مانند بارگذاری صفحه آهسته یا بستهای کوچک نشده میشود. بازار ابزار بازرسی کوتاه نیست: ابزارهای اساسی مانند pingdom, AWS CloudWatch, gcp StackDriver can be easily configured to watch whether the server is alive and response under a reasonable SLA. This only scratches the surface of what might get wrong, hence it's preferable to opt for tools that specialize in frontend (e.g. lighthouse, pagespeed) and perform richer analysis. The focus should be on symptoms, metrics that directly affect the UX, like page load time, meaningful paint, time until the page gets interactive (TTI). On top of that, one may also watch for technical causes like ensuring the content is compressed, time to the first byte, optimize images, ensuring reasonable DOM size, SSL and many others. It's advisable to have these rich monitors both during development, as part of the CI and most important - 24x7 over the production's servers/CDN
❌ در غیر این صورت: باید ناامید کننده باشد که متوجه شوید پس از چنین دقت زیادی برای ایجاد یک UI، تست های عملکردی 100% گذرانده شده و بسته بندی پیچیده - UX به دلیل پیکربندی اشتباه CDN وحشتناک و کند است.
✅ انجام دادن: هنگام کدنویسی تستهای اصلی خود (نه تستهای E2E)، از درگیر کردن هر منبعی که خارج از مسئولیت و کنترل شماست مانند API پشتیبان خودداری کنید و به جای آن از خردهها استفاده کنید (یعنی تست دوبار). عملا، به جای تماس های شبکه واقعی با API ها، از چند کتابخانه دوتایی (مانند Sinon, Test doubles, etc) برای کلفت کردن پاسخ API. مزیت اصلی جلوگیری از پوسته پوسته شدن است - آزمایش یا مرحلهبندی APIها طبق تعریف چندان پایدار نیستند و هر از چند گاهی در تستهای شما شکست میخورند، اگرچه مؤلفه شما به خوبی رفتار میکند (انواع تولید برای آزمایش در نظر گرفته نشده است و معمولاً درخواستها را کاهش میدهد). انجام این کار به شبیهسازی رفتارهای API مختلف اجازه میدهد که رفتار مؤلفه شما را مانند زمانی که هیچ دادهای پیدا نمیشود یا زمانی که API خطا میدهد، هدایت کند. آخرین اما نه کماهمیت، تماسهای شبکه سرعت آزمایشها را بسیار کند میکند
❌ در غیر این صورت: میانگین تست بیشتر از چند میلی ثانیه اجرا نمی شود، یک تماس API معمولی 100 میلی ثانیه طول می کشد>، این باعث می شود هر تست 20 برابر کندتر شود.
✏ نمونه کد
// واحد در حال آزمایش
export default function ProductsList() {
const [products, setProducts] = useState(false);
const fetchProducts = async () => {
const products = await axios.get("api/products");
setProducts(products);
};
useEffect(() => {
fetchProducts();
}, []);
return products ? <div>{products}</div> : <div data-test-id="no-products-message">No products</div>;
}
// تست کردن
test("When no products exist, show the appropriate message", () => {
// مقدار دهی کردن
nock("api")
.get(`/products`)
.reply(404);
// اجرا کردن
const { getByTestId } = render(<ProductsList />);
// مقایسه کردن
expect(getByTestId("no-products-message")).toBeTruthy();
});
✅ انجام دادن: اگرچه E2E (پایان به انتها) معمولاً به معنای آزمایش فقط UI با یک مرورگر واقعی است (نگاه کنید به bullet 3.6), برای دیگر آنها به معنای آزمایش هایی هستند که کل سیستم از جمله باطن واقعی را گسترش می دهند. تستهای نوع دوم بسیار ارزشمند هستند، زیرا اشکالات یکپارچهسازی بین frontend و backend را پوشش میدهند که ممکن است به دلیل درک اشتباه از طرح مبادله رخ دهد. آنها همچنین یک روش کارآمد برای کشف مسائل یکپارچه سازی backend-to-backend هستند (مثلاً Microservice A پیام اشتباهی را به Microservice B ارسال می کند) و حتی برای تشخیص خرابی های استقرار - هیچ چارچوب Backend برای آزمایش E2E وجود ندارد که به اندازه UI دوستانه و بالغ باشد. چارچوب هایی مانندCypress و Puppeteer. نقطه ضعف چنین آزمایشهایی هزینه بالای پیکربندی یک محیط با اجزای بسیار زیاد و بیشتر شکنندگی آنها است - با توجه به 50 میکروسرویس، حتی اگر یکی از آنها ناموفق باشد، کل E2E فقط از کار افتاده است. به همین دلیل، ما باید از این تکنیک کم استفاده کنیم و احتمالاً 1-10 مورد از آنها را داشته باشیم و نه بیشتر. گفته میشود، حتی تعداد کمی از تستهای E2E احتمالاً نوع مشکلاتی را که برای آنها هدف قرار گرفتهاند - خطاهای استقرار و یکپارچهسازی را شناسایی میکنند. توصیه میشود آنها را روی یک محیط صحنهسازی شبیه تولید اجرا کنید
❌ در غیر این صورت: UI ممکن است برای آزمایش عملکرد خود سرمایه گذاری زیادی کند تا دیرتر متوجه شود که بار بازگشتی بازگشتی (شمایه داده ای که UI باید با آن کار کند) بسیار متفاوت از آنچه انتظار می رود است.
✅ انجام دادن: در تستهای E2E که شامل یک باطن واقعی است و برای فراخوانیهای API به یک توکن کاربر معتبر متکی است، جداسازی آزمون در سطحی که کاربر ایجاد شده و در هر درخواستی به سیستم وارد شود، سودی ندارد. در عوض، قبل از شروع اجرای آزمایشها فقط یک بار وارد شوید (یعنی قبل از همه قلاب)، توکن را در برخی از حافظههای محلی ذخیره کنید و در درخواستها مجدداً از آن استفاده کنید. به نظر می رسد که این یکی از اصول اصلی آزمایش را نقض می کند - تست را بدون جفت شدن منابع مستقل نگه دارید. در حالی که این یک نگرانی معتبر است، عملکرد در تستهای E2E یک نگرانی کلیدی است و ایجاد 1-3 درخواست API قبل از شروع هر آزمایش ممکن است منجر به زمان اجرای وحشتناک شود. استفاده مجدد از اعتبارنامهها به این معنی نیست که آزمایشها باید روی سوابق کاربر یکسانی عمل کنند - اگر به سوابق کاربر (مثلاً سابقه پرداختهای کاربر آزمایشی) تکیه میکنند، مطمئن شوید که آن سوابق را به عنوان بخشی از آزمایش ایجاد کردهاید و از اشتراکگذاری وجود آنها با سایر آزمایشها اجتناب کنید. همچنین به یاد داشته باشید که Backend میتواند جعلی باشد - اگر آزمایشهای شما بر روی frontend متمرکز شدهاند، بهتر است آن را ایزوله کنید و API باطن را خرد کنید (نگاه کنید به bullet 3.6).
❌ در غیر این صورت: با توجه به 200 مورد تست و با فرض ورود = 100ms = 20 ثانیه فقط برای ورود مجدد و دوباره
✏ نمونه کد
let authenticationToken;
// قبل از اجرای همه آزمایشات اتفاق می افتد
before(() => {
cy.request('POST', 'http://localhost:3000/login', {
username: Cypress.env('username'),
password: Cypress.env('password'),
})
.its('body')
.then((responseFromLogin) => {
authenticationToken = responseFromLogin.token;
})
})
// قبل از هر آزمون اتفاق می افتد
beforeEach(setUser => {
cy.visit('/home', () => {
onBeforeLoad (win => {
win.localStorage.setItem('token', JSON.stringify(authenticationToken))
})
})
})
✅ انجام دادن: برای نظارت بر تولید و بررسی سلامت زمان توسعه، یک تست E2E را اجرا کنید که از همه/بیشتر صفحات سایت بازدید می کند و مطمئن می شود که هیچ کس خراب نمی شود. این نوع تست بازگشت سرمایه زیادی را به ارمغان می آورد زیرا نوشتن و نگهداری آن بسیار آسان است، اما می تواند هر نوع خرابی از جمله مشکلات عملکردی، شبکه و استقرار را تشخیص دهد. سایر سبکهای بررسی دود و سلامت عقل چندان قابل اعتماد و جامع نیستند - برخی از تیمهای عملیاتی فقط صفحه اصلی (تولید) را پینگ میکنند یا توسعهدهندگانی که تستهای ادغام زیادی را انجام میدهند که مشکلات بستهبندی و مرورگر را کشف نمیکنند. ناگفته نماند که تست دود جایگزین تست های عملکردی نمی شود، بلکه فقط به عنوان یک آشکارساز سریع دود عمل می کند.
❌ در غیر این صورت: ممکن است همه چیز عالی به نظر برسد، همه آزمایشها با موفقیت انجام میشوند، بررسی سلامت تولید نیز مثبت است، اما مؤلفه پرداخت مشکلی در بستهبندی داشت و فقط مسیر پرداخت / رندر نمیشود.
✏ نمونه کد
it("هنگام انجام تست smoke در تمام صفحات، باید همه آنها را با موفقیت بارگیری کنید", () => {
// نمونه ای با استفاده از Cypress است اما می توان آن را به راحتی اجرا کرد
// با استفاده از هر مجموعه E2E
cy.visit("https://mysite.com/home");
cy.contains("Home");
cy.visit("https://mysite.com/Login");
cy.contains("Login");
cy.visit("https://mysite.com/About");
cy.contains("About");
});
✅ انجام دادن: علاوه بر افزایش قابلیت اطمینان برنامه، آزمایشها فرصت جذاب دیگری را به روی میز میآورند - به عنوان مستندات برنامه زنده ارائه میشوند. از آنجایی که تستها ذاتاً با زبانی کمتر فنی و محصول/UX صحبت میکنند، با استفاده از ابزارهای مناسب میتوانند به عنوان یک مصنوع ارتباطی عمل کنند که تا حد زیادی همه همتایان - توسعهدهندگان و مشتریانشان را همسو میکند. برای مثال، برخی از چارچوبها امکان بیان جریان و انتظارات (یعنی طرح آزمایشها) را با استفاده از زبانی قابل خواندن برای انسان فراهم میکنند تا هر ذینفع، از جمله مدیران محصول، بتوانند آزمایشهایی را که بهتازگی به سند نیازمندیهای زنده تبدیل شدهاند، بخوانند، تأیید کنند و در آن همکاری کنند. این تکنیک همچنین به عنوان "آزمون پذیرش" نامیده می شود زیرا به مشتری اجازه می دهد معیارهای پذیرش خود را به زبان ساده تعریف کند. این هست BDD (behavior-driven testing) در خالص ترین شکل خود یکی از فریمورک های محبوبی که این امکان را فراهم می کند این است Cucumber which has a JavaScript flavor, مثال زیر را ببینید یک فرصت مشابه اما متفاوت دیگر, StoryBook, اجازه می دهد تا مؤلفه های رابط کاربری را به عنوان یک کاتالوگ گرافیکی در معرض دید قرار دهید، جایی که می توانید در حالت های مختلف هر مؤلفه قدم بزنید (مثلاً یک شبکه بدون فیلتر ارائه دهید، آن شبکه را با چندین ردیف یا با هیچ کدام و غیره ارائه کنید)، ببینید چگونه به نظر می رسد و چگونه است. برای راه اندازی آن حالت - این می تواند برای افراد محصول نیز جذاب باشد، اما بیشتر به عنوان سند زنده برای توسعه دهندگانی که آن اجزا را مصرف می کنند، عمل می کند.
❌ در غیر این صورت: پس از سرمایه گذاری منابع برتر در آزمایش، فقط حیف است که از این سرمایه گذاری استفاده نکنید و ارزش زیادی کسب نکنید
✏ نمونه کد
اینگونه می توان تست ها را با استفاده از cucumber توصیف کرد: زبانی ساده که به هر کسی اجازه می دهد درک کند و همکاری کند.
Feature: Twitter new tweet
I want to tweet something in Twitter
@focus
Scenario: Tweeting from the home page
Given I open Twitter home
Given I click on "New tweet" button
Given I type "Hello followers!" in the textbox
Given I click on "Submit" button
Then I see message "Tweet saved"
✅ انجام دادن: ابزارهای خودکار را برای گرفتن اسکرین شات های رابط کاربری در هنگام ارائه تغییرات تنظیم کنید و مشکلات بصری مانند همپوشانی یا شکستن محتوا را شناسایی کنید. این تضمین میکند که نه تنها دادههای مناسب آماده میشوند، بلکه کاربر نیز میتواند به راحتی آنها را ببیند. این تکنیک به طور گسترده مورد استفاده قرار نگرفته است، طرز فکر تست ما به سمت تستهای عملکردی گرایش دارد، اما تصاویر آن چیزی است که کاربر تجربه میکند و با انواع دستگاههای بسیار، نادیده گرفتن برخی از باگهای ناخوشایند UI بسیار آسان است. برخی از ابزارهای رایگان می توانند اصول اولیه را فراهم کنند - اسکرین شات ها را برای بازرسی چشم انسان تولید و ذخیره کنند. اگرچه این رویکرد ممکن است برای برنامههای کوچک کافی باشد، اما مانند هر آزمایش دستی دیگری که هر زمان که چیزی تغییر میکند به نیروی انسانی نیاز دارد، ناقص است. از سوی دیگر، به دلیل نداشتن تعریف واضح، تشخیص خودکار مسائل رابط کاربری بسیار چالش برانگیز است - اینجاست که میدان "رگرسیون بصری" به صدا در می آید و با مقایسه رابط کاربری قدیمی با آخرین تغییرات و تشخیص تفاوت ها، این معما را حل می کند. برخی از ابزارهای OSS/رایگان می توانند برخی از این قابلیت ها را ارائه دهند (e.g. wraith, PhantomCSS اما ممکن است زمان راه اندازی قابل توجهی را شارژ کند. خط تجاری ابزار (e.g. Applitools, Percy.io) با هموارسازی نصب و بستهبندی ویژگیهای پیشرفته مانند رابط کاربری مدیریت، هشدار، ضبط هوشمند با حذف نویز بصری (مانند تبلیغات، انیمیشنها) و حتی تجزیه و تحلیل علت اصلی تغییرات DOM/CSS که منجر به این مشکل شده است، یک قدم بیشتر است.
❌ در غیر این صورت: صفحه محتوایی که محتوای عالی را نمایش میدهد چقدر خوب است (100٪ تستها با موفقیت انجام شد)، فورا بارگیری میشود اما نیمی از منطقه محتوا پنهان است.?
✏ نمونه کد
# Add as many domains as necessary. Key will act as a label
domains:
english: "http://www.mysite.com"
# Type screen widths below, here are a couple of examples
screen_widths:
- 600
- 768
- 1024
- 1280
# Type page URL paths below, here are a couple of examples
paths:
about:
path: /about
selector: '.about'
subscribe:
selector: '.subscribe'
path: /subscribe
import * as todoPage from "../page-objects/todo-page";
describe("visual validation", () => {
before(() => todoPage.navigate());
beforeEach(() => cy.eyesOpen({ appName: "TAU TodoMVC" }));
afterEach(() => cy.eyesClose());
it("should look good", () => {
cy.eyesCheckWindow("empty todo list");
todoPage.addTodo("Clean room");
todoPage.addTodo("Learn javascript");
cy.eyesCheckWindow("two todos");
todoPage.toggleTodo(0);
cy.eyesCheckWindow("mark as completed");
});
});
✅ انجام دادن: هدف از تست گرفتن اعتماد به نفس کافی برای حرکت سریع است، بدیهی است که هر چه کد بیشتر تست شود، تیم می تواند اعتماد بیشتری داشته باشد. پوشش معیاری است از تعداد خطوط کد (و شاخه ها، دستورات و غیره) که توسط آزمایش ها به دست می آیند. پس چقدر کافی است؟ 10-30٪ واضح است که برای درک درستی ساخت بسیار کم است، از طرف دیگر 100٪ بسیار گران است و ممکن است تمرکز شما را از مسیرهای مهم به گوشه های عجیب و غریب کد تغییر دهد. پاسخ طولانی این است که به عوامل زیادی مانند نوع برنامه بستگی دارد - اگر شما در حال ساخت نسل بعدی ایرباس A380 هستید، 100٪ ضروری است، برای یک وب سایت تصاویر کارتونی 50٪ ممکن است خیلی زیاد باشد. اگرچه اکثر علاقه مندان به تست ادعا می کنند که آستانه پوشش مناسب متنی است، اکثر آنها عدد 80٪ را نیز به عنوان یک قانون ذکر می کنند. (Fowler: “in the upper 80s or 90s”) که احتمالاً باید اکثر برنامه ها را برآورده کند.
نکات پیاده سازی: ممکن است بخواهید ادغام پیوسته (CI) خود را برای داشتن آستانه پوشش پیکربندی کنید. (Jest link) و ساختنی را که مطابق با این استاندارد نیست متوقف کنید (همچنین می توان آستانه را برای هر جزء پیکربندی کرد، به مثال کد زیر مراجعه کنید). علاوه بر این، تشخیص کاهش پوشش ساخت را در نظر بگیرید (زمانی که یک کد جدید متعهد شده دارای پوشش کمتری است)— این باعث میشود توسعهدهندگان مقدار کد تست شده را افزایش دهند یا حداقل حفظ کنند. همه آنچه گفته شد، پوشش تنها یک معیار است، یک معیار مبتنی بر کمی، که برای نشان دادن استحکام آزمایش شما کافی نیست. و همچنین همانطور که در گلوله های بعدی نشان داده شده است می توان آن را فریب داد
❌ در غیر این صورت: اعتماد به نفس و اعداد دست به دست هم می دهند، بدون اینکه واقعاً بدانید که بیشتر سیستم را آزمایش کرده اید - همچنین مقداری ترس وجود خواهد داشت و ترس سرعت شما را کاهش می دهد.
✏ نمونه کد
✅ انجام دادن: برخی از مسائل دقیقاً زیر رادار پنهان می شوند و با استفاده از ابزارهای سنتی پیدا کردن آنها واقعاً سخت است. اینها واقعاً باگ نیستند، بلکه بیشتر رفتارهای شگفت انگیز برنامه هستند که ممکن است تأثیر شدیدی داشته باشند. به عنوان مثال، اغلب برخی از مناطق کد هرگز یا به ندرت فراخوانی نمی شوند - شما فکر می کنید که کلاس "PricingCalculator" همیشه قیمت محصول را تعیین می کند، اما به نظر می رسد که در واقع هرگز فراخوانی نمی شود، اگرچه ما 10000 محصول در DB داریم و تعداد زیادی فروش... پوشش کد گزارشها به شما کمک میکنند متوجه شوید که آیا برنامه بهگونهای که شما فکر میکنید رفتار میکند یا خیر. به غیر از آن، همچنین میتواند مشخص کند که کدام نوع کد آزمایش نشده است - با اطلاع از اینکه 80 درصد کد آزمایش شده است، نمیگوید که آیا قسمتهای حیاتی پوشش داده شدهاند یا خیر. ایجاد گزارش آسان است - فقط برنامه خود را در مرحله تولید یا در حین آزمایش با ردیابی پوشش اجرا کنید و سپس گزارشهای رنگارنگی را مشاهده کنید که نشان میدهد تعداد دفعات فراخوانی هر ناحیه کد مشخص میشود. اگر وقت خود را صرف نگاهی اجمالی به این داده ها کنید - ممکن است چند اشتباه پیدا کنید
❌ در غیر این صورت: اگر نمیدانید کدام بخش از کدتان آزمایش نشده است، نمیدانید این مشکلات از کجا میآیند.
✏ نمونه کد
بر اساس یک سناریوی واقعی که در آن ما استفاده از برنامه خود را در QA ردیابی کردیم و الگوهای ورود جالبی را پیدا کردیم (نکته: میزان خرابی های ورود به سیستم نامتناسب است، چیزی به وضوح اشتباه است. در نهایت مشخص شد که برخی از باگ های frontend مدام به سیستم وارد می شوند. API ورود باطن)
✅ انجام دادن: معیار پوشش سنتی اغلب دروغ می گوید: ممکن است پوشش 100٪ کد را به شما نشان دهد، اما هیچ یک از توابع شما، حتی یک مورد، پاسخ مناسب را نشان نمی دهد. چطور؟ آن را به سادگی اندازه گیری می کند که از کدام خطوط کد تست بازدید کرده است، اما بررسی نمی کند که آیا آزمون ها واقعاً چیزی را آزمایش کرده اند - برای پاسخ درست اظهار شده است. مانند کسی که برای تجارت سفر می کند و مهر پاسپورت خود را نشان می دهد - این نشان دهنده انجام هیچ کاری نیست، فقط اینکه او از تعداد کمی از فرودگاه ها و هتل ها بازدید کرده است.
آزمایش مبتنی بر جهش با اندازهگیری مقدار کدی که واقعاً تست شده است و نه فقط بازدید شده، به شما کمک میکند.. Stryker یک کتابخانه جاوا اسکریپت برای آزمایش جهش است و پیاده سازی آن واقعاً منظم است:
(1) به عمد کد را تغییر می دهد و "اشکالات گیاهی" را ایجاد می کند. برای مثال کد newOrder.price===0 به newOrder.price!=0 تبدیل می شود. این "اشکال" جهش نامیده می شود
(2) تست ها را اجرا می کند، اگر همه موفق شوند، مشکل داریم - آزمایش ها به هدف خود یعنی کشف باگ ها عمل نکردند، جهش ها به اصطلاح زنده می مانند. اگر آزمایش ها شکست خوردند، عالی است، جهش ها کشته شدند.
دانستن اینکه همه یا بیشتر جهشها کشته شدهاند، اطمینان بسیار بیشتری نسبت به پوشش سنتی میدهد و زمان راهاندازی مشابه است.
❌ در غیر این صورت: شما فریب خواهید خورد که فکر کنید پوشش 85٪ به این معنی است که تست شما اشکالات را در 85٪ از کد شما تشخیص می دهد.
✏ نمونه کد
function addNewOrder(newOrder) {
logger.log(`Adding new order ${newOrder}`);
DB.save(newOrder);
Mailer.sendMail(newOrder.assignee, `A new order was places ${newOrder}`);
return { approved: true };
}
it("addNewOrder را تست کنید، از چنین نام های آزمایشی استفاده نکنید", () => {
addNewOrder({ assignee: "John@mailer.com", price: 120 });
}); //پوشش 100٪ کد را فعال می کند، اما چیزی را بررسی نمی کند
✅ انجام دادن: مجموعه ای از پلاگین های ESLint به طور خاص برای بازرسی الگوهای کد تست و کشف مشکلات ساخته شده است. مثلا, eslint-plugin-mocha هنگامی که یک تست در سطح جهانی نوشته می شود (نه یک عبارت describe()) یا زمانی که تست ها پرش کرد که ممکن است به این باور نادرست منجر شود که همه آزمون ها قبول شده اند. به همین ترتیب، eslint-plugin-jest برای مثال می تواند هشدار دهد زمانی که یک آزمون اصلاً ادعایی ندارد (چک نکردن چیزی)
❌ در غیر این صورت: مشاهده 90% پوشش کد و 100% تستهای سبز باعث میشود چهره شما لبخند بزرگی بزند فقط تا زمانی که متوجه شوید که بسیاری از تستها برای هیچ چیزی ادعا نمیکنند و بسیاری از مجموعههای آزمایشی صرفاً نادیده گرفته شدهاند. امیدوارم بر اساس این مشاهده نادرست چیزی را مستقر نکرده باشید
✏ نمونه کد
describe("توضیحات خیلی کوتاه", () => {
const userToken = userService.getDefaultToken() // *error:no-setup-in-describe، به جای آن از هوک ها (به مقدار کم) استفاده کنید
it("کمی توضیحات", () => {});//* error: valid-test-description. باید شامل کلمه "Should" + حداقل 5 کلمه باشد
});
it.skip("نام تست", () => {// *error:no-skipped-tests, error:error:no-global-tests. تست ها را فقط در قسمت توصیف یا مجموعه قرار دهید
expect("somevalue"); // error:no-assert
});
it("نام تست", () => {// *error:no-identical-title. عناوین منحصر به فرد را به آزمون ها اختصاص دهید
});
✅ انجام دادن: لینترها یک ناهار رایگان هستند، با راه اندازی 5 دقیقه ای به صورت رایگان یک خلبان خودکار از کد شما محافظت می کند و هنگام تایپ مشکل مهمی را متوجه می شوید. روزهایی که پرزها در مورد لوازم آرایشی بودند (نه نیم کولن!) گذشته است. امروزه، Linters می تواند مشکلات شدیدی مانند خطاهایی که به درستی پرتاب نمی شوند و اطلاعات از دست می دهند، بگیرند. علاوه بر مجموعه قوانین اساسی شما (مانند ESLint standard یا Airbnb style), شامل برخی از Linters تخصصی مانند eslint-plugin-chai-expect که می تواند تست ها را بدون ادعا کشف کند, eslint-plugin-promise می تواند وعده ها را بدون هیچ مشکلی کشف کند (کد شما هرگز ادامه نخواهد داشت), eslint-plugin-security که می تواند عبارات regex مشتاق را کشف کند که ممکن است برای حملات DOS استفاده شود، و eslint-plugin-you-dont-need-lodash-underscore زمانی که کد از روشهای کتابخانه ابزاری استفاده میکند که بخشی از روشهای هسته V8 مانند Lodash هستند، میتواند هشدار دهد._map(…)
❌ **در غیر این صورت:**یک روز بارانی را در نظر بگیرید که در آن تولید شما مدام خراب میشود اما گزارشها ردیابی پشته خطا را نشان نمیدهند. چی شد؟ کد شما به اشتباه یک شی بدون خطا پرتاب کرد و رد پشته از بین رفت، دلیل خوبی برای ضربه زدن سر خود به دیوار آجری است. یک راهاندازی 5 دقیقهای لینتر میتواند این اشتباه تایپی را شناسایی کرده و در روز شما صرفهجویی کند
✏ نمونه کد
✅ انجام دادن: آیا از یک CI با بازرسی های کیفیت براق مانند آزمایش، پرده زدن، بررسی آسیب پذیری ها و غیره استفاده می کنید؟ به توسعه دهندگان کمک کنید تا این خط لوله را نیز به صورت محلی اجرا کنند تا بازخورد فوری دریافت کنند و زمان را کوتاه کنند حلقه بازخورد. چرا؟ یک فرآیند تست کارآمد حلقه های تکراری و متعددی را تشکیل می دهد: (1) آزمایشات -> (2) بازخورد -> (3) بازساز. هرچه بازخورد سریعتر باشد، توسعه دهنده می تواند تکرارهای بهبود بیشتری را در هر ماژول انجام دهد و نتایج را کامل کند. در تلنگر، زمانی که بازخورد دیر می رسد، تکرارهای بهبود کمتری می تواند در یک روز بسته بندی شود، تیم ممکن است در حال حاضر به سمت موضوع/وظیفه/ماژول دیگری حرکت کند و ممکن است برای اصلاح آن ماژول آماده نباشد.
در عمل، برخی از فروشندگان CI (مثال: CLI محلی CircleCI) اجازه می دهد خط لوله را به صورت محلی اجرا کند. برخی از ابزارهای تجاری مانند wallaby بینش های بسیار ارزشمند و آزمایشی را ارائه می دهد به عنوان نمونه اولیه توسعه دهنده (بدون وابستگی). از طرف دیگر، میتوانید اسکریپت npm را به package.json اضافه کنید که تمام دستورات کیفیت (مانند تست، پرز، آسیبپذیریها) را اجرا میکند - از ابزارهایی مانند استفاده کنید. همزمان برای موازی سازی و کد خروج غیر صفر در صورت خرابی یکی از ابزارها. اکنون توسعهدهنده باید فقط یک دستور را فراخوانی کند - به عنوان مثال. "کیفیت اجرای npm" - برای دریافت بازخورد فوری. همچنین در صورت عدم موفقیت بررسی کیفیت با استفاده از githook، یک commit را لغو کنید (هاسکی می تواند کمک کند)
❌ در غیر این صورت: هنگامی که نتایج کیفی یک روز پس از کد به دست میآیند، آزمایش به بخشی روان از توسعه تبدیل نمیشود، بلکه به یک مصنوع رسمی پس از واقعیت تبدیل میشود.
✏ نمونه های کد
👏 درست انجام دادن مثال: اسکریپتهای npm که بازرسی کیفیت کد را انجام میدهند، همه بهصورت موازی اجرا میشوند یا زمانی که یک توسعهدهنده تلاش میکند کد جدیدی را وارد کند.
{
"scripts": {
"inspect:sanity-testing": "mocha **/**--test.js --grep \"sanity\"",
"inspect:lint": "eslint .",
"inspect:vulnerabilities": "npm audit",
"inspect:license": "license-checker --failOn GPLv2",
"inspect:complexity": "plato .",
"inspect:all": "concurrently -c \"bgBlue.bold,bgMagenta.bold,yellow\" \"npm:inspect:quick-testing\" \"npm:inspect:lint\" \"npm:inspect:vulnerabilities\" \"npm:inspect:license\""
},
"husky": {
"hooks": {
"precommit": "npm run inspect:all",
"prepush": "npm run inspect:all"
}
}
}
✅ انجام دادن: آزمایش انتها به انتها (e2e) چالش اصلی هر خط لوله CI است - ایجاد یک آینه تولید زودگذر یکسان در حال پرواز با تمام خدمات ابری مرتبط می تواند خسته کننده و گران باشد. یافتن بهترین سازش بازی شماست: Docker-compose اجازه می دهد تا با استفاده از یک فایل متنی ساده، محیط ایزوله شده را با کانتینرهای یکسان ایجاد کنید، اما فناوری پشتیبان (به عنوان مثال شبکه، مدل استقرار) با تولیدات دنیای واقعی متفاوت است. می توانید آن را با آن ترکیب کنید ‘AWS محلی’ برای کار با تعدادی از خدمات واقعی AWS. اگه رفتی serverless فریمورک های متعدد مانند بدون سرور و AWS SAM فراخوانی محلی کد FaaS را امکان پذیر می کند.
اکوسیستم عظیم Kubernetes هنوز یک ابزار مناسب استاندارد برای آینهکاری محلی و CI رسمی نکرده است، اگرچه ابزارهای جدید زیادی به طور مکرر راهاندازی میشوند. یک رویکرد اجرای «کوبرنتهای کوچکشده» با استفاده از ابزارهایی مانند Minikube and MicroK8s که شبیه چیزهای واقعی هستند فقط با سربار کمتری عرضه می شوند. روش دیگر آزمایش بر روی یک "Kubernetes واقعی" از راه دور، برخی از ارائه دهندگان CI است (e.g. Codefresh) دارای ادغام بومی با محیط Kubernetes است و اجرای خط لوله CI را بر روی چیز واقعی آسان می کند، دیگران اجازه می دهند اسکریپت سفارشی در برابر Kubernetes از راه دور انجام شود.
❌ **در غیر این صورت:**استفاده از فناوری های مختلف برای تولید و آزمایش مستلزم حفظ دو مدل استقرار است و توسعه دهندگان و تیم عملیات را از هم جدا نگه می دارد.
✏ نمونه کد
👏 مثال: یک خط لوله CI که خوشه Kubernetes را در حال پرواز تولید می کند (اعتبار: پویا-محیط های Kubernetes)
deploy:
stage: deploy
image: registry.gitlab.com/gitlab-examples/kubernetes-deploy
script:
- ./configureCluster.sh $KUBE_CA_PEM_FILE $KUBE_URL $KUBE_TOKEN
- kubectl create ns $NAMESPACE
- kubectl create secret -n $NAMESPACE docker-registry gitlab-registry --docker-server="$CI_REGISTRY" --docker-username="$CI_REGISTRY_USER" --docker-password="$CI_REGISTRY_PASSWORD" --docker-email="$GITLAB_USER_EMAIL"
- mkdir .generated
- echo "$CI_BUILD_REF_NAME-$CI_BUILD_REF"
- sed -e "s/TAG/$CI_BUILD_REF_NAME-$CI_BUILD_REF/g" templates/deals.yaml | tee ".generated/deals.yaml"
- kubectl apply --namespace $NAMESPACE -f .generated/deals.yaml
- kubectl apply --namespace $NAMESPACE -f templates/my-sock-shop.yaml
environment:
name: test-for-ci
✅ انجام دادن: هنگامی که آزمایش به درستی انجام شود، دوست شما 24 ساعته و تقریباً فوری بازخورد ارائه می کند. در عمل، اجرای آزمایش 500 واحد محدود به CPU بر روی یک رشته ممکن است خیلی طول بکشد. خوشبختانه، دونده های آزمایشی مدرن و پلت فرم های CI (مانند Jest, AVA و Mocha extensions) می تواند آزمون را در چندین فرآیند موازی کند و به بهبود قابل توجهی در زمان بازخورد دست یابد. برخی از فروشندگان CI نیز آزمایشات را در کانتینرها موازی می کنند (!) که حلقه بازخورد را حتی بیشتر کوتاه می کند. چه به صورت محلی بر روی چندین فرآیند، یا از طریق برخی CLI ابری با استفاده از چندین ماشین - تقاضای موازی کردن تستها را مستقل نگه میدارد زیرا هر کدام ممکن است در فرآیندهای مختلف اجرا شوند.
❌ در غیر این صورت: دریافت نتایج آزمون 1 ساعت پس از فشار دادن کد جدید، همانطور که قبلاً ویژگیهای بعدی را کدنویسی کردهاید، یک دستور العمل عالی برای کاهش مرتبط کردن تست است.
✏ نمونه کد
👏 انجام درست مثال: موازی موکا و جست به لطف آزمایش موازی سازی به راحتی از موکای سنتی پیشی می گیرند. (Credit: JavaScript Test-Runners Benchmark)
✅ انجام دادن: مسائل مربوط به مجوز و سرقت ادبی احتمالاً دغدغه اصلی شما در حال حاضر نیست، اما چرا این کادر را در 10 دقیقه تیک ندهید؟ یک دسته از بسته های npm مانند بررسی مجوز و چک سرقت ادبی (تجاری با طرح رایگان) را می توان به راحتی در خط لوله CI خود قرار داد و غم هایی مانند وابستگی ها را با مجوزهای محدود یا کدی که از Stack Overflow کپی شده است و ظاهراً برخی از حق چاپ را نقض می کند بررسی کرد.
❌ در غیر این صورت: به طور ناخواسته، توسعهدهندگان ممکن است از بستههایی با مجوزهای نامناسب استفاده کنند یا کد تجاری را کپی پیست کنند و با مشکلات قانونی مواجه شوند.
✏ نمونه کد
# لایسنس چک را در محیط CI خود یا به صورت محلی نصب کنید
npm install -g license-checker
# از آن بخواهید که تمام مجوزها را اسکن کند و اگر مجوز غیرمجاز پیدا کرد با کد خروجی غیر از 0 شکست بخورد. سیستم CI باید این شکست را بگیرد و ساخت را متوقف کند
license-checker --summary --failOn BSD
✅ انجام دادن: حتی معتبرترین وابستگی ها مانند Express آسیب پذیری های شناخته شده ای دارند. این می تواند به راحتی با استفاده از ابزارهای جامعه مانند npm audit, یا ابزارهای تجاری مانند snyk (نسخه انجمن رایگان را نیز ارائه دهید). هر دو را می توان از CI شما در هر ساختنی فراخوانی کرد
❌ در غیر این صورت: پاک نگه داشتن کد خود از آسیبپذیریها بدون ابزار اختصاصی، مستلزم این است که دائماً انتشارات آنلاین در مورد تهدیدات جدید را دنبال کنید. کاملا خسته کننده
✅ انجام دادن: Yarn و npm آخرین معرفی package-lock.json یک چالش جدی را معرفی کرد (جاده جهنم با نیت خوب هموار شده است)——به طور پیش فرض اکنون بسته ها دیگر به روز رسانی نمی شوند. حتی تیمی که بسیاری از استقرارهای جدید را با "npm install" و "npm update" اجرا می کند، هیچ به روز رسانی جدیدی دریافت نخواهد کرد. این منجر به نسخههای بستههای وابسته به پایینتر و در بدترین حالت به کدهای آسیبپذیر میشود. اکنون تیم ها برای به روز رسانی دستی package.json یا استفاده از ابزارها به حسن نیت و حافظه توسعه دهندگان متکی هستند مانند ncu به صورت دستی یک راه قابل اطمینان تر می تواند خودکار کردن فرآیند دریافت قابل اعتمادترین نسخه های وابستگی باشد، اگرچه هیچ راه حل گلوله نقره ای وجود ندارد، اما دو راه اتوماسیون ممکن وجود دارد:
(1) CI میتواند در ساختهایی که وابستگیهای منسوخ دارند با استفاده از ابزارهایی مانند ‘npm outdated’ یا ‘npm-check-updates (ncu)’ . انجام این کار توسعه دهندگان را وادار می کند تا وابستگی ها را به روز کنند.
(2) از ابزارهای تجاری استفاده کنید که کد را اسکن می کنند و به طور خودکار درخواست های کشش را با وابستگی های به روز ارسال می کنند. یک سوال جالب باقی مانده این است که سیاست بهروزرسانی وابستگی چگونه باید باشد— بهروزرسانی در هر وصله سربار زیادی ایجاد میکند، بهروزرسانی درست زمانی که یک اصلی منتشر میشود ممکن است به یک نسخه ناپایدار اشاره کند (بسیاری از بستهها در همان روزهای اول پس از انتشار آسیبپذیر هستند., را ببینید eslint-scope حادثه).
یک خطمشی بهروزرسانی کارآمد ممکن است اجازه دهد تا مقداری «دوره واگذاری» وجود داشته باشد - اجازه دهید کد برای مدتی از آخرین @ و نسخهها عقب بماند، قبل از اینکه نسخه محلی را منسوخ در نظر بگیرید (به عنوان مثال نسخه محلی 1.3.1 و نسخه مخزن 1.3.8 است).
❌ در غیر این صورت: تولید شما بسته هایی را اجرا می کند که به صراحت توسط نویسنده آنها به عنوان خطرناک برچسب گذاری شده است
✏ نمونه کد
👏 مقال: ncu می تواند به صورت دستی یا در یک خط لوله CI برای تشخیص میزان عقب ماندگی کد از آخرین نسخه ها استفاده شود
✅ انجام دادن: این پست بر روی توصیههای آزمایشی متمرکز شده است، یا حداقل میتوان آن را با Node JS مثال زد. با این حال، این گلوله چند نکته غیر مرتبط با Node را که به خوبی شناخته شده هستند، گروه بندی می کند
- از یک نحو اعلانی استفاده کنید. این تنها گزینه برای اکثر فروشندگان است اما نسخه های قدیمی Jenkins امکان استفاده از کد یا UI را می دهد
- فروشنده ای را انتخاب کنید که از پشتیبانی Docker بومی برخوردار باشد
- زود شکست بخورید، ابتدا سریع ترین تست های خود را اجرا کنید. یک مرحله / نقطه عطف "تست دود" ایجاد کنید که چندین بازرسی سریع را گروه بندی می کند (مانند پرده زدن، تست های واحد) و بازخورد سریع را به committer کد ارائه می دهد.
- بررسی تمام مصنوعات ساختنی از جمله گزارشهای آزمایش، گزارشهای پوشش، گزارشهای جهش، گزارشها و غیره را آسان کنید.
- چندین خط لوله/شغل برای هر رویداد ایجاد کنید، از مراحل بین آنها دوباره استفاده کنید. به عنوان مثال، یک کار را برای commit های شاخه ویژگی و یک کار دیگر را برای PR اصلی پیکربندی کنید. به هر منطق اجازه استفاده مجدد را با استفاده از مراحل مشترک بدهید (اکثر فروشندگان مکانیزمی برای استفاده مجدد از کد ارائه می دهند)
- هرگز اسرار را در یک اعلامیه شغلی قرار ندهید، آنها را از یک فروشگاه مخفی یا از پیکربندی شغل بگیرید.
- به صراحت نسخه را در یک نسخه منتشر کنید یا حداقل اطمینان حاصل کنید که توسعه دهنده این کار را انجام داده است
- فقط یک بار بسازید و تمام بازرسیها را روی یک مصنوع ساخت (مثلاً تصویر داکر) انجام دهید.
- در یک محیط زودگذر که حالت رانش بین ساختها را ندارد، تست کنید. ذخیره node_modules ممکن است تنها استثنا باشد
❌ در غیر این صورت: شما سالهای خرد را از دست خواهید داد
✅ انجام دادن: بررسی کیفیت در مورد سرندیپیتی است، هرچه زمینه بیشتری را پوشش دهید، در تشخیص زودهنگام مشکلات شانس بیشتری خواهید داشت. هنگام توسعه بستههای قابل استفاده مجدد یا اجرای یک تولید چند مشتری با پیکربندیهای مختلف و نسخههای Node، CI باید خط لوله آزمایشها را روی همه جایگشتهای پیکربندیها اجرا کند. به عنوان مثال، با فرض اینکه ما از MySQL برای برخی از مشتریان و از Postgres برای برخی دیگر استفاده میکنیم - برخی از فروشندگان CI از ویژگی به نام «Matrix» پشتیبانی میکنند که امکان اجرای مجموعه آزمایشی را در برابر همه جایگشتهای MySQL، Postgres و چندین نسخه Node مانند 8، 9 و 10 میدهد. این کار فقط با استفاده از پیکربندی بدون هیچ تلاش اضافی انجام می شود (با فرض اینکه تست یا هر گونه بررسی کیفیت دیگری داشته باشید). سایر CI که از Matrix پشتیبانی نمی کنند ممکن است افزونه ها یا ترفندهایی برای اجازه دادن به آن داشته باشند
❌ در غیر این صورت: بنابراین، پس از انجام این همه کار سخت در تست نوشتن، تنها به دلیل مشکلات پیکربندی، اجازه میدهیم باگها پنهان شوند.?
✏ نمونه کد
زبان: node_js
node_js:
- "7"
- "6"
- "5"
- "4"
install:
- npm install
script:
- npm run test
Role: Writer
About: I'm an independent consultant who works with Fortune 500 companies and garage startups on polishing their JS & Node.js applications. More than any other topic I'm fascinated by and aims to master the art of testing. I'm also the author of Node.js Best Practices
📗 Online Course: Liked this guide and wish to take your testing skills to the extreme? Consider visiting my comprehensive course Testing Node.js & JavaScript From A To Z
Follow:
Role: Tech reviewer and advisor
Took care to revise, improve, lint and polish all the texts
About: full-stack web engineer, Node.js & GraphQL enthusiast
Role: Concept, design and great advice
About: A savvy frontend developer, CSS expert and emojis freak
Role: Helps keep this project running, and reviews security related practices
About: Loves working on Node.js projects and web application security.