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

تست‌نویسی در javascript/typescript — بخش ۳: تکنیک‌های پیشرفته تست واحد

در مقاله قبلی، با مبانی تست واحد و TDD آشنا شدیم. در این مقاله تکنیک‌های پیشرفته تست نویسی واحد رو با هم بررسی می‌کنیم. در این مقاله، ابزارهای قدرتمندی رو یاد می‌گیریم که به ما کمک می‌کنن تست‌های حرفه‌ای‌تر و انعطاف‌پذیر‌تری بنویسیم.

تساوی مرجعی و ساختاری (Referential vs Structural Equality)

توی جاوااسکریپت (و قطعا تایپ‌اسکریپت)، تفاوت بین toBe و toEqual بسیار مهمه و انتخاب نادرست، می‌تونه به تست‌های اشتباه و گمراه‌کننده منجر بشه. داستان به این حقیقت برمی‌گرده که دو متغیر از نوع ‌reference در جاوااسکریپت، اگر رفرنس مشترکی نداشته باشند، حتی اگرمقادیر کاملا یکسانی داشته باشند، باز هم با هم مساوی نیستند. مثال:

let a = { x:1 }; let b = { x:1 }; console.log(a===b); // false

استفاده از toBe (مقایسه مرجعی)

متد toBe برای مقایسه مرجعی استفاده می‌شه و مشابه === یا ()Object.is عمل می‌کنه:

test('strings should be strictly equal', () => { expect('string').toBe('string'); }); test('numbers should be strictly equal', () => { expect(2).toBe(2); }); test('booleans should be strictly equal', () => { expect(true).toBe(true); expect(false).toBe(false); }); test('undefined and null should be strictly equal to themselves', () => { expect(undefined).toBe(undefined); expect(null).toBe(null); });

همه تست‌های فوق پاس میشن، اما وقتی به سراغ اشیا و آرایه‌ها می‌ریم، مشکلاتی پیش میاد:

test.fails('objects should not be strictly equal', () => { expect({ a: 1 }).toBe({ a: 1 }); // it fails! }); test.fails('arrays should not be strictly equal', () => { expect([1, 2, 3]).toBe([1, 2, 3]); // it fails! }); test.fails('functions should not be strictly equal', () => { expect(() => {}).toBe(() => {}); // it fails! });

پس می‌بینیم که استفاده از toBe در چنین مواردی راهکار درستی نیست و باید سراغ toEqual رفت.

استفاده از toEqual (مقایسه ساختاری)

متد toEqual برای مقایسه ساختاری استفاده می‌شه و به صورت shallow مقادیر رو بررسی می‌کنه:

test('objects with the same properties are equal', () => { expect({ a: 1, b: 2 }).toEqual({ a: 1, b: 2 }); }); test('arrays should be equal', () => { expect([1, 2, 3]).toEqual([1, 2, 3]); }); test('nested objects should be equal', () => { expect({ a: 1, b: { c: 2 } }).toEqual({ a: 1, b: { c: 2 } }); }); test('multi-dimensional arrays should be equal', () => { expect([1, [2, 3]]).toEqual([1, [2, 3]]); });

حالا تمامی تست های فوق به درستی پاس می شوند.

تفاوت toEqual و toStrictEqual

متد toStrictEqual نسخه سفت‌وسخت‌تری از toEqual هست:

class Person { constructor(name) { this.name = name; } } test('objects with undefined properties are equal to objects without those properties', () => { expect({ a: 1 }).toEqual({ a: 1, b: undefined }); // ✅ موفق می‌شه }); test('objects with undefined properties are NOT strictly equal to objects without those properties', () => { expect({ a: 1 }).not.toStrictEqual({ a: 1, b: undefined }); // ✅ موفق می‌شه }); test('instances are equal to object literals with the same properties', () => { expect(new Person('Alice')).toEqual({ name: 'Alice' }); // ✅ موفق می‌شه }); test('instances are NOT strictly equal to object literals with the same properties', () => { expect(new Person('Alice')).not.toStrictEqual({ name: 'Alice' }); // ✅ موفق می‌شه });

دقت کنید که در تست‌های فوق با not عدم تساوی رو بررسی کردیم.

کدام روش رو انتخاب کنیم؟

  • متد toBe: وقتی می‌خوایم مطمئن بشیم دو متغیر به یک شیء اشاره می‌کنن

  • متد toEqual: وقتی می‌خوایم مطمئن بشیم دو شیء محتوای یکسانی دارن (معمولاً اینو می‌خوایم)

  • متد toStrictEqual: وقتی می‌خوایم نسبت به toEqual دقیقتر باشیم و undefinedها و انواع شیء رو هم بررسی کنیم

مثال عملی با کلاس‌ها

اگر بخواهیم یک مثال از مفهوم تساوی اینبار در ساختار کلاسی بزنیم، بدین صورت میشه.

class Calculator { constructor() { this.result = 0; this.history = []; } add(num) { this.result += num; this.history.push(`+${num}`); return this; } subtract(num) { this.result -= num; this.history.push(`-${num}`); return this; } }

حالا تستش:

test('calculator should work correctly', () => { const calc = new Calculator(); calc.add(5).subtract(2); expect(calc).not.toBe(new Calculator()); // it fails! expect(calc).toEqual({ result: 3, history: ['+5', '-2'] }); // it success! });

همسان‌یابی نامتقارن (Asymmetric Matchers)

همسان‌یابی نامتقارن بدون شک یکی از قدرتمندترین ابزارهای تست نویسیه. matcher ها به ما اجازه می‌دن فقط قسمت‌هایی از داده‌ها رو تست کنیم که واقعاً مهم هستن و بقیه رو نادیده بگیریم. این کار باعث می‌شه تست‌هامون انعطاف‌پذیرتر و مقاوم‌تر نسبت به تغییرات غیرضروری بشن.

چرا از همسان‌یابی نامتقارن استفاده کنیم؟

تصور کنید یه API داریم که اطلاعات کاربر رو برمی‌گردونه:

{ id: "user-123", name: "John Doe", email: "john@example.com", createdAt: "2024-01-15T10:30:00Z", lastLogin: "2024-01-20T15:45:00Z", settings: { theme: "dark", notifications: true } }

اگه بخوایم این رو با toEqual تست کنیم، باید همه فیلدها رو دقیق مشخص کنیم. این کار تست رو خیلی سفت می‌کنه و با هر تغییر کوچیک (مثلاً تغییر timestamp)، تست fail میشه.

همسان‌یابی نامتقارن به ما اجازه می‌دن فقط روی چیزایی تمرکز کنیم که واقعاً برامون مهم هستن.


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

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-advanced

موفق باشید.

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