ویرگول
ورودثبت نام
فرشید کریمی
فرشید کریمیفرشید کریمی‌ام. یه زمانی مهندسی برق خوندم، کلی توی دنیای embedded دور زدم و با سخت‌افزارها ور رفتم. چند سالیه توسعه وب رو شروع کردم و خیلی به عمیق شدن و فهم مفاهیم پیچیده علاقه‌مندم.
فرشید کریمی
فرشید کریمی
خواندن ۵ دقیقه·۳ روز پیش

تست‌نویسی در javascript/typescript — بخش ۲: تست واحد یا unit test

در این مقاله، قصد داریم وارد دنیای تست واحد (Unit Testing) بشیم. تست واحد یکی از پایهایترین و مهمترین انواع تستهای نرمافزاریه که در اون فقط یک واحد کوچیک از کد (معمولاً یک تابع یا متد) به صورت ایزوله تست میشه.

تست واحد به ما کمک میکنه تا:

  • خطاهای اولیه رو سریع شناسایی کنیم

  • تغییرات ایمن داشته باشیم، یعنی وقتی کدمون رو میخواهیم تغییر بدیم، دست و دلمون نلرزه که چندجای دیگه بترکه!

  • اعتماد به نفس بیشتری در ریلیز کردن نسخهها داشته باشیم

توسعه مبتنی بر تست (Test-Driven Development - TDD)

قبل از اینکه وارد نوشتن تستها بشیم، واسه اینکه تاکید کنم، تست نوشتن چقدر می تونه مهم باشه باید بگم اصلا ما یک متدولوژی توسعه داریم که برمبنای تست نویسیه و اسمش توسعه مبتنی بر تست یا TDD عه.

توی متدولوژی TDD ما اول تست مینویسیم، بعد کد. درباره اینکه چه موقعیه اوکیه این متدولوژی رو استفاده کنیم و چه موقع foul عه بعدا صحبت می کنیم ولی اول بذارید توضیح بدم که چقدر بامزه است، توسعه این سبکی!

چرخه Red-Green-Refactor

متدولوژی TDD رو اینجوری پیاده میکنند:

  1. قرمز (Red): اول یک تست برای تیکه کدمون مینویسیم که شکست میخوره

  2. سبز (Green): حداقل کد ممکن رو مینویسیم تا تست موفق بشه

  3. بازنگری (Refactor): کد رو بهبود میدیم بدون اینکه تستها شکست بخورن

مثال عملی TDD

فرض کنید میخوایم یک تابع برای محاسبه فاکتوریل بنویسیم. دقت کنید ما تابع factorial رو هنوز ننوشتیم و تازه میخواهیم شروع کنیم به نوشتنش. متد TDD به ما میگه اول تستش رو خرد خرد بنویس تا به خودش برسی!

مرحله ۱: قرمز (نوشتن تست شکست خورده)

اول تست رو مینویسیم: (فعلا بیخیال syntax اش، میگیم جلوتر چیه داستانش!)

describe('factorial', () => { it('should calculate factorial of 5', () => { expect(factorial(5)).toBe(120); }); });

این تست حتماً شکست میخوره چون تابع factorial هنوز وجود نداره!

مرحله ۲: سبز (نوشتن حداقل کد برای موفقیت)

حالا حداقل کد ممکن رو مینویسیم:

export const factorial = (n) => { return 120; };

تست موفق میشه! ولی این کد قاعدتا درست نیست.

مرحله ۳: بازنگری (افزایش تستها و بهبود کد)

حالا تستهای بیشتری اضافه میکنیم:

describe('factorial', () => { it('should calculate factorial of 0', () => { expect(factorial(0)).toBe(1); }); it('should calculate factorial of 1', () => { expect(factorial(1)).toBe(1); }); it('should calculate factorial of 5', () => { expect(factorial(5)).toBe(120); }); });

حالا برای پاس شدن تستها، کد رو بهبود میدیم:

export const factorial = (n) => { if (n === 0 || n === 1) { return 1; } return n * factorial(n - 1); };

مزایای TDD

  • کیفیت بالاتر: کدی که با TDD نوشته میشه معمولاً باگ کمتری داره

  • طراحی بهتر: TDD به شما کمک میکنه APIهای بهتری طراحی کنید

  • اعتماد به نفس بیشتر: با تستهای جامع، جرئت تغییر و بهبود کد رو دارید

  • مستندسازی: تستها به عنوان مستندات اجرایی عمل میکنن

معایب و چالشهای TDD

  • زمان بیشتر: اول امر زمان بیشتری میبره

  • منحنی یادگیری شیب دار: یادگیری TDD نیاز به تمرین داره

  • برای همه چیز مناسب نیست: نوشتن تست با این روش، برای بعضی از قسمتهای کد (مثل UI) مناسب نیست.

به عنوان یه rule of thumb اگر بخام بگم، تست TDD رو زمانی بنویسید که میدونید برای هر ورودی دقیقا چه خروجی قابل انتظار است، مثلا در validation ها یا state machine ها یا توابع ریاضی.

حالا که با TDD آشنا شدیم، بیاید وارد نوشتن اولین تستهامون بشیم!

شروع به نوشتن تستها

خب اول از همه اگر پروژه ندارید یه پروژه با روال زیر بسازید و dependency ها رو نصب کنید. اگرم در حال حاضر هر نوع پروژه JavaScript یا typescript دارید، کافیه فقط dependency رو نصبش کنید.

npm init -y npm install -D vitest

حالا فایل زیر رو با عنوان helloWorld.test.ts مثلا ذخیره کنید.

import { test, expect } from 'vitest'; test('is a super simple test', () => { expect(true).toBe(true); });

میتونید این تست رو با اجرای npx vitest از خط فرمان اجرا کنید و میبینید که تست پاس میشه. اگر بخوام این فایل رو توضیح بدم:

یه تابع به نام test وجود داره که دو تا آرگومان میگیره: 1. یه رشته که نام تست رو نشون میده 2. یه تابع که بدنه تست رو شامل میشه

داخل اون تابع، از یه کتابخونه assert برای بیان انتظاراتمون استفاده میکنیم 1. در اینجا، انتظار داریم این دو تا چیز با هم برابر باشن 2. و اونها هستن!

یک مثال دیگه

حالا بیاید یه مثال واقعیتر بررسی کنیم:

test('another test, but with some logic', () => { expect(1 + 1).toBe(2); }); test('a test with a function', () => { const add = (a, b) => a + b; expect(add(1, 2)).toBe(3); });

نکته مهم اینه که، وقتی یک تست مثل فایل بالا مینویسیم، میتونیم (نه اینکه باید!) هر تعداد خواستیم از این (...)test ها داشته باشیم . اینجوری میتونیم مطمئن بشیم هر سناریویی رو پوشش دادیم. وظیفه تست runner (در اینجا npx vitest) اینه که تست ما رو اجرا کنه و مطمئن بشه که همه چیز درست کار میکنه.

نحوه کار تستها

یه تست فقط به خاطر اینکه شکست نخورده، قبول میشه.

این به این معنیه که اگه یه تست شکست بخوره، یعنی یه error انداخته شده. پس وقتی میگیم تست قبول شده، یعنی هیچ errorای ننداخته!

تستی که انتظار داریم شکست بخوره با test.fail

میتونیم از .test.fails برای تستهایی استفاده کنیم که انتظار داریم شکست بخورن:

test.fails('works with "test" as well', () => { expect(true).toBe(false); });

این مثال ساده نشون میده که چطور میتونیم تستهایی بنویسیم که انتظار داریم شکست بخورن.

نادیده گرفتن تست با it.skip

اگر میخواید یه تست خاص رو موقتاً اجرا نکنید (مثلاً در حال کار روی اون بخش هستید یا تست مشکل داره):

it.skip('this test is temporarily disabled', () => { expect(someFunction()).toBe('expected result'); });

اجرای فقط یه تست با it.only

اگر میخواید فقط یه تست خاص رو اجرا کنید و بقیه تستها رو نادیده بگیرید (مثلاً وقتی روی یه بخش خاص کار میکنید و میخواید فقط تست مربوط به اون بخش رو ببینید):

it.only('this is the only test that will run', () => { expect(add(2, 3)).toBe(5); });

به جای test در vitest میشه it هم نوشت و فرقی ندارند و به کرات به جای هم استفاده میشن، فلذا گیج نشید!

تست کد Async

خب فرض کنیم یه کد async رو بخواهیم تست کنیم. تست زیر به اشتباه قبول میشه، چون تابع بالادست setTimeout یه تابع سنکرونه و منتظر resolve این تایم اوت و رسیدن به assertion نمیشه. (رجوع به مفاهیم async)

it('it will pass, unfortunately', () => { setTimeout(() => { expect('This should fail.').toBe('Totally not the same.'); }, 1000); });

خب راه حلش اینه که به سادگی از async/await استفاده کنیم:

test('works with "test" as well', async () => { const result = await addAsync(2, 3); expect(result).toBe(5); });

مشاهده مقاله کامل در این آدرس:

https://farshidev.ir/Post/%D8%AA%D8%B3%D8%AA%E2%80%8C%D9%86%D9%88%DB%8C%D8%B3%DB%8C-javascript-typescript-unit-test

موفق باشید.

تست واحدunit testreact
۲
۰
فرشید کریمی
فرشید کریمی
فرشید کریمی‌ام. یه زمانی مهندسی برق خوندم، کلی توی دنیای embedded دور زدم و با سخت‌افزارها ور رفتم. چند سالیه توسعه وب رو شروع کردم و خیلی به عمیق شدن و فهم مفاهیم پیچیده علاقه‌مندم.
شاید از این پست‌ها خوشتان بیاید