در دنیای برنامه نویسی، توسعهی نرمافزارهای مختلف با سرعتی شگفتآور پیش میرود. اما با افزایش پیچیدگی نرمافزارها، نیاز به اطمینان از کیفیت و عملکرد صحیح آنها افزایش مییابد. یکی از ابزارهای مؤثر برای ارتقاء کیفیت نرمافزار، تست واحد یا "Unit Testing" است. در این مقاله، به پنج اصل اساسی تست واحد پرداخته و نحوهی استفاده از آن در تضمین کیفیت نرمافزار را بررسی خواهیم کرد.با این 5 اصل قدرتمند تست واحد، توسعه نرم افزار خود را به سطح بعدی برسانید! تست کد یکی از مهمترین جنبه های توسعه نرم افزار است، زیرا کیفیت، مقیاس پذیری و قابلیت اطمینان محصول شما را تضمین می کند.
اما، بدون هیچ دستورالعملی، نوشتن تست های موثر می تواند دشوار باشد. در واقع، کد تست می تواند پیچیده تر و سخت تر از کد تولید واقعی باشد!
اجازه ندهید این اتفاق برای شما بیفتد. این بهترین روشها را برای تست واحد دنبال کنید، که میتواند در هر فریمورک یا کتابخانهای، هم در بک اند یا هم در فرانتاند برنامهتان اعمال شود، و میتوانید کد آزمایشی ناب، دقیق و خوانا بنویسید. با این موارد، در زمان ارزشمند توسعه دهنده صرفه جویی می کنید، از بدهی فنی جلوگیری می کنید و از آزمایش کد خود لذت می برید.
برای نوشتن تست های موثر و کم هزینه با نحوه نوشتن نقشه تست نویسی آشنا شویم.
تست واحد چیست؟
تست واحد به معنای بررسی و اعتبارسنجی کوچکترین قسمتهای نرمافزار، یعنی توابع و متدها به صورت جداگانه است. این اصلیترین قسمت از تست نرمافزار است و به طور خاص تضمین میکند که هر بخش از نرمافزار به تنهایی به درستی کار میکند.
1. تست ناب و دقیق
کد تست باید ساده و کار با آن آسان باشد. هر کسی که به تست نگاه می کند باید فورا بداند که این تست در مورد چیست و هدف آن چیست. توسعه آزمایشها باید با تلاش و سرمایهگذاری بسیار کم ارزش زیادی به همراه داشته باشد.
آیا برای خواندن و درک تست خود به بیش از 30 ثانیه زمان نیاز دارید؟ بازنویسی کن!
تست فقط برای «درصد پوشش»؟ نکن!!! این فقط باعث می شود که اکثر مجموعه آزمایشی برای توسعه دهندگان غیر ضروری و نامطلوب باشد. فقط موارد مورد نیاز را تست کنید. بهتر است برخی از تستهای چابکی و سادگی را کنار بگذارید و فقط منطق اصلی تجاری و موارد لبه اصلی را آزمایش کنید.
2. رفتار را آزمایش کنید، نه اجرا را
تک تک خط ها و متغیرهای داخلی تغییر شده در کد را بررسی نکنید. هنگام آزمایش، باید روی نتیجه تمرکز کنید. نتیجه باید همیشه ثابت بماند حتی اگر کد داخل متد دوباره فاکتور شده باشد!
به این ترتیب، در صورت تغییر پایه کد، نیازی به بازنویسی تست های خود نخواهید داشت.
// Wrong ❌ - Test behaviour
describe('Evaluation Service', () => {
describe('Register Students', () => {
it('Should add new students to the evaluation service', () => {
const studentJosh = {
id: 1,
name: 'Josh McLovin',
average: 6.98,
}
evaluationService.addStudent(studentJosh)
expect(evaluationService._students[0].name).toBe('Josh')
expect(evaluationService._students[0].average).toBe(6.98)
})
})
})
// Right ✅ - Test behaviour
describe('Evaluation Service', () => {
describe('Register Students', () => {
it('Should add new students to the evaluation service', () => {
const studentJosh = {
id: 1,
name: 'Josh McLovin',
average: 6.98,
}
evaluationService.addStudent(studentJosh)
expect(evaluationService.getStudentAverage('Josh')).toBe(6.98)
})
})
})
3. نامگذاری و ساختاربندی تست. الگوی AAA
آیا تا به حال برایتان پیش آمده است که تستی با نام «این باید به درستی [...] درست باشد» شکست خورده اید و چند دقیقه ای را از دست داده اید تا بفهمید مشکل کجاست؟
نامگذاری و ساختار مجموعه تست شما می تواند توانایی شما را برای رسیدگی سریع و دقیق به هر آزمونی که شکست خورده است افزایش دهد و در نهایت در وقت ارزشمند شما صرفه جویی کند. بنابراین، بیایید دو اصل کلیدی را برای تلاشهای تست های بعدی خود در نظر داشته باشید:
3.1 نامگذاری تست به صورت متفکرانه:
هنگام نامگذاری تستهای خود، سعی کنید اطلاعات زیر را وارد کنید:
- چه چیزی در حال آزمایش است؟
- در چه شرایطی؟
- نتیجه مورد انتظار چیست؟
// Right ✅ - Test naming
// 1. What is being tested:
describe('Evaluation Service', () => {
describe('Evaluate Students', () => {
// 2 & 3. Context and expected result
it('If the student grade is below the minimum grade, student should be suspended', () => {
const students = [
{ name: 'Mark', grade: 4.25 },
{ name: 'Colin', grade: 6.7 },
{ name: 'Ben', grade: 5.3 },
]
const result = evaluationService.evaluateStudents({ students, minGrade: 5 })
expect(result['Mark']).toBe('suspended')
})
})
})
3.2 الگوی AAA برای تست ساختار کد:
اگر میخواهید یک مجموعه تستی خوانا و قابل درک داشته باشید، تست را به صورت زیر ساختار دهید:
Arrange: تمام کدهای مورد نیاز برای شبیه سازی وضعیت مورد نیاز را تنظیم کنید. این می تواند شامل مقداردهی اولیه متغیرها، پاسخ های تمسخر آمیز، نمونه سازی واحد تحت آزمایش و غیره باشد.
Act: آنچه را که در حال آزمایش است، معمولاً در یک خط کد اجرا کنید.
Assert: بررسی کنید که نتیجه به دست آمده مورد انتظار است. مانند مورد بالا، این باید فقط یک خط طول بکشد.
// Right - AAA Testing Pattern
describe('Evaluation Service', () => {
describe('Average Calculation', () => {
it('Should calculate the average grade of all the students', () => {
// Arrange: create an object with the student names and their grades
const students = [
{ name: 'Mark', grade: 4 },
{ name: 'Colin', grade: 10 },
{ name: 'Ben', grade: 7 },
{ name: 'Tim', grade: 3 },
]
// Act: execute the getAverage method
const avg = evaluationService.getAverage(students)
// Assert: check if the result is the expected one -> (4+10+7+3)/4 = 6
expect(avg).toEqual(6)
})
})
})
4. تست های قطعی و ایزوله
اگر یک تست مردودی کل مجموعه شما را قرمز کند، ممکن است به روش درستی به آن نزدیک نشوید!
تست ها باید مستقل و مجزا باشند، هر بار یک منطق خاص را هدف قرار داده و با آن سروکار داشته باشند و مجموعه آزمایشی سریعتر و پایدارتری را انجام دهند.
اگر تست های خود را مستقل ننویسید چه اتفاقی می افتد؟
- شما نمی توانید علت و محل دقیق اشکالات و مشکلات را مشخص کنید.
- هنگام تنظیم مجدد تست ها، باید چندین مورد را به روز رسانی و همگام سازی کنید.
- شما نمی توانید تست های خود را به ترتیبی اجرا کنید، که ممکن است باعث شکستن یا نادیده گرفتن برخی از assertions یا expectations شود.
5. تست مبتنی بر ویژگی ها و داده های واقعی
از نوشتن حجم زیادی از ورودی های ممکن در تست های خود خسته شده اید؟ تست مبتنی بر ویژگی این کار را برای شما انجام می دهد! اما... آن چیست؟
آزمایش مبتنی بر ویژگی صدها ترکیب ممکن را ایجاد می کند، تست را تحت فشار قرار می دهد و فرصت کشف اشکالات قبلی را افزایش می دهد. این رویکرد حتی میتواند ورودیهایی را که ممکن است باعث یک نتیجه غیرمنتظره شده باشد را بازگرداند.
کتابخانههایی مانند JSVerify یا Fast-Check ابزارهای ضروری را برای تسهیل تستهای مبتنی بر ویژگی ها ارائه میکنند.
با این حال، اگر ترجیح میدهید به چنین تست های گستردهای نپردازید، استفاده از دادههای واقع بینانه در صورت امکان بسیار مهم است. ورودیهایی مانند «abc» یا «1234» ممکن است به اشتباه تست را در زمانی که واقعاً شکست بخورند، پشت سر بگذارند.
// Wrong ❌ - False Positive - Test that passes even though it shouldn't
class EvaluationService {
_students = [];
addStudent(student) {
// Add the student if the name has no numbers
if(!student.name.matches(/^([^0-9]*)$/)){
this._students.push(student);
}
}
}
describe('Evaluation Service', () => {
describe('Register Students', () => {
it('Should add students to the Evaluation service', () => {
const mockStudent = {
id: 2,
name: 'username',
average: 7
}
// Won't fail because the name is a string without number -> We are not checking what happens if the user
// inputs a name with a number.
evaluationService.addStudent(mockStudent)
expect(evaluationService.getStudentAverage('username')).toBe(7)
})
})
})
// In the example above, we are making a test so the test passes, instead of looking for edge cases with realistic data
اگر تست هر منطقی در کامپوننت خود برای شما سخت است، این می تواند نشان دهنده این باشد که، شاید باید منطق کامپوننت خود را به قطعات کوچکتر، ساده تر و قابل آزمایش کردن کد تقسیم کنید!
نکات اضافی در تست نویسی
با رعایت نکات زیر نیز می توانید تست های بسیار با کیفیت و با عملکرد بالا بنویسید.و یادتان باشد از تست کردن نباید بترسید.
سریع
-یک توسعهدهنده نباید تردیدی داشته باشد که تستها به عنوان آهسته شناخته شوند.
-تمامی اقدامات از جمله تنظیم، اجرای واقعی تست و تمیزکاری باید واقعاً سریع اجرا شوند (به میلیثانیه) زیرا ممکن است در کل پروژهی خود هزاران تست داشته باشید.
مستقل / ایزوله شده
-یک متد تست باید سه مرحله را انجام دهد: ترتیب دادن (Arrange)، عمل کردن (Act)، و اعتراض کردن (Assert).
- ترتیب دادن: دادههای استفاده شده در یک تست نباید به محیطی که تست در آن اجرا میشود وابسته باشند. تمامی دادههای مورد نیاز برای یک تست باید به عنوان بخشی از تست ترتیب داده شوند.
- عمل کردن: متد واقعی مورد آزمایش را فراخوانی کنید.
- اعتراض کردن: یک متد تست باید برای یک نتیجه منطقی واحد آزمایش کند، که تلمیح میدهد که به طور معمول باید فقط یک اعتراض منطقی وجود داشته باشد. یک اعتراض منطقی ممکن است دارای چندین اعتراض فیزیکی باشد تا زمانی که همه اعتراضها وضعیت یک شی را آزمایش کنند. در چندین مورد، یک عملیات ممکن است بر روی چند شی اعمال شود.
- اجتناب از انجام اعتراضها در بخش ترتیب دادن، اجازه دهید استثناء را پرتاب کند و تست شما همچنان ناکام خواهد شد.
- بدون وابستگی به ترتیب اجرا. آنها باید به یک شیوه در مجموعه یا هنگام اجرای جداگانه گذر کنند.
- پس از اظهار (اعتراض) ها اقدامات دیگری انجام ندهید، بهتر است تنها یک اظهار منطقی وجود داشته باشد.
قابل تکرار
-یک متد تست نباید به هیچ داده در محیط / نمونهای که در آن اجرا میشود وابسته باشد.
- نتایج قطعی - باید هر بار و در هر مکانی که اجرا میشوند نتایج یکسانی را تولید کنند.
- بدون وابستگی به تاریخ / زمان یا خروجی توابع تصادفی.
- هر تست باید دادههای خود را تنظیم یا ترتیب دهد.
-اگر مجموعهای از تستها به دادههای مشترکی نیاز دارند، از کلاسهای کمکی (Data Helper) استفاده کنید که این دادهها را برای قابلیت استفاده مجدد تنظیم کنند.
خود-تأییدکننده
هیچ بازرسی دستی برای بررسی اینکه تست گذشته یا نپذیرفته است، لازم نیست.
جامع
-باید همهی سناریوهای ممکن استفاده و نه فقط هدف از پوشش 100٪ را پوشش دهند.
- تستها برای مقادیر گوشه / لبه / مرزی.
- تستها برای مجموعه دادههای بزرگ - این تست زمان اجرا و پیچیدگی فضایی را آزمایش میکند.
- تستها برای امنیت با کاربران دارای نقشهای مختلف - رفتار ممکن است بر اساس نقش کاربر متفاوت باشد.
- تستها برای مقادیر بزرگ - خطاهای سرریز و زیرریز برای انواع دادههایی مانند عدد صحیح.
- تستها برای استثناءها و خطاها.
- تستها برای آرگومانهای غیرقانونی یا ورودیهای نادرست.
امیدوارم این نکات اضافی نیز در تست نویسی خود استفاده کنید.
نتیجه
تست واحد یکی از ابزارهای مهم در تضمین کیفیت نرمافزار است. با رعایت پنج اصل اساسی تست واحد، میتوان به بهبود عملکرد و پایداری نرمافزارها کمک کرد. این ابزار تضمین میکند که هر قسمت از نرمافزار به تنهایی به درستی کار میکند و مشکلات به سرعت شناسایی و رفع میشوند.
در نتیجه، پیروی از این بهترین شیوهها ممکن است به روشی جدید، خوانا و قابل نگهداری برای آزمایش کد تولید محبوب شما منجر شود. با تشکر برای خواندن! اگر تجربه یا نکته ای برای تست نویسی دارید می توانید در بخش نظرات آن را با ما و جامعه برنامه نویسی به اشتراک بگذارید.
یک اپلیکیشن تست شده یک برنامه قابل اعتماد است!