نحوه مدیریت عوارض جانبی در جست – راهنمای تمسخر موثر
تست واحد یک موضوع اصلی برای هر توسعه دهنده است. این یک تمرین اساسی در ساخت برنامه های نرم افزاری است. تست واحد به شما کمک می کند تا اشکالات را زودتر شناسایی کنید و نگهداری کد را آسان تر می کند. با جداسازی و آزمایش تک واحدها یا اجزای برنامه خود، می توانید از قابلیت اطمینان و عملکرد آنها اطمینان حاصل کنید.
هنگام استفاده از تست واحد، باید روی منطق اصلی یک مؤلفه بدون تأثیر بر وابستگیهای خارجی یا ایجاد عوارض جانبی تمرکز کنید - تغییرات ناخواستهای که خارج از محدوده یک تابع رخ میدهند، مانند پرس و جوهای پایگاه داده یا درخواستهای شبکه.
Jest یک چارچوب تست محبوب است که قابلیت های قدرتمندی را برای کمک به تست موثر ارائه می دهد. Mocking in Jest به شما کمک می کند وابستگی های خارجی را آزمایش و مدیریت کنید و عوارض جانبی را کنترل کنید.
در این راهنما، شما در مورد موارد ضروری تست واحد، تمرکز بر جست و جو میآموزید. چه تازه شروع کرده باشید و چه به دنبال ارتقای استراتژی تست خود باشید، این راهنما شما را با دانش نوشتن تست های موثر و کارآمد مجهز می کند.
در اینجا چیزی است که ما پوشش خواهیم داد:
مورد استفاده: Login Express Controller
تست واحد چیست؟
تست واحد یک تکنیک تست نرم افزاری است که برای آزمایش یک جزء از برنامه شما به صورت مجزا استفاده می شود. این جزء ممکن است یک کلاس، یک متد یا یک ماژول باشد.
چرا باید از تست واحد استفاده کنید
شما قادر خواهید بود اشکالات را زودتر تشخیص دهید، این به شما کمک می کند تشخیص دهید که آیا یک جزء مطابق انتظار عمل می کند یا خیر.
به شما این امکان را می دهد که مؤلفه خود را با خیال راحت تغییر دهید. اگر کامپوننت خود را بهروزرسانی کنید و به اشتباه چیزی را که نباید اضافه یا تغییر دهید، در صورتی که این تغییرات باگ جدیدی ایجاد کند، آزمایش ناموفق خواهد بود.
این می تواند به عنوان سندی عمل کند که نشان می دهد واحدهای جداگانه برنامه شما چگونه کار می کنند.
شما را به نوشتن کدهای پاک تر تشویق می کند. هرچه کامپوننت شما تمیزتر باشد، تست شما آسانتر و ساده تر خواهد بود.
این به شما کمک می کند تا به راحتی بخش های مختلف برنامه خود را ادغام کنید، زیرا مطمئن خواهید بود که هر جزء به درستی کار می کند.
در دراز مدت، می توانید برنامه خود را سریعتر حفظ کنید.
اجازه دهید به طور عمیق به چند کاربرد عملی بپردازیم:
فرض کنید یک تابع ضرب دارید که باید دو آرگومان بگیرد و نتیجه را برگرداند.
این هم کد:
function multiply ( a,b ) { return a*b } export default multiply
توجه : برای استفاده از Jest با ماژولهای Node.js ECMAScript، این مقاله را برای پیکربندی تحلیل کنید.
پس چگونه می توانید این تابع را با استفاده از Jest آزمایش کنید؟
پوشه __tests__ را در پوشه root ایجاد کنید.
فایل multiply.test.js را در داخل __tests__ ایجاد کنید.
توجه داشته باشید که هر فایلی که به .test.js ختم شود توسط Jest اجرا خواهد شد.
نوشتن تست های خود را با فراخوانی متد it("",()=>{})
Jest شروع کنید.
بیایید بفهمیم که ` it("",()=>{})
` چه کاری انجام می دهد:
متد it
یک تابع Jest است که برای آزمایش برخی رفتارها در عملکرد شما استفاده می شود.
اولین آرگومان باید نام آزمون باشد که می تواند یک متن ادعایی برای آنچه از این آزمون انتظار دارید باشد.
به عنوان مثال، اگر باید آزمایش کنید که آیا تابع multiply
با استفاده از آرگومانها نتیجه درست را برمیگرداند یا خیر، میتوانید it("should return the multiplication of inputs of type number",()=>{})
.
آرگومان دوم تابعی برای منطق تست شما است. هنگامی که تست خود را اجرا می کنید، فراخوانی می شود .
برای نوشتن موثر آزمونهای خود، باید الگوی AAA (Arrange-Act-Assert) را اعمال کنید.
ترتیب : داده ها را تنظیم کنید یا وابستگی هایی را که در این تست استفاده می کنید پیکربندی کنید.
Act: تابعی را که در حال آزمایش هستید فراخوانی کنید.
ادعا کنید: انتظارات خود را بنویسید - انتظار دارید عملکردی که آزمایش می کنید چگونه رفتار کند. برای ادعا، شما همیشه از روش expect
جست استفاده خواهید کرد.
هر عبارت it("",()=>{})
را به عنوان سناریویی متفاوت از عملکرد خود در نظر بگیرید.
در اینجا یک مثال است:
import multiply from './../multiply.js' it( "should return the multiplication of inputs of type number" , () => { // Arrange const testArg1 = 5 ; const testArg2 = 2 ; // Act const result = multiply(testArg1, testArg2); // Assert expect(result).toBe( 10 ); }); it( "should returns NaN if no arguments are passed" , () => { // Arrange // Act const result = multiply(); // Assert expect(result).toBeNaN(); }); it( "should returns NaN if only one argument is passed" , () => { // Arrange const arg = 5 ; // Act const result = multiply(arg); // Assert expect(result).toBeNaN(); }); it( "should returns Zero if one of the arguments is empty string" , () => { // Arrange const testArg1 = "" ; const testArg2 = 5 ; // Act const result = multiply(testArg1, testArg2); // Assert expect(result).toBe( 0 ); });
این تست ها از جمله تست هایی هستند که می توانید به فایل خود اضافه کنید. بسته به سناریوهای مختلف عملکردی که آزمایش می کنید، می توانید تست های بیشتری اضافه کنید یا برخی را حذف کنید.
وابستگی های خارجی چیست؟
وابستگی های خارجی ماژول ها یا توابعی هستند که کد شما بر آنها تکیه دارد و خارج از پایگاه کد شما منشأ می گیرد. اینها می توانند شامل کتابخانه ها، API ها، پایگاه های داده، توابع یا هر سرویسی باشد که برنامه شما با آن تعامل دارد.
آزمایش با وابستگی های خارجی می تواند چالش برانگیز باشد زیرا:
آنها می توانند تست ها را به دلیل تاخیر در شبکه یا پردازش کند کنند.
ممکن است در طول آزمایش در دسترس نباشند، که به نوبه خود باعث خرابی می شود.
همانطور که در تابع زیر نشان داده شده است، اگر تابع شما تابع دیگری را فراخوانی کند چه؟ اکثر توابعی که روزانه می نویسید در واقع توابع دیگر را فراخوانی می کنند.
یعنی:
function processNumbers ( numbers, callback ) { // numbers: array // callback: function return numbers.map(callback); } export default processNumbers;
هنگام اعمال تست واحد، واحدها باید به صورت مجزا آزمایش شوند. تابع processNumbers
به یک callback
دیگر بستگی دارد.
پس در این مورد چه باید کرد؟ تمسخر راه حل است و بعداً در بخش دیگری در مورد آن صحبت خواهیم کرد.
عوارض جانبی چیست؟
عوارض جانبی زمانی اتفاق میافتد که یک تابع حالتی خارج از محدوده خود را تغییر میدهد یا جدا از برگرداندن یک مقدار، تعاملات قابل مشاهده با دنیای خارج دارد.
به عنوان مثال می توان به تغییر یک متغیر جهانی، تغییر سیستم فایل یا ارسال درخواست HTTP اشاره کرد.
عوارض جانبی میتواند آزمایشها را غیرقابل پیشبینی و مدیریت آنها دشوار کند، زیرا:
ممکن است با سیستم های دیگر تعامل داشته باشد و باعث تغییر حالت های خارجی شود.
اگر به درستی ایزوله نشود، می تواند منجر به تست های پوسته پوسته شود.
در اینجا یک مثال است که یک کاربر را از پایگاه داده با استفاده از id
خود باز می گرداند:
async function getUserFromDatabase ( userId ) { // Simulates fetching from a database return { id : userId, name : 'John' }; } export {getUserFromDatabase}
در اینجا تابع دیگری است که از getUserFromDatabase
در کد بالا استفاده می کند:
async function getProfile ( userId ) { return await getUserFromDatabase(userId); } export default getProfile
هنگام آزمایش این تابع، در واقع نباید یک درخواست واقعی ارسال کنید، تنها چیزی که نیاز دارید این است که رفتار تابع getProfile
را بدون ضربه زدن به هیچ سیستم خارجی آزمایش کنید.
برای حل این وضعیت می توانید از تمسخر نیز استفاده کنید.
تمسخر چیست؟
تمسخر در مورد شبیه سازی است—شما باید تابعی را که در حال آزمایش آن هستید جدا کنید. اگر عملکرد به هر وابستگی خارجی متکی است یا ممکن است عوارض جانبی ایجاد کند، باید رفتار آن جنبه ها را شبیه سازی کنید.
تمسخر شامل ایجاد یک نسخه جعلی از یک تابع، شی یا ماژول برای کنترل رفتار آن در طول آزمایش است. این به شما امکان می دهد سناریوهای مختلف را شبیه سازی کنید و تعاملات را بدون تکیه بر پیاده سازی های واقعی تأیید کنید.
ما بر دو رویکرد برای تمسخر تمرکز خواهیم کرد:
Function Mocks (همچنین جاسوس نامیده می شود):
شما می توانید از jest.fn()
برای ایجاد یک تابع ساختگی استفاده کنید که می تواند برای ردیابی یک تابع یا جایگزینی پیاده سازی واقعی استفاده شود. یا از jest.spyOn(object, methodName)
برای ردیابی تماس های object[methodName]
استفاده کنید.
ماژول Mocks : می توانید از jest.mock(“path-of-your-module”)
برای مسخره کردن کل ماژول ها یا واردات خاص استفاده کنید. با استفاده از آن، تمام توابع داخل این ماژول به توابع ساختگی تبدیل می شوند. علاوه بر این، در طول تست، ماژول هایی که شما در حال آزمایش آن هستید، نسخه تقلبی این ماژول را دریافت خواهند کرد.
هر تابع ساختگی روش هایی دارد که می توانید از آنها برای شبیه سازی رفتار تابع استفاده کنید. برخی از پرکاربردترین روش ها عبارتند از:
mockFn.mockImplementation(fn)
: برای جایگزینی اجرای واقعی یک تابع استفاده می شود. fn
اجرای جایگزین است.
mockFn.mockReturnValue(value)
: اگر تنها چیزی که به آن اهمیت می دهید مقدار بازگشتی یک تابع است، می توانید از این استفاده کنید.
mockFn.mockResolvedValue(value)
: اگر تابع mock یک وعده را برمی گرداند، می توانید از این استفاده کنید.
مثال استفاده 1
بیایید processNumbers
با استفاده از mock های تابع آزمایش کنیم. چالش اینجاست که processNumbers
تابع callback را به عنوان آرگومان می گیرد. اگر نیاز به آزمایش این تابع فراخوانی در داخل processNumbers
داشته باشید، چه؟
این هم کد:
import processNumbers from 'file-path' ; test( 'processNumbers applies callback and return the right result' , () => { // Arrange const arr = [ 2 , 3 ] const mockedCallback = jest.fn().mockImplementation( x => x + 2 ); // Act const result = processNumbers(arr, mockedCallback); // Assert expect(result).toEqual([ 4 , 5 ]); expect(mockedCallback).toHaveBeenCalledTimes(arr.length); });
ما با ترتیب دادن استدلال ها شروع کردیم:
متغیر arr
آرایه ای از اعداد است. در آزمون یک آرایه با اعداد تصادفی به آن اختصاص دادیم.
متغیر callback
یک تابع callback است. این تابع باید در آزمون مورد تمسخر قرار گیرد.
ممکن است از خود بپرسید که چرا باید callback
مسخره کنید، چرا آن را به عنوان یک عملکرد عادی اختصاص نمی دهید؟
پاسخ این است که بدون تمسخر آرگومان callback
، نمیتوانید آن را در داخل processNumbers
در حین آزمایش آن ردیابی کنید. از آنجایی که تمسخر یک نسخه جعلی از تابع ایجاد میکند، جاسوسی ایجاد میکند که دارای ردیاب است که از طریق آن میتوانید هر اقدامی را که در این تابع مسخرهشده انجام میشود، چه فراخوانی شود و چه آرگومانهایی به آن ارسال شود، ابراز کنید.
jest.fn()
یک تابع ساختگی ایجاد می کند. شما می توانید یک تابع را به جای تابع واقعی به fn
ارسال کنید.
در مرحله بعد، با فراخوانی تابعی که در حال آزمایش هستیم، "عمل" می کنیم: processNumbers
.
در نهایت، ما ادعاهایی را نوشتیم، که انتظاراتی در مورد نحوه رفتار processNumbers
و اعمال callback
توسط processNumbers
و برگرداندن نتیجه هستند.
مثال استفاده 2
عوارض جانبی جنبه دیگری است که باید در آزمایش کنترل کنید. در تابع getProfile
یک سیستم خارجی فراخوانی می شود که یک پایگاه داده را برای بازیابی اطلاعات فراخوانی می کند و این یک عارضه جانبی است.
در سناریویی دیگر، یک تابع ممکن است یک پایگاه داده را برای ایجاد یک کاربر متصل کند و از طریق آزمایش، نیازی به گفت ن یا تغییر داده های واقعی در پایگاه داده نخواهید داشت.
برای شبیه سازی رفتار getUserFromDatabase
بدون ضربه زدن به پایگاه داده، باید ماژول آن را مسخره کنید، و به طور پیش فرض، getUserFromDatabase
یک تابع ساختگی خالی خواهد بود که می توان آن را در طول آزمایش ردیابی کرد.
این هم کد:
import getProfile from 'file-path' ; import { getUserFromDatabase } from 'file-path' ; // Mock the module of getUserFromDatabase method jest.mock( './../DB/databaseMethods.js' ); describe( 'getProfile' , () => { it( 'should call getUserFromDatabase with the correct userId and return the result' , async () => { // Arrange const userId = '123' ; const dummyUser = { id : userId, name : 'John' }; getUserFromDatabase.mockResolvedValue(dummyUser); // Act const result = await getProfile(userId); // Assert expect(result).toEqual(dummyUser); expect(getUserFromDatabase).toHaveBeenCalledWith(userId); expect(getUserFromDatabase).toHaveBeenCalledTimes( 1 ); }); });
ما با مرتب کردن استدلال ها شروع کردیم:
userId
فقط یک عدد است.
dummyUser
یک شی است که داده های جعلی کاربر را شبیه سازی می کند.
ما dummyUser
از getUserFromDatabas
با استفاده از mockResolvedValue
برگرداندیم.
مشابه مثال آخر، ما با فراخوانی تابع مورد آزمایش "عمل" می کنیم: getProfile
.
در نهایت، ما این اظهارات را نوشتیم، انتظارات شما در مورد اینکه getProfile
چگونه باید رفتار کند و اینکه getUserFromDatabase
به درستی فراخوانی شده و نتیجه مطابق انتظار برمی گردد.
مورد استفاده: Login Express Controller
در اینجا یک کنترلر ورود وجود دارد که ایمیل و رمز عبور یک کاربر را از طریق شی req
دریافت می کند و سپس کاربر را در پایگاه داده جستجو می کند. برخی تحلیل ها را انجام می دهد، سپس اگر همه چیز درست است، یک res
برمی گرداند، یا با یک شی خطا، next
را فراخوانی می کند.
import User from "file-path" ; export const login = async (req, res, next) => { const { email, password } = req.body; const user = await User.findOne({ email }); if (!user) return next( new Error ( "Invalid Email!" )); const checkPassword = user.checkPassword(password); if (!checkPassword) return next( new Error ( "Invalid Password!" )); const token = user.generateToken(); return res.status( 200 ).json({ success : true , results : { token } }); };
در مورد مراحلی که می توانید برای آزمایش عملکرد ورود استفاده کنید فکر کنید. میتوانید چند سؤال بپرسید که به شما در ارائه ایدهها کمک میکند:
سناریوهای گردش کار تابع login
چیست؟
کاربر پیدا نشد
رمز عبور نادرست است
همه چیز اوکی است و یک پاسخ با یک نشانه برگردانده می شود.
پس می توانید برای انجام کارهای زیر login
شوید:
اگر کاربر پیدا نشد، باید login
next
بگیرد.
اگر رمز عبور مطابقت نداشته باشد، login
باید next
تماس بگیرد.
login
باید res.json را با توکن و res.status را با 200 فراخوانی کند اگر همه چیز درست است.
آرگومان هایی که روش login
باید دریافت کند چیست؟
شیء req
با خاصیت body
.
شی res
با ویژگی status
و json
.
تابع next
res.json()
یا res.status()
یا next()
همگی توابعی هستند که login
برای انجام کار خود به آنها نیاز دارد. در طول آزمایش، شما به این آرگومان ها دسترسی ندارید، پس باید آنها را مسخره کنید.
req
می توان به صورت {body: { email: "
test@foo.com
", password: "bar" }}
تعریف کرد
res
می توان به صورت {json: jest.fn().mockReturnThis(), status: jest.fn().mockReturnThis()}
تعریف کرد.
next
می توان به عنوان jest.fn()
تعریف کرد
آیا تعاملی با سیستم های خارجی یا وابستگی هایی وجود دارد؟
User.findOne()
user.checkPassword()
user.generateToken()
پس تمسخر راه حل است:
برای User.findOne()
باید کل ماژول User
را مسخره کنید و findOne()
جعلی را برای بازگرداندن یک user
جعلی تنظیم کنید. چالش اینجاست که findOne
یک متد شی است. چگونه می توانید آن را پیگیری کنید؟ jest.spyOn(object, methodName)
روح است.
روش spyOn
برای ردیابی تماس های object[methodName]
استفاده می شود که در مورد ما User.findOne
است.
user.checkPassword()
و user.generateToken()
باید توابع ساختگی باشند.
برای اعمال تمام این مفاهیم و قرار دادن بلوک ها با یکدیگر، آزمون نهایی باید به صورت زیر باشد:
import User from "file-path" ; import { login } from "file-path" ; jest.mock( "../DB/models/user.model.js" ); let mockReq, mockRes, mockNext, dummyUser; describe( "login controller" , () => { beforeEach( () => { mockReq = { body : { email : "test@foo.com" , password : "bar" } }; mockRes = { json : jest.fn().mockReturnThis(), status : jest.fn().mockReturnThis(), }; mockNext = jest.fn(); dummyUser = { checkPassword : jest.fn( () => true ), generateToken : jest.fn( () => "token" ), }; }); it( "should call next if user not found" , async () => { // Arrange jest.spyOn(User, "findOne" ).mockResolvedValueOnce( null ); // Act await login(mockReq, mockRes, mockNext); // Assert expect(mockNext).toHaveBeenCalledWith( new Error ( "Invalid Email!" )); expect(mockRes.json).not.toHaveBeenCalled(); }); it( "should call next if password doesn't match" , async () => { // Arrange dummyUser.checkPassword.mockReturnValueOnce( false ); jest.spyOn(User, "findOne" ).mockResolvedValue(dummyUser); // Act await login(mockReq, mockRes, mockNext); // Assert expect(mockNext).toHaveBeenCalledWith( new Error ( "Invalid Password!" )); expect(dummyUser.generateToken).not.toHaveBeenCalled(); expect(mockRes.json).not.toHaveBeenCalled(); }); it( "should call res.json with the token and call res.status with 200 if everything is ok" , async () => { // Arrange jest.spyOn(User, "findOne" ).mockResolvedValue(dummyUser); // Act await login(mockReq, mockRes, mockNext); // Assert expect(mockNext).not.toHaveBeenCalled(); expect(User.findOne).toHaveBeenCalledWith({ email : mockReq.body.email }); expect(dummyUser.checkPassword).toHaveBeenCalledWith(mockReq.body.password); expect(dummyUser.generateToken).toHaveBeenCalled(); expect(mockRes.status).toHaveBeenCalledWith( 200 ); expect(mockRes.json).toHaveBeenCalledWith({ success : true , results : { token : "token" }, }); }); });
نکته پایانی : beforeEach
یک قلاب Jest باشد، می توانید از آن برای پیاده سازی کد قبل از هر تست استفاده کنید. در داخل beforeEach
تابع، میتوانید هر متغیر مشترکی را که آزمونهای شما ممکن است به آن نیاز داشته باشند، بنویسید، بهجای اینکه آنها را بهطور مستقل برای هر آزمون بنویسید.
خلاصه
در این آموزش اصول اولیه تست واحد با جست را با تمرکز بر نحوه استفاده از ماک ها آموختید. تست واحد کمک می کند تا اطمینان حاصل شود که بخش های جداگانه کد شما به طور مجزا به درستی کار می کنند.
مدیریت وابستگی های خارجی، مدیریت عوارض جانبی و استفاده از تمسخر، مهارت های ضروری برای آزمایش قوی هستند. Jest ابزارهای قدرتمندی برای مقابله با این چالشها ارائه میکند و آزمایشهای شما را قابل اعتمادتر، سریعتر و آسانتر نگه میدارد.
درک این مفاهیم به شما کمک می کند تا تست های بهتری بنویسید و برنامه های کاربردی انعطاف پذیرتری تولید کنید.
در این آموزش نحوه استفاده از ویژگی های تمسخر جست برای شبیه سازی وابستگی های خارجی و مدیریت عوارض جانبی توضیح داده شده است. این شامل یک مثال عملی از آزمایش یک کنترل کننده ورود Express.js است که نشان می دهد چگونه عملکردها و سناریوهای آزمایش را کنترل کنید.
این رویکرد به شما کمک میکند تا با جداسازی و مدیریت مؤثر وابستگیها، تستهای قابل اعتمادی ایجاد کنید و کیفیت کد را حفظ کنید.
ارسال نظر