تازگی ها بخاطر یه پروژه ای درگیر تست نویسی شدم و صرفاً برای دونستن و آشنایی خودم رفتم یکم با انواع تست نویسی برای فرانت اند آشنا بشم. اینجا به طور خلاصه اطلاعاتی که به دست آوردم رو باهاتون به اشتراک میذارم باشد که رستگار شویم??. فقط اینم بگم اگه بخواید مثال هارو متوجه بشید یه دانش پایه از jsو es6 و react داشته باشید بهتره. تو مثالا برای تست نویسی من از jest استفاده کردم که فک نمیکینم خیلی پیچیده باشه و نیاز به دانش قبلی داشته باشه. در کل این بیشتر یه توضیح مفهومیه و نه آموزش پس نبود این دانش ها هم مشکلی ایجاد نمیکنه.
بخوام به شکل خیلی ساده بگم داستان اینه که شما با تست نویسی تو فرانت اند متوجه میشید که آیا همه عناصر سایتتون همونجوری که توقع دارید کار میکنن یا نه، مثلاً اگه فلان جا کیلیک بشه اون فانکشنالیتی که توقع دارید اجرا میشه یا نه ، سرعت لود شدن صفحه استاندارد هست یا نه ،اگه فیلد های یه فرم پر شد و ارسال شد اطلاعاتش هم همونجوری که توقع دارید ارسال میشه یا نه. خلاصه اینکه تست نویسی انجام میشه که شما بفهمید همه عملکرد هایی که از این لایه از پروژه توقع دارید بدون اشکال اجرا میشه یا نه ??
تست نویسی فرانت اند به شکل های مختلفی انجام میشه که اینجا سه تاشون(که حداقل حس میکنم ) کاربردی ترن رو براتون توضیح میدم:
فرض کنید بخواید عملکرد کوچیک ترین بخش از کدتون که برای مثال یه فانکشن ساده است رو تست کنید
برای مثال متد زیر
function add(x, y) { return x + y }
خب اینجاست که کار یونیت تست شروع میشه??
راستی همونجوری که برای پیاده سازی بهتر و سریع تر ui سایت از framework هاو کتابخونه هایی مثل vue و react استفاده میکنیم ، تست نویسی هم ابزار و فریم ورک های خودشو داره و یکی از رایج ترین فریم ورک های تست نویسی jest هست که اینجا برای تونیت تست از اون استفاده میکنیم
// 1. The method we want to test function add(x, y) { return x + y } // 2. A test suite describe("add method", () => { // 3. A unit test it("should return 2", () => { // 4. An assertion expect(add(1, 1)).toBe(2) }) })
حالا بذارید توضیح بدم چه اتفاقی داره میوفته
1 - خب تو این مرحله متدی که میخوایم تست کنیم نوشته شده و واضحه چیه ، در واقع یه متد ساده داریم که دوتا ورودی رو میگیره و بعد جمع زدنشون اونا رو برمیگردونه
2- تو این مرحله برای تستمون یه بدنه تعریف میکینم به اسم add method و در واقع هر چندتا تست بخوایم تو این مرحله اجرا بشه رو توی این بدنه قرار میدیم
3- توی این مرحله در واقع یه یونیت تست ساختیم که تست رو به صورت کال بک ازمون میگیره
4- مرحله آخر و مهم ترین قسمت یونیت تست اینجاست که ما میگیم ما از یه متدی به اسم add که قبلاً تعریفش کردیم یه توقعی داریم که اگه جفت ورودی هارو بهش 1 بدیم اون به ما 2 برگردونه ( بالاتر تعریف کردیم که کار این متد همینه که ورودی هارو جمع کنه و نتیجه رو بهمون برگردونه)
حالا بعد ران کردن تست این قطعه کد بررسی میشه و اگه نتیجه مطلوبو برگردونه تست متد درسته و مشکلی نداره ولی اگه اشتباه باشه باید بریم کدمونو بررسی کنیم که ببینم کجا ایراد وجود داره و تمام همین شکلی یه یونیت تست ساده که یه تیکه خیلی کوچولو از کدمونو قراره تست کنه رو نوشیتم??
خب تو یه پروژه بزرگ یه عالمه ماژول و کامپوننت و متد هست که باید بعضی وقتا کنار هم و بعضی وقتا پشت سر هم و... کار کنند تا پروژه بدون اشکال اجرا بشه . برای اینکه ببینیم این یک پارچگی وجود داره از integration test استفاده میکنیم.
پس کار اصلی integration test بررسی یکپارچگی عملکرد تو پروژه ماست
حالا بذارید یه مثال بزنم ، فرض کنید کاپوننت زیر به اسم Counter.js رو تو پروژه مون داریم
import React, { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); const handleIncrement = () => { setCount(count + 1); }; return ( <div> <h1>Count: {count}</h1> <button ={handleIncrement}>Increment</button> </div> ); } export default Counter;
خب خیلی چیز پیچیده ای نیست، یه کاپوننتی داریم که توش یه نمایش دهده یک counterوجود داره و یه button که به یه متد متصله و کار این متد اضافه کردن عدد counter هست.پس با هر بار کیلیک کردن روی button متد مربوط بهش کال میشه و به مجموع عدد counter یه عدد اضافه پیدا میکنه. تا اینجا اوکی؟ حله ، حالا میخوایم براش تست بنویسیم.اینجا بازم میخوایم از jest استفاده کنیم . پس یه فایل جدا مثلاً به اسم Counter.test.js میسازیم و کد زیر رو توش مینویسیم
import React from 'react'; import { render, fireEvent } from '@testing-library/react'; import Counter from './Counter'; //step 1 test('clicking the increment button updates the count', () => { //step 2 const { getByText } = render(<Counter />); const countElement = getByText('Count: 0'); const incrementButton = getByText('Increment'); //step 3 // Click the button twice fireEvent.click(incrementButton); fireEvent.click(incrementButton); //step 4 // Check that the count has been updated expect(countElement).toHaveTextContent('Count: 2'); });
خب بریم سراغ توضیح این مثال
1- تو این مرحله میایم یه بدنه برای تست میسازیم به اسم clicking the increment button updates the count .
2- تو مرحله دوم اطلاعات مورد نیازمون از کامپونتت که برای تست احتیاج داریم میگیرم ، مثل نمایشگر counter و button افزایش دهنده عدد نمایشگر.
3- تو این مرحله برای انجام تست میایم یه کار انجام میدیم که عدد نمایشگر تغییر کنه و درواقع دوبار روی باتن کلیک میکنیم که طبیعتاً به شرط درست بودن عملکرد کامپونتمون باید عدد نمایشگر دوتا اضافه بشه که با فرض 0 بودن باید برابر 2 بشه.
4- خب اینجا تست اصلیمونه که بهش چی میگیم؟ میگیم داداش شما بیا countElement که بالاتر گرفتی رو دوباره بررسی کن ببین اون متن داخلش تبدیل شده به 'Count: 2' یا نه.
به همین سادگی یه integration test نوشتیم??
اینجا یکم داستان فرق داره چه فرقی؟ اینکه ما تو e2e میخوایم ببینیم فلان عملکرد و یا فرآیندی که داریم از دید کاربر چجوریه
فرض کنید میخوایم ببینیم فرآیند login یه سایت درست کار میکنه یا نه .
این بار خیلی از نظر فنی کد بررسی نمیشه بیشتر از این لحاظ بررسی میشه که اول تا آخر یه فرآیند از چشم یه کاربر به شکل کامل و بدون اشکال انجام میشه یا نه.
خب فرض کنید کامپوننت Login.js رو برای فرایند login داریم:
import React, { useState } from 'react'; const Login = () => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [loggedIn, setLoggedIn] = useState(false); const handleEmailChange = (e) => { setEmail(e.target.value); }; const handlePasswordChange = (e) => { setPassword(e.target.value); }; const handleSubmit = (e) => { e.preventDefault(); // Simulate login process // Replace this with your actual login logic if (email === 'example@example.com' && password === 'password123') { setLoggedIn(true); } }; return ( <div> <form ={handleSubmit}> <label> Email: <input type="text" value={email} ={handleEmailChange} /> </label> <br /> <label> Password: <input type="password" value={password} ={handlePasswordChange} /> </label> <br /> <button type="submit">Submit</button> </form> {loggedIn && <p>Logged in</p>} </div> ); }; export default Login;
خب اینجا یه کامپوننت ساده داریم که توش گفتیم ببین آقا فرض کن تو دوتا فیلد password و email داری که اگه این دوتا فیلد پر بود و دکمه submit کلیک شد بیا بگو login شدیم و logged in رو نمایش بده در غیر این صورت login اتفاق نیافتاده. پس تو تستمون هم همینو میخوایم بررسی کنیم . یعنی اول باید دوتا فیلد از فرم رو پر کنیم بعد روی submit کلیک کنیم و بعد چک کنیم ببینیم آیا login انجام شده یا نه.
خب بیاید بریم سراغ تستش، اینجا فایل Login.test.js رو داریم:
import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import Login from './Login'; //step1 describe('Login Component', () => { test('should log in successfully', () => { render(<Login />); //step2 // Find the login form elements const emailInput = screen.getByLabelText('Email:'); const passwordInput = screen.getByLabelText('Password:'); const submitButton = screen.getByRole('button', { name: 'Submit' }); //step3 // Type email and password fireEvent.change(emailInput, { target: { value: 'example@example.com' } }); fireEvent.change(passwordInput, { target: { value: 'password123' } }); //step 4 // Submit the login form fireEvent.click(submitButton); //step // Assert that the user is logged in expect(screen.getByText('Logged in')).toBeInTheDocument(); }); });
1- میایم بدنه تستمون تعریف میکنیم و یه تست میسازیمو و کاپونتمونو رندر میکنیم
2- فیلد های password و email و submit رو از روی صفحه پیدا میکنیم.
3- فیلد های passwordو email رو با دیتای مورد نظرمون پر میکنیم
4- روی submit کلیک میکنیم
5- نتیجه رو بررسی میکینم. اینجا چک میکنیم ببینیم اون متن logged in که قرار بود تایید کنندهی لاگین ما بوده تو صفحه وجود داره یا نه و تمام و اینجوری یه تست e2e برای پروژه مون نوشتیم??
جمع بندی: من تو این پست بیشتر قصد داشتم یه نگاه مفهومی درباره تست نویسی تو فرانت اند صرفاً بر اساس تحقیق و مطالعه ای که این چند وقت داشتم باشه ، اگه کم و کسر و ایرادی هم داشت هم امیدوارم بعد تر اصلاحش کنم هم اینکه شما به بزرگی خودتون ببخشید. امیدوارم این توضیح هات کمکتون کرده باشه و به کارتون اومده باشه و این که تا اینجا خوندید دمتون خیلی گرمه و خدافظ???