میراثی منتقلی کے دوران تفریق ٹیسٹنگ کا استعمال کیسے کریں۔

وراثت کی منتقلی کا سب سے خطرناک لمحہ ضروری نہیں ہے کہ جب آپ ایک نیا نفاذ لکھنا شروع کریں۔ نیا عمل درآمد مکمل ہوتا دکھائی دے رہا ہے۔

آپ کا کوڈ مرتب ہوتا ہے، آپ کے ٹیسٹ پاس ہوتے ہیں، آپ کا فن تعمیر صاف ستھرا ہوتا ہے، اور آپ کی نئی سروس تیزی سے جواب دیتی ہے۔

پھر سب ایک ہی سوال کرنے لگتے ہیں۔

کیا آپ اب ٹریفک کو تبدیل کر سکتے ہیں؟

یہ وہ جگہ ہے جہاں اعتماد مشکل ہو جاتا ہے۔

ایک نیا نفاذ اپنے ٹیسٹ سوٹ کو پاس کر سکتا ہے اور پھر بھی اس نظام سے مختلف برتاؤ کر سکتا ہے جسے وہ تبدیل کرتا ہے۔

ہو سکتا ہے راؤنڈنگ تبدیل ہو گئی ہو، کالعدم اقدار کو مختلف طریقے سے ہینڈل کیا جا سکتا ہے، یا کسی غلطی کے نتیجے میں کامیاب جواب ہو سکتا ہے۔

ریکارڈز کو مختلف طریقے سے ترتیب دیا جا سکتا ہے، ضمنی اثرات مختلف ترتیب میں ہو سکتے ہیں، یا نقل مکانی کے دوران غیر دستاویزی کاروباری قواعد ضائع ہو سکتے ہیں۔

یہی وجہ ہے کہ ہم میراثی ہجرت کے دوران ثبوت کا ایک اور ذریعہ پسند کرتے ہیں۔ پرانے اور نئے نفاذ کو ایک ہی ان پٹ کے ساتھ چلائیں اور نتائج کا موازنہ کریں۔

یہ امتیازی جانچ کا بنیادی خیال ہے۔ صرف یہ پوچھنے کے بجائے کہ آیا نیا سسٹم امتحان پاس کر گیا ہے، آپ یہ بھی پوچھتے ہیں: ایک جیسے ان پٹ کو دیکھتے ہوئے، نیا سسٹم پرانے سسٹم سے مختلف کہاں برتاؤ کرتا ہے؟

فرق ثبوت ہے۔

کچھ کیڑے ہیں، کچھ جان بوجھ کر کی گئی بہتری ہیں، کچھ بے ضرر اظہار کے فرق ہیں، اور کچھ ایسے رویے کو ظاہر کرتے ہیں جس کے بارے میں کوئی نہیں جانتا تھا۔

یہ ٹیوٹوریل دکھاتا ہے کہ میراثی منتقلی کے دوران تفریق ٹیسٹنگ کا استعمال کیسے کریں:

  • پرانے اور نئے نفاذ کا موازنہ

  • وضاحت کریں کہ کیا مساوی سمجھا جانا چاہئے۔

  • اس کا موازنہ کرنے سے پہلے آؤٹ پٹ کو معمول بنائیں

  • ٹائم اسٹیمپ اور دیگر غیر مقررہ اقدار کو ہینڈل کرنا

  • غلطیوں اور ضمنی اثرات کا موازنہ

  • تفریق ٹیسٹ خود بخود چلائیں۔

  • رواداری کو متعارف کروائیں جہاں قطعی مساوات کا کوئی مطلب نہیں ہے۔

  • تضاد کا تجزیہ

  • پروڈکشن میں شیڈو ٹریفک کو محفوظ طریقے سے استعمال کریں۔

  • ہم AI کا استعمال درستگی کا تعین کیے بغیر اختلافات کی درجہ بندی کرنے کے لیے کرتے ہیں۔

  • تصدیق کریں کہ نیا نفاذ کٹ اوور کے لیے تیار ہے۔

مثالیں TypeScript اور Vitest کا استعمال کرتی ہیں، لیکن یہ نقطہ نظر زیادہ تر زبانوں اور منتقلی کی حکمت عملیوں پر لاگو ہوتا ہے۔

مقصد یہ ثابت کرنا نہیں ہے کہ دونوں نفاذ داخلی طور پر ایک جیسے ہیں۔ یہ ثبوت کو محفوظ کرنے کے بارے میں ہے کہ یہ موجود ہے۔ رویے کے لحاظ سے مساوی جب مساوات اہم ہو۔.

شرطیں

ساتھ چلنے کے لیے، آپ کو درج ذیل سے واقف ہونا چاہیے:

  • TypeScript یا اس سے ملتی جلتی زبان

  • یونٹ اور انضمام کی جانچ

  • غیر مطابقت پذیر کوڈ

  • API اور سروس کی حدود

  • میراثی جدید کاری

  • بنیادی مشاہداتی تصورات

آپ کو پہلے سے ہی منتقل ہونے والی خصوصیات کے بارے میں کچھ سمجھنا چاہئے۔

مثالی طور پر، آپ ان پٹس، آؤٹ پٹس، اہم کاروباری قواعد، بیرونی معاہدوں، ضمنی اثرات، اور غیر یقینی صورتحال کے معلوم علاقوں کو جانتے ہیں۔

تفریق ٹیسٹنگ بہترین کام کرتی ہے جب آپ پہلے سے ہی ان خصوصیات کے ارد گرد حدود بنا لیتے ہیں جنہیں آپ منتقل کرنا چاہتے ہیں۔

انڈیکس

تفریق کی جانچ واقعی ہمیں کیا بتاتی ہے۔

تصور کریں کہ آپ کی موجودہ درخواست کسی آرڈر کی حتمی قیمت کا حساب لگاتی ہے۔

میراث کا نفاذ یہ ہے:

type Order = {
  subtotal: number;
  customerType: "STANDARD" | "PREMIUM";
  country: string;
};

function legacyCalculateTotal(order: Order): number {
  let total = order.subtotal;

  if (order.customerType === "PREMIUM") {
    total *= 0.9;
  }

  if (order.country === "AR") {
    total -= 500;
  }

  return Math.max(total, 0);
}

منتقلی کے دوران ایک نیا نفاذ بنائیں۔

function newCalculateTotal(order: Order): number {
  const premiumDiscount =
    order.customerType === "PREMIUM"
      ? order.subtotal * 0.1
      : 0;

  const countryAdjustment =
    order.country === "AR"
      ? 500
      : 0;

  return Math.max(
    order.subtotal -
      premiumDiscount -
      countryAdjustment,
    0
  );
}

عمل درآمد مختلف نظر آتا ہے۔ اور یہ ٹھیک ہے۔ اہم بات یہ ہے کہ آیا وہ ایک جیسا سلوک پیدا کرتے ہیں۔

ایک سادہ تفریق ٹیسٹ دونوں کر سکتا ہے:

import { describe, expect, it } from "vitest";

describe("order total migration", () => {
  it("matches the legacy implementation", () => {
    const order: Order = {
      subtotal: 10000,
      customerType: "PREMIUM",
      country: "AR",
    };

    const legacy =
      legacyCalculateTotal(order);

    const migrated =
      newCalculateTotal(order);

    expect(migrated).toBe(legacy);
  });
});

اس ان پٹ کے لیے:

legacy → 8500
new    → 8500

اچھا تاہم، میچ کی ایک مثال شاذ و نادر ہی ثابت ہوتی ہے۔

قدر منظم سوالات سے آتی ہے جیسے:

same input
↓
legacy implementation ──→ result A

same input
↓
new implementation ─────→ result B

compare A and B

کوئی بھی تضاد آپ کو تحقیقات کے لیے کچھ دیتا ہے۔

ایک قابل مشاہدہ حد سے شروع کریں۔

پوری ایپلی کیشنز کا موازنہ کرکے شروع نہ کریں۔ شروع کرنے کے لیے ایک خصوصیت منتخب کریں۔

مثال کے طور پر:

Calculate Order Total
Generate Invoice
Approve Customer
Renew Subscription
Calculate Commission
Create Shipment

فرض کریں کہ منتقلی کی حد یہ ہے:

interface OrderProcessor {
  process(order: Order): Promise;
}

اب دو نفاذ ہیں:

LegacyOrderProcessor

NewOrderProcessor

یہ ایک مفید تفریق کی حد ہے کیونکہ دونوں ایک ہی تصوراتی ان پٹ حاصل کرتے ہیں اور ایک ہی تصوراتی پیداوار پیدا کرتے ہیں۔

موازنہ داخلی فن تعمیر سے مماثل کیے بغیر کیا جا سکتا ہے۔

ہجرت اہم ہے کیونکہ اس میں اکثر جان بوجھ کر ساختی تبدیلیاں شامل ہوتی ہیں۔

میراث کا نفاذ یہ ہے:

controller
→ service
→ SQL
→ provider SDK

نیا نفاذ یہ ہے:

use case
→ repository
→ gateway
→ events

آپ کو تفریق کی جانچ کے بارے میں فکر کرنے کی ضرورت نہیں ہے۔ ہمیں قابل مشاہدہ رویے میں دلچسپی لینے کی ضرورت ہے۔

ایک ہی ان پٹ کے ساتھ پرانے اور نئے نفاذ کو چلائیں۔

فرض کریں کہ دونوں نفاذ کو بے نقاب کریں:

interface OrderProcessor {
  process(order: Order): Promise;
}

آپ تشکیل دے سکتے ہیں:

const legacyProcessor =
  new LegacyOrderProcessor();

const newProcessor =
  new NewOrderProcessor();

پھر:

it("produces the same processed order", async () => {
  const input: Order = {
    id: "order-1",
    subtotal: 10000,
    customerType: "PREMIUM",
    country: "US",
  };

  const legacy =
    await legacyProcessor.process(
      structuredClone(input)
    );

  const migrated =
    await newProcessor.process(
      structuredClone(input)
    );

  expect(migrated).toEqual(legacy);
});

کے استعمال کو نوٹ کریں:

structuredClone(input)

اگر آپ کے نفاذ میں سے ایک ان پٹ کو تبدیل کرتا ہے تو یہ اہم ہے۔

علیحدہ کاپی کے بغیر، پہلی رن دوسری رن کو متاثر کر سکتی ہے۔

آپ کیا چاہتے ہیں:

same initial state

نہیں:

new implementation receives state modified by legacy implementation

اس قسم کی آلودگی غلط نتائج کا باعث بن سکتی ہے۔

خام پیداوار کا آنکھ بند کر کے موازنہ نہ کریں۔

تفریق ٹیسٹ کا پہلا ورژن درج ذیل ہے:

expect(newResult).toEqual(legacyResult);

کبھی کبھی یہ بالکل صحیح ہوتا ہے۔ لیکن دوسری بار میں غلط ہوں۔

میراثی نظام کی واپسی کا تصور کریں۔

{
  "id": "order-1",
  "total": 9000,
  "status": "PROCESSED",
  "generatedAt": "2026-09-09T10:00:01.231Z",
  "requestId": "legacy-f93a"
}

نیا نظام واپس آتا ہے:

{
  "requestId": "new-b517",
  "status": "PROCESSED",
  "generatedAt": "2026-09-09T10:00:01.416Z",
  "total": 9000,
  "id": "order-1"
}

خام آبجیکٹ کا موازنہ درج ذیل وجوہات کی بناء پر ناکام ہو سکتا ہے۔

requestId differs
timestamp differs

تاہم، کاروباری رویہ ایک جیسا ہو سکتا ہے۔

آپ کو یہ فیصلہ کرنے کی ضرورت ہے کہ کون سے فیلڈز بامعنی معاہدے کا حصہ ہیں۔

شاید:

id
total
status

مسئلہ

جبکہ:

generatedAt
requestId

قطعی مساوات کی ضرورت نہیں ہے۔

یہ معمول کی طرف جاتا ہے۔

ان کا موازنہ کرنے سے پہلے اقدار کو معمول بنائیں

نارملائزیشن کا مطلب ہے کہ ان کا موازنہ کرنے سے پہلے آؤٹ پٹ کو مشترکہ نمائندگی میں تبدیل کرنا۔

مقصد ڈیٹا کے کاروباری معنی کو تبدیل کرنا نہیں ہے۔ خیال یہ ہے کہ ان اختلافات کو دور کیا جائے جو متوقع ہیں لیکن موازنہ سے متعلق نہیں ہیں، جیسے کہ تیار کردہ درخواست کی IDs یا ٹائم اسٹیمپ، تاکہ آپ اپنی جانچ کو ان فیلڈز پر مرکوز کر سکیں جو دراصل دلچسپی کے رویے کی وضاحت کرتے ہیں۔

عملی طور پر، اس کا اکثر مطلب ہوتا ہے ایک معیاری نمائندگی، ایک چھوٹی، زیادہ مستحکم شکل جس میں صرف وہی معنی خیز فیلڈز ہوں جن کا آپ موازنہ کرنا چاہتے ہیں۔

مثال کے طور پر:

type ProcessedOrder = {
  id: string;
  total: number;
  status: string;
  generatedAt: string;
  requestId: string;
};

function normalizeOrder(
  order: ProcessedOrder
) {
  return {
    id: order.id,
    total: order.total,
    status: order.status,
  };
}

یہاں، ProcessedOrder کاروبار سے متعلقہ تمام فیلڈز اور اقدار شامل ہیں جو قانونی طور پر ایک دوسرے سے چلتے ہوئے مختلف ہو سکتے ہیں۔

کہ normalizeOrder() فنکشن برقرار ہے id, totalاور statusباہر نکلتے وقت generatedAt اور requestId. اس کا مطلب یہ ہے کہ یہاں تک کہ اگر دو نتائج قدرے مختلف اوقات میں بنائے گئے ہوں یا مختلف درخواست کے شناخت کنندگان کا استعمال کریں، تب بھی انہیں ایک جیسا سمجھا جا سکتا ہے۔

اب موازنہ کریں:

expect(
  normalizeOrder(migrated)
).toEqual(
  normalizeOrder(legacy)
);

یہ مساوات کے اصول کو واضح کرتا ہے۔

آپ کہہ رہے ہیں:

یہ فیلڈز اس موازنہ کے لیے متعلقہ رویے کی وضاحت کرتی ہیں۔

نارملائزیشن بھی سنبھال سکتی ہے:

تاہم، نارملائزیشن جان بوجھ کر ہونا چاہیے۔ بہت زیادہ ہٹانے سے نقل مکانی کے حقیقی کیڑے چھپ سکتے ہیں۔

ٹائم اسٹیمپ اور دیگر غیر مقررہ اقدار کو ہینڈل کرنا

میراثی نظام میں بہت سی غیر متعین اقدار ہوتی ہیں۔

مثال کے طور پر:

timestamps
UUIDs
random tokens
request IDs
trace IDs
database-generated IDs
unordered collections
provider-generated references

ان اقدار کا عین مطابق موازنہ کرنا تفریق والے خاندانوں کو مسلسل ناکام ہونے کا سبب بن سکتا ہے۔

ایک آپشن انحصار کنٹرول ہے۔

انحصار کے کنٹرول کا مطلب ہے غیر متعین ذرائع کو منتقل کرنا، جیسے موجودہ وقت یا ID جنریٹر، ایک انٹرفیس کے پیچھے جہاں انہیں جانچ کے دوران تبدیل کیا جا سکتا ہے۔

ہر عمل کو اصل گھڑی کو آزادانہ طور پر پڑھنے کے بجائے، ہم دونوں میں ایک ہی کنٹرول گھڑی لگاتے ہیں۔ اس سے انہیں یکساں قدر ملتی ہے اور وقت کو خود ہی بے معنی اخراج کا ذریعہ بناتا ہے۔

فرض کریں کہ آپ کا کوڈ استعمال کرتا ہے:

new Date()

آپ اس انحصار کو گھڑی سے بدل سکتے ہیں۔

interface Clock {
  now(): Date;
}

پھر دونوں نفاذ کو موصول ہوتا ہے:

const clock = {
  now: () =>
    new Date(
      "2026-09-09T10:00:00.000Z"
    ),
};

اب وقت مستقل طور پر بدلتا ہے۔

آئی ڈی بنانے کے لیے بھی یہی تکنیک استعمال کی جا سکتی ہے۔

interface IdGenerator {
  next(): string;
}

پھر ٹیسٹ فراہم کر سکتا ہے:

const ids = {
  next: () => "fixed-id",
};

اگر عدم استحکام پر قابو پانا ناقابل عمل ہے، تو اسے صرف اس صورت میں معمول بنائیں جب یہ اس طرز عمل کا حصہ نہ ہو جس کی آپ کو حفاظت کرنے کی ضرورت ہے۔

کاروباری سیمنٹکس کا موازنہ کرنا، نہ صرف JSON

دو نظام ایک ہی کاروباری ریاست کی نمائندگی کر سکتے ہیں لیکن مختلف نمائندگییں واپس کر سکتے ہیں۔

تصور کریں کہ آپ کے میراثی نظام میں درج ذیل ہیں:

{
  "status": 2
}

نئی مصنوعات میں شامل ہیں:

{
  "status": "APPROVED"
}

خام موازنہ کہتا ہے:

different

کاروباری موازنہ اس طرح کہا جا سکتا ہے:

equivalent

آپ سیمنٹک نارملائزیشن ٹول بنا سکتے ہیں۔

function normalizeStatus(
  status: number | string
) {
  if (status === 2) {
    return "APPROVED";
  }

  return status;
}

یہاں نارملائزر موجودہ عددی اقدار کو تبدیل کرتا ہے۔ 2 نئے نفاذ میں استعمال ہونے والے کاروباری معنی کے ساتھ: "APPROVED".

میں یہ دعوی نہیں کر رہا ہوں کہ تمام نمبرز اور سٹرنگز قابل تبادلہ ہیں۔ یہ ایک واضح مساوی اصول کو انکوڈ کرتا ہے جسے ہم نے پہلے ہی اس منتقلی کے لیے درست قرار دے دیا ہے۔

پھر:

expect(
  normalizeStatus(newResult.status)
).toBe(
  normalizeStatus(legacyResult.status)
);

یہ خاص طور پر مفید ہے جب نقل مکانی کی تبدیلیاں جان بوجھ کر ہوں۔

database schema
API representation
enumerations
provider-specific formats
internal identifiers

اہم سوالات یہ ہیں:

کیا قابل مشاہدہ کاروباری مضمرات وہی رہیں گے؟

نہیں:

کیا بائٹس ایک جیسے ہیں؟

معاہدے کے حصے کے طور پر خرابی کا موازنہ

کامیابی کا ردعمل پوری کارروائی نہیں ہے۔ غلطیاں بھی اہم ہیں۔

فرض کریں کہ آپ کا میراثی نفاذ لاپتہ صارفین کو مسترد کرتا ہے۔

throw new Error("Customer not found");

نیا نفاذ غلطی سے واپس آتا ہے:

return null;

یہ دونوں نفاذ ایک ہی غلط ان پٹ کے لیے بہت مختلف طریقے سے برتاؤ کرتے ہیں۔

میراثی ورژن واضح طور پر ناکام ہو جاتا ہے، لیکن نیا ورژن خود بخود ایک قدر لوٹاتا ہے جسے کالر کامیاب نتیجہ کے طور پر بیان کر سکتا ہے۔

اگر تفریق کی جانچ صرف ایسے معاملات میں چلتی ہے جہاں درست گاہک موجود ہیں، تو دونوں نفاذ ایک جیسے دکھائی دے سکتے ہیں اور یہ معاہدہ تبدیلی پوشیدہ رہے گی۔

اس لیے ہمیں ناکامی کے رویے کا بھی موازنہ کرنے کی ضرورت ہے۔

غلطیوں کو پکڑنے کے لیے کیس بنائیں۔

async function captureResult(
  operation: () => Promise
) {
  try {
    return {
      type: "success" as const,
      value: await operation(),
    };
  } catch (error) {
    return {
      type: "error" as const,
      error:
        error instanceof Error
          ? error.message
          : String(error),
    };
  }
}

ایک مددگار ایک غیر مطابقت پذیر آپریشن کو لپیٹتا ہے اور تمام ممکنہ نتائج کو ڈیٹا میں تبدیل کرتا ہے۔

اگر آپریشن کامیاب ہو جاتا ہے، تو یہ اس طرح کی چیز لوٹاتا ہے: type: "success" اور لوٹی ہوئی قیمت۔ جب کام رک جاتا ہے۔ catch بلاک اس استثنا کو اس طرح کی چیز میں تبدیل کرتا ہے: type: "error" اور پڑھنے کے قابل غلطی کا پیغام۔

یہ دونوں عملوں کے لیے موازنہ کی ایک ہی شکل فراہم کرتا ہے، ٹیسٹوں کو کامیابی اور ناکامی کا واضح طور پر موازنہ کرنے کی اجازت دیتا ہے، بجائے اس کے کہ دونوں کارروائیوں کا جائزہ لینے سے پہلے کسی استثنا کی وجہ سے ٹیسٹ کو روکا جائے۔

اب:

const legacy =
  await captureResult(() =>
    legacyProcessor.process(input)
  );

const migrated =
  await captureResult(() =>
    newProcessor.process(input)
  );

expect(migrated.type).toBe(legacy.type);

اگر غلطی معاہدہ کے لحاظ سے مادی ہے تو موازنہ کریں:

error category
HTTP status
error code
retryability
validation details

درست نمائندگیوں کا موازنہ کرنا ضروری نہیں ہے جب تک کہ صارف اس پر انحصار نہ کرے۔

ضمنی اثرات کا بھی موازنہ کریں۔

نقل مکانی کی سب سے آسان غلطیوں میں سے ایک ضمنی اثرات کو کھوتے ہوئے واپسی کی اقدار کو محفوظ رکھنا ہے۔

فرض کریں کہ دونوں نفاذ کی واپسی:

{
  "status": "PROCESSED"
}

لیکن میراثی ورژن بھی ہے:

persists the order
publishes an event
creates a payment
writes an audit entry

نیا ورژن آڈٹ اندراجات کو بھول جاتا ہے۔

تفریق رسپانس لیول ٹیسٹنگ اس پر گرفت نہیں کر سکتی۔ لہذا آپ ضمنی اثرات پر قبضہ کرنا چاہیں گے۔

مثال کے طور پر:

type Effect =
  | {
      type: "payment";
      orderId: string;
      amount: number;
    }
  | {
      type: "event";
      name: string;
      orderId: string;
    };

ٹیسٹ اڈاپٹر اسے لاگ ان کر سکتا ہے۔

class RecordingPaymentGateway {
  effects: Effect[] = [];

  async charge(
    orderId: string,
    amount: number
  ) {
    this.effects.push({
      type: "payment",
      orderId,
      amount,
    });
  }
}

اصل ادائیگی کی درخواست بھیجنے کے بجائے، یہ اڈاپٹر ریکارڈ کرتا ہے کہ درخواست نے کیا کرنے کی کوشش کی۔ effects صف

اسی خیال کو ایونٹ پوسٹنگ پر لاگو کیا جا سکتا ہے۔

class RecordingEvents {
  effects: Effect[] = [];

  async publish(
    name: string,
    orderId: string
  ) {
    this.effects.push({
      type: "event",
      name,
      orderId,
    });
  }
}

ایپلیکیشن اب بھی ہمیشہ کی طرح ادائیگی اور ایونٹ کے انحصار کو کال کرتی ہے۔ ایک ٹیسٹ ڈبل کیپچر کرتا ہے جو کسی بھی حقیقی بیرونی کام کو انجام دینے کے بجائے سٹرکچرڈ ڈیٹا کے طور پر کال کرتا ہے۔

آپ کے اپنے ریکارڈنگ اڈاپٹر کا استعمال کرتے ہوئے میراث اور منتقلی کے نفاذ کو چلانے کے بعد، آپ دو ریکارڈ شدہ اثرات کی فہرستوں کا موازنہ کر سکتے ہیں اور یہ تعین کر سکتے ہیں کہ آیا دونوں سسٹمز نے ایک جیسے قابل مشاہدہ ضمنی اثرات کی کوشش کی۔

امتیازی جانچ اب ہمیں موازنہ کرنے کی اجازت دیتی ہے:

expect(newEffects).toEqual(legacyEffects);

ایک بار پھر، درست ترتیب صرف اس صورت میں ضروری ہے جب حکم دینا ضروری ہو۔

صحیح مساوات غلط ہونے پر رواداری کا استعمال کریں۔

کچھ ڈومینز کو قطعی مساوی استعمال نہیں کرنا چاہیے۔

تصور کریں کہ ہجرت شدہ حساب کتاب پیدا کرتا ہے:

legacy → 34.333333333
new    → 34.333333334

کیا یہ ہجرت کا بگ ہے؟ شاید نہیں۔

فلوٹنگ پوائنٹ کیلکولیشنز رواداری کا جواز پیش کر سکتے ہیں۔

مثال کے طور پر:

expect(newResult).toBeCloseTo(
  legacyResult,
  6
);

یا ایک واضح موازنہ کی وضاحت کریں:

function withinTolerance(
  a: number,
  b: number,
  tolerance: number
) {
  return Math.abs(a - b) <= tolerance;
}

پھر:

expect(
  withinTolerance(
    migrated.total,
    legacy.total,
    0.01
  )
).toBe(true);

تاہم، رواداری کو ڈومین کی ضروریات سے آنا چاہیے۔ اسے صرف ناکام ٹیسٹوں کو دور کرنے کے لیے استعمال نہ کریں۔

جب مالیاتی نظام کی بات آتی ہے تو، ہر فیصد اہمیت رکھتا ہے۔ سائنسی حسابات کے لیے، بہت چھوٹے عددی فرق اہم ہو سکتے ہیں۔

مساوات ایک کاروباری اور انجینئرنگ کا فیصلہ ہے۔

دوبارہ استعمال کے قابل تفریق ٹیسٹ ہارنس بنانا

کیسز کا موازنہ کرنے سے آپ کو دوبارہ قابل استعمال ہارنس بنانے میں مدد مل سکتی ہے۔

مثال کے طور پر:

type DifferentialResult = {
  input: T;
  equivalent: boolean;
  legacy: unknown;
  migrated: unknown;
};

async function compareImplementations<
  TInput,
  TOutput
>(
  input: TInput,
  legacy: (
    input: TInput
  ) => Promise,
  migrated: (
    input: TInput
  ) => Promise,
  normalize: (
    output: TOutput
  ) => unknown
): Promise<
  DifferentialResult
> {
  const legacyResult =
    await legacy(
      structuredClone(input)
    );

  const migratedResult =
    await migrated(
      structuredClone(input)
    );

  const normalizedLegacy =
    normalize(legacyResult);

  const normalizedMigrated =
    normalize(migratedResult);

  return {
    input,
    equivalent:
      JSON.stringify(
        normalizedLegacy
      ) ===
      JSON.stringify(
        normalizedMigrated
      ),
    legacy: normalizedLegacy,
    migrated: normalizedMigrated,
  };
}

ہارنس چار کام کرتا ہے:

سب سے پہلے، چونکہ ہم ایک ہی ان پٹ کی الگ الگ نقلوں کا استعمال کرتے ہوئے میراث اور منتقلی کے نفاذ کو چلاتے ہیں، اس لیے ایک عملدرآمد ڈیٹا کو تبدیل نہیں کر سکتا جو دوسرے ایگزیکیوشن کو نظر آتا ہے۔

دوسرا، ہم دونوں آؤٹ پٹ کو ایک ہی راستے پر روٹ کرتے ہیں۔ normalize() فنکشن یہ مساوات کے اصولوں کو ہر امتحان میں دہرانے کے بجائے ایک جگہ پر لاگو کرتا ہے۔

تیسرا، ہم عام نتائج کا موازنہ کرتے ہیں اور نوٹ کرتے ہیں کہ آیا وہ ایک جیسے ہیں۔

آخر میں، یہ ان پٹ اور نارملائزڈ آؤٹ پٹ کو ایک ساتھ لوٹاتا ہے۔ ٹیسٹ رپورٹس ناکام موازنہ کا معائنہ کرنا آسان بناتی ہیں کیونکہ وہ آپ کو بالکل دکھا سکتی ہیں کہ کون سے معاملات سامنے آئے اور ہر عمل درآمد سے کیا نتیجہ نکلا۔

پھر:

const result =
  await compareImplementations(
    input,
    legacyProcessor.process.bind(
      legacyProcessor
    ),
    newProcessor.process.bind(
      newProcessor
    ),
    normalizeOrder
  );

expect(result.equivalent).toBe(true);

حقیقی نظاموں کے لیے، آپ عام طور پر انحصار نہیں کرتے: JSON.stringify() ایک حتمی مساوات کے طریقہ کار کے طور پر۔

اس مثال میں ہم ہارنس کو پڑھنے کے قابل رکھیں گے۔

پروڈکشن کوالٹی ٹولنگ میں، مناسب ساختی یا ڈومین کے لیے مخصوص موازنہ کار استعمال کریں۔

اہم حصہ یہ ہے کہ موازنہ منطق مرکزی ہے۔

حقیقی رویے سے ٹیسٹ کیسز بنائیں

ہاتھ سے لکھی ہوئی مثالیں مددگار ہیں۔ تاہم، ہجرت اکثر ناکام ہو جاتی ہے اگر کوئی انہیں دستی طور پر لکھنے کو تیار نہ ہو۔

مفید ان پٹ ذرائع میں شامل ہیں:

existing test fixtures
historical incidents
production-safe request samples
database records
boundary values
previous bug reports
known customer scenarios

فرض کریں کہ پیداوار مندرجہ ذیل آرڈر فارم کو ظاہر کرتی ہے:

const cases: Order[] = [
  {
    subtotal: 0,
    customerType: "STANDARD",
    country: "US",
  },
  {
    subtotal: 500,
    customerType: "PREMIUM",
    country: "AR",
  },
  {
    subtotal: 10000,
    customerType: "STANDARD",
    country: "AR",
  },
];

پہلا بلاک ٹیسٹ ڈیٹا ہے۔ یہ حقیقی دنیا کے استعمال میں مشاہدہ کردہ نمائندہ ان پٹ شکلوں کے ایک چھوٹے سے سیٹ کو پکڑتا ہے یا پروڈکشن آپریشن سے محفوظ طریقے سے دوبارہ تشکیل دیا جاتا ہے۔

اگلا بلاک خود ٹیسٹ ہے۔ it.each(cases) Vitest کو اس صف میں موجود تمام ان پٹس کے لیے ایک ہی مرتبہ ایک ہی تفریق کا موازنہ چلانے کی ہدایت کرتا ہے۔

یہ دو خدشات کو الگ کرتا ہے: حقیقت پسندانہ کیسز کی وضاحت کرنا اور تمام کیسز کا جائزہ لینے کے طریقے کی وضاحت کرنا۔

اب:

it.each(cases)(
  "matches legacy behavior",
  async (input) => {
    const legacy =
      await legacyProcessor.process(
        structuredClone(input)
      );

    const migrated =
      await newProcessor.process(
        structuredClone(input)
      );

    expect(
      normalizeOrder(migrated)
    ).toEqual(
      normalizeOrder(legacy)
    );
  }
);

حقیقی دنیا کی مثالیں ان مفروضوں کو ظاہر کرنے میں مدد کرتی ہیں جو مصنوعی ٹیسٹ کے اعداد و شمار اکثر چھوٹ جاتے ہیں۔ تاہم، پیداوار کے اعداد و شمار کو احتیاط سے ہینڈل کیا جانا چاہئے.

ہٹانا یا گمنامی:

personal data
credentials
tokens
financial identifiers
confidential business data

مقصد حساس پیداواری معلومات کو ٹیسٹ کی سہولت میں کاپی کرنا نہیں ہے، بلکہ مفید رویے کے نمونوں کو محفوظ کرنا ہے۔

تمام اختلافات کو حل کریں۔

تفریق کی غلطی کا خود بخود یہ مطلب نہیں ہے کہ نیا عمل درآمد خراب ہے۔

فرض کریں کہ آپ کو 200 تضادات ملتے ہیں۔ اسے ترتیب دیں۔

مجھے درج ذیل زمرے پسند ہیں:

migration defect
legacy defect intentionally preserved
intentional behavior change
representation difference
nondeterministic difference
test/comparator defect
unknown

مثال کے طور پر:

Input:
subtotal = 5000

Legacy:
discount = 0

New:
discount = 500

Classification:
unknown

ہماری تحقیقات سے پتہ چلتا ہے کہ نئے نفاذ میں درج ذیل تبدیلیاں ہیں:

amount > 5000

کو:

amount >= 5000

اب ایک فیصلہ کرنے کی ضرورت ہے۔

مندرجات درج ذیل ہیں:

accidental migration change

یا:

intentional bug fix

امتیازی جانچ فیصلوں کو ظاہر کرتی ہے۔ یہ آپ کے لیے فیصلے نہیں کرتا ہے۔

یہ سب سے بڑے فائدے میں سے ایک ہے۔

مختلف غلطیوں کی تحقیقات کے لیے AI کا استعمال کیسے کریں۔

بڑے پیمانے پر نقل مکانی کے نتیجے میں سینکڑوں یا اس سے بھی ہزاروں اختلافات ہو سکتے ہیں۔ AI اس کو حل کرنے میں مدد کر سکتا ہے۔

فرض کریں کہ آپ کی مندرجہ ذیل صورت حال ہے:

{
  "input": {
    "subtotal": 5000,
    "country": "AR"
  },
  "legacy": {
    "total": 4500
  },
  "new": {
    "total": 4000
  }
}

آپ اپنا ماڈل فراہم کر سکتے ہیں:

  • ان پٹ

  • دونوں آؤٹ پٹ

  • متعلقہ میراثی کوڈ

  • متعلقہ منتقلی کوڈ

  • تقابلی قواعد

پھر سوال پوچھیں۔

Analyze this differential test failure.

Identify the smallest behavioral difference that could
explain the mismatch.

Compare the legacy and migrated implementations.

Return:

1. observed difference,
2. relevant legacy branch,
3. relevant migrated branch,
4. likely cause,
5. evidence supporting the cause,
6. additional test cases that could confirm it.

Do not decide which behavior is correct.
Do not modify the code yet.

آخری ہدایت اہم ہے۔ AI یہ جاننے میں بہت مفید ہو سکتا ہے کہ دو نفاذ مختلف کیوں ہیں۔ اس تشخیص کو خاموشی سے کاروباری فیصلے میں ترجمہ نہیں کیا جانا چاہئے۔

AI کو فیصلہ کرنے نہ دیں کہ کون سا عمل درست ہے۔

تصور کریں کہ آپ کا میراثی نظام مندرجہ ذیل کام کرتا ہے:

Customer age 65 → no discount
Customer age 66 → discount

نیا نظام یہ کرے گا:

Customer age 65 → discount
Customer age 66 → discount

AI آپ کے کوڈ کو دیکھ کر کہہ سکتا ہے:

چونکہ سینئر رعایتیں عام طور پر 65 سال کی عمر سے شروع ہوتی ہیں، اس لیے نیا نفاذ زیادہ منطقی لگتا ہے۔

یہ متعلقہ نہیں ہے۔

کاروباری قوانین ہیں:

age > 65

ایک وجہ ہے۔ یا، میراثی رویے میں کیڑے شامل ہو سکتے ہیں۔

ہمیں ثبوت چاہیے۔

استعمال کریں:

requirements
existing tests
production behavior
business owners
historical tickets
commit history
contracts

AI ثبوت جمع کرنے اور خلاصہ کرنے میں مدد کر سکتا ہے۔ آپ کو اصول نہیں بنانے چاہئیں۔

تفریق کی جانچ قابل قدر ہے کیونکہ یہ آپ کو یہ بتانے دیتا ہے کہ اس سے پہلے کہ آپ غلطی سے اس فرق کو پیداواری رویے میں ترجمہ کر دیں۔

شیڈو ٹریفک کو محفوظ طریقے سے استعمال کرنے کا طریقہ

جیسا کہ آپ کے آف لائن تفریق ٹیسٹ کے نتائج بہتر ہوتے جاتے ہیں، آپ بعض اوقات حقیقی ٹریفک کے ساتھ رویے کا موازنہ کر سکتے ہیں۔ اسے اکثر شیڈونگ یا ٹریفک مررنگ کہا جاتا ہے۔

پیٹرن مندرجہ ذیل ہے:

real request
    │
    ├────────────→ legacy system
    │                  │
    │                  ↓
    │             real response
    │
    └────────────→ new system
                       │
                       ↓
                  shadow result

صارفین وصول کرتے رہیں گے:

legacy response

جب کہ نیا نظام آپ کی درخواست کی ایک کاپی پر کارروائی کرتا ہے۔

پھر موازنہ کریں:

legacy output
vs.
shadow output

یہ ایسے معاملات کو ظاہر کر سکتا ہے جنہیں ٹیسٹ سویٹ پکڑنے میں ناکام رہا۔

مثال کے طور پر:

unexpected null combinations
rare customer states
unusual international data
old records
large values
unusual sequence patterns

تاہم، شیڈو پر عملدرآمد کے لیے محتاط ڈیزائن کی ضرورت ہوتی ہے، خاص طور پر اگر آپریشن کے ضمنی اثرات ہوں۔

شیڈو ایگزیکیوشن کے نقلی ضمنی اثرات سے کیسے بچیں۔

سائے کا تصور کریں:

POST /payments

اگر وہ واقعی ادائیگی کرتے ہیں تو دونوں نظاموں میں سنگین مسائل ہیں۔

اسی کا اطلاق مندرجہ ذیل پر ہوتا ہے:

send email
create shipment
charge card
modify inventory
publish event
write external record

شیڈو کے نفاذ کو کوئی تباہ کن یا بیرونی طور پر نظر آنے والے اثرات کو انجام نہیں دینا چاہیے جب تک کہ محفوظ طریقے سے موجود نہ ہوں۔

ایک نقطہ نظر جسمانی گیٹ وے کو ریکارڈنگ اڈاپٹر سے تبدیل کرنا ہے۔

class ShadowPaymentGateway
  implements PaymentGateway {
  calls: PaymentRequest[] = [];

  async charge(
    request: PaymentRequest
  ) {
    this.calls.push(request);

    return {
      paymentId: "shadow",
    };
  }
}

نیا نفاذ اب بھی چلانے کی کوشش کرے گا۔

payment

لیکن اصل کارڈ کو چارج کرنے کے بجائے، شیڈو اڈاپٹر ریکارڈ کرتا ہے:

what would have been sent

اس کے بعد آپ اس ارادے کا میراثی ضمنی اثرات سے موازنہ کر سکتے ہیں۔

یہ فرق اہم ہے۔

compare behavior

اس کا مطلب یہ نہیں ہے:

duplicate production effects

کمال کا انتظار کیے بغیر انحراف کی پیمائش کریں۔

جب ہزاروں موازنہ چل رہے ہیں، بائنری:

pass / fail

یہ پوری کہانی نہیں بتا سکتا۔

اختلاف کی پیمائش کی جا سکتی ہے۔

مثال کے طور پر:

Requests compared:     100,000
Equivalent:             99,620
Different:                 380

Divergence rate:          0.38%

پھر ان 380 کو ترتیب دیں۔

250 timestamp differences
80 known intentional changes
30 comparator problems
15 migration defects fixed
5 still unexplained

معمول کے بعد:

meaningful unresolved divergence:
5 / 100,000
= 0.005%

اب بات چیت بہت زیادہ مخصوص ہو جاتی ہے۔

بجائے:

ایسا لگتا ہے کہ آپ کی منتقلی تیار ہے۔

آپ کہہ سکتے ہیں:

ہم نے 100,000 نمائندہ رنز کا موازنہ کیا اور پانچ غیر حل شدہ طرز عمل کے اختلافات پائے۔

آیا یہ قابل قبول ہے اس کا انحصار پانچ مختلف صورتوں پر ہے:

ایک خراب مالی لین دین کے نتیجے میں 100 سے زیادہ بے ضرر فارمیٹ میں فرق ہو سکتا ہے۔

لہذا صرف فیصد کا اندازہ نہ کریں۔ شدت کا اندازہ لگائیں۔

اگر آپ کٹ اوور کے لیے تیار ہیں تو کیسے جانیں۔

تفریق ٹیسٹنگ آفاقی حد فراہم نہیں کرتی ہے۔ لیکن یہ ثبوت دے سکتا ہے۔

کٹ اوور سے پہلے، میں درج ذیل سوالات کا جواب دینا چاہوں گا:

کیا آپ نے اہم ان پٹ کلاسز کا موازنہ کیا ہے؟

یہ صرف خوشی کا راستہ نہیں ہے۔

شامل ہیں:

boundaries
errors
historical bugs
large values
missing values
rare states

کیا معنی خیز اختلافات کی درجہ بندی کی گئی ہے؟

اجتناب:

we have 47 unexplained mismatches

کیا کوئی اہم اختلافات حل ہو گئے ہیں؟

خاص طور پر:

money
authorization
state transitions
data integrity
external contracts
idempotency

کیا کوئی جان بوجھ کر اختلافات کو دستاویز کیا گیا ہے؟

اگر نیا رویہ جان بوجھ کر مختلف ہے، تو اسے بیان کرنا ضروری ہے۔

کیا ضمنی اثرات برابر ہیں؟

یہ صرف ردعمل نہیں ہے۔

کیا آپ نے پیداوار میں اسی طرح کے معاملات کا تجربہ کیا ہے؟

اکیلے جامع فکسچر کافی نہیں ہوسکتے ہیں۔

کیا میں ہجرت واپس لے سکتا ہوں؟

امتیازی اعتماد خطرے کو کم کرتا ہے۔ اس کا مطلب یہ نہیں ہے کہ رول بیک ضروری نہیں ہے۔

اگر آپ ان سوالوں کا جواب دے سکتے ہیں، تو آپ کنٹرول شدہ کٹ اوور کے بہت قریب ہیں۔

عملی تفریق ٹیسٹ ورک فلو

میں جو ورک فلو استعمال کروں گا وہ درج ذیل ہے:

1. ایک خصوصیت منتخب کریں۔

مثال کے طور پر:

Process Order
Calculate Invoice
Approve Customer

ایک ساتھ تمام پلیٹ فارمز کا موازنہ نہ کریں۔

2. قابل مشاہدہ معاہدے کی تعریف

اہم نکات کی فہرست بنائیں۔

return value
status
error
database state
events
external calls

3. میراثی اور نئے اڈاپٹر بنائیں

ہم ایک ہی تصوراتی انٹرفیس کے ذریعے دونوں نفاذ کو بے نقاب کرتے ہیں۔

4. معمول کے اصولوں کی وضاحت کریں۔

فیصلہ کریں کہ کس طرح آگے بڑھنا ہے۔

timestamps
generated IDs
ordering
representation changes
optional values

بہت زیادہ ناکامیاں دیکھنے سے پہلے یہ کریں۔ بصورت دیگر آپ صرف نتیجہ پاس کرنے کے لیے موازنہ کرنے والے کو کمزور کر سکتے ہیں۔

5. معلوم مقدمات کا موازنہ

کے ساتھ شروع کریں:

existing tests
characterization cases
edge cases
historical bugs

6. ضمنی اثرات کو پکڑنا

اگر ضروری ہو تو ریکارڈنگ یا جعلی اڈاپٹر استعمال کریں۔

7. استعمال آٹومیشن

تمام تضادات کے لیے سٹرکچرڈ آؤٹ پٹ تیار کرتا ہے۔

مثال کے طور پر:

{
  "caseId": "case-493",
  "equivalent": false,
  "legacy": {},
  "migrated": {},
  "difference": {}
}

8. اختلافات کی درجہ بندی

استعمال کی قسم:

defect
intentional change
normalization issue
nondeterminism
unknown

9. نمائندہ حقیقی زندگی کی مثالیں شامل کریں۔

گمنام یا محفوظ طریقے سے دوبارہ تعمیر شدہ پروڈکشن پیٹرن استعمال کریں۔

10. جہاں مناسب ہو وہاں حقیقی ٹریفک کو شیڈو کریں۔

یہ ضمنی اثرات اور رازداری کے خطرات کو کنٹرول کرنے کے بعد ہی ممکن ہے۔

11. انحراف کی پیمائش

دونوں کو ٹریک کریں:

frequency
severity

12. کٹ اوور سے پہلے نامعلوم مسائل کو حل کریں۔

سب سے خطرناک زمرہ یہ ہے جب:

different

کہ:

different and nobody knows why

تفریق ٹیسٹ سے کیا ثابت نہیں کیا جا سکتا

امتیازی جانچ کی اہم حدود ہیں۔ یعنی نئے نظام کا پرانے نظام سے موازنہ کریں۔

اس کا مطلب ہے کہ میراثی نظام رویے کے لیے معیار بن جاتے ہیں۔ لیکن آپ کا میراثی نظام پہلے سے ہی غلط ہو سکتا ہے۔

فرض کریں:

legacy output = wrong
new output    = same wrong result

امتیازی امتحان پاس ہو جاتا ہے، لیکن رویہ درست نہیں ہے۔

یہی وجہ ہے کہ تفریق کی جانچ کو مکمل کرنا چاہئے:

specification tests
characterization tests
business requirements
security testing
performance testing
contract testing
domain review

جواب یہ ہے:

کیا آپ کا رویہ بدل گیا ہے؟

یہ خود بخود جواب نہیں دیتا۔

کیا یہ صحیح کام ہے؟

یہ فرق اہم ہے۔ میراثی درخواستیں ثبوت ہیں، قطعی سچائی نہیں۔

ہجرت کے خطرے کو امتیازی جانچ کے ساتھ ثبوت میں تبدیل کریں۔

مجھے اس ٹیکنالوجی کو پسند کرنے کی ایک اور وجہ ہے۔ تفریق کی جانچ کے بغیر، منتقلی کے مباحثے موضوعی بن سکتے ہیں۔

ایک شخص کہتا ہے:

ایسا لگتا ہے کہ ایک نیا نفاذ تیار ہے۔

ایک اور شخص کہتا ہے:

مجھے ابھی تک اس پر بھروسہ نہیں ہے۔

دونوں میں معقول جبلت ہو سکتی ہے، لیکن کوئی بھی بیان قابل پیمائش نہیں ہے۔

تفریق کی جانچ گفتگو کو بدل دیتی ہے۔

اب آپ کہہ سکتے ہیں:

12,000 cases compared
47 differences found
31 representation differences
9 intentional behavior changes
6 migration defects fixed
1 unresolved

یہ ایک بہت بہتر انجینئرنگ بحث ہے۔ آپ غیر یقینی کو قابل مشاہدہ اختلافات میں تبدیل کر رہے ہیں۔ پھر آپ فیصلہ کر سکتے ہیں کہ ان کے ساتھ کیا کرنا ہے۔

نتیجہ

وراثت کی منتقلی مکمل نہیں ہے کیونکہ نیا نفاذ ہماری اپنی آزمائش سے گزر چکا ہے۔

ایک زیادہ مشکل سوال یہ ہے کہ کیا اس کی جگہ لینے والا نظام اہم رویے کو محفوظ رکھتا ہے۔

امتیازی جانچ اس سوال کا جواب دینے کا ایک اور طریقہ فراہم کرتی ہے۔

ایک ہی ان پٹ کے ساتھ دونوں نفاذ کو چلائیں۔

آؤٹ پٹ کا موازنہ کریں۔

غلطیوں کا موازنہ کریں۔

ضمنی اثرات کا موازنہ کریں۔

صرف ان اختلافات کو معمول بنائیں جو واقعی اہم نہیں ہیں۔

باقی ہر چیز کی تحقیق کریں۔

اگر ممکن ہو تو، اپنے ٹیسٹ سویٹ میں غیر متوقع کیسز کو پکڑنے کے لیے نمائندہ پیداواری رویہ استعمال کریں۔

اب ہجرت کا حکم درج ذیل ہے:

Understand
↓
Characterize
↓
Refactor
↓
Migrate
↓
Compare
↓
Cut over

AI اس عمل کو بھی تیز کر سکتا ہے۔

یہ آپ کو موازنہ کرنے والے بنانے، غلطیوں کا تجزیہ کرنے، ایک جیسے فرقوں کو گروپ کرنے، کوڈ کے راستوں کا معائنہ کرنے اور ٹیسٹ کے اضافی کیس تجویز کرنے میں مدد کر سکتا ہے۔

تاہم، یہ تعین نہیں کرنا چاہیے کہ کون سا عمل درست ہے۔

یہ اب بھی ثبوت، ڈومین علم، اور انجینئرنگ کے فیصلے کی ضرورت ہے.

امتیازی جانچ کا مقصد غیر یقینی صورتحال کو مکمل طور پر ختم کرنا نہیں ہے۔

یہ غیر یقینی صورتحال کو ظاہر کرنے کے بارے میں ہے۔ پہلے پیداواری ٹریفک کو تبدیل کریں۔

اس کی وجہ یہ ہے کہ ہجرت کے دوران، یہ دریافت کرنا مفید ہے کہ نیا نظام مختلف طریقے سے برتاؤ کرتا ہے۔

پرانے سسٹم کو آف کرنے کے بعد اسے دریافت کرنا زیادہ مہنگا ہے۔

اوپر تک سکرول کریں۔