برنامه نویسی

استراتژی هایی برای نوشتن کد با قابلیت آزمایش بیشتر – یک تحلیل ضروری

Summarize this content to 400 words in Persian Lang

کد با قابلیت تست بیشتر

مشاهدات اولیه

بهبود در نوشتن تست و قابلیت تست کد بستگی به بلوغ تیم با موارد زیر دارد:

محیط توسعه
توسعه تست
معماری سیستم
درک و وضوح الزامات

نکات تحت پوشش در این سند بر اساس مطالعه نظری و دانش کسب شده در طول تمرین جامع توسعه آزمون است:

چنین شیوه‌هایی مفید و مرتبط خواهند بود، با این حال، استانداردهایی باید توسط تیم‌ها ایجاد و حفظ شود تا گردش کار توسعه کد و تست‌های نوشتن به یک جریان رایج تبدیل شود که برای همه آسان است.

آزمایش ها برای چیست؟

آزمایش ها به عنوان مستند برای توسعه دهندگان دیگر عمل می کنند:

درک و نگهداری از محصولات یک شرکت را تسهیل می کند.

آزمایش‌ها اطمینان را در توسعه و نگهداری جریان محصول نشان می‌دهند.

تست ها نشان می دهد که آیا کد شما به خوبی نوشته شده است یا خیر:

اگر نوشتن تست برای کد دشوار است، ممکن است بد نوشته، پیچیده، پرمخاطب، بیش از حد همراه یا پیچیده تر از آن چیزی باشد که می تواند باشد.

تست ها در مرحله توسعه، خرابی ها را تشخیص می دهند:

آنها از مواجه شدن کاربران با این نقص ها جلوگیری می کنند.

چرا داشتن کد قابل آزمایش برای عمر مفید یک نرم افزار مثبت است؟

جریان نرم افزار همیشه برای هماهنگی با دنیای واقعی و نیازهای کاربر تغییر می کند:

حفظ فرهنگ تست زنی یک تیم به نسبت زمان/فایده نوشتن تست ها بستگی دارد.

داشتن کد قابل آزمایش به معنای پذیرش و نگهداری یک ساختار/معماری پایه کد است که باعث ایجاد:

درک و توسعه آسان
کاهش زمان برای یافتن عیوب
درک بهتر جریان های پیچیده
گردش کار کم هزینه، خسته کننده و بی نظم

استراتژی هایی برای بهبود تست پذیری کد

درک کامل الزامات و طراحی سیستم

این شامل زیرساخت ارائه شده توسط کد می شود
نحوه تعامل اجزای داخلی و خارجی

استانداردسازی خطا (نحوه بازگرداندن آن)

استاندارد سازی پیام خطا

تعریف “نقاط شکست”.

استانداردسازی خطا (قالب)

استانداردسازی پیام خطا

نرمال سازی داده ها

جداسازی اجزای داخلی و خارجی به خوبی جدا شده است

بیایید نکات بالا را تجزیه کنیم:

زیرساخت های موجود

درک زیرساخت مستلزم دانستن منابع موجود، مانند سرورها، سرویس‌های ابری، پایگاه‌های داده و نحوه اجرای کد است. این امر بر تصمیمات طراحی، تصمیم گیری توسعه و سناریوهای آزمایش تأثیر می گذارد.

تعامل بین اجزای داخلی و خارجی

دانستن نحوه تعامل داخلی (ماژول ها، خدمات) و اجزای خارجی (API ها، پایگاه های داده، سیستم های شخص ثالث) برای تصمیم گیری، طراحی سیستم و سناریوهای آزمایش بسیار مهم است. به عنوان مثال، هنگام ادغام با یک API شخص ثالث، مهم است که به وضوح نحوه رسیدگی به خرابی ها یا تأخیرها را برای حفظ استحکام سیستم تعریف کنید. در مورد دوم، اجزای داخلی را به خوبی از اجزای خارجی جدا نگه داریدبه ما کمک می کند تا مسخره کنیم، احتمالات را شبیه سازی کنیم و کنترل همه سناریوهای ممکن در ویژگی را داشته باشیم.که در حال توسعه هستیم

استانداردسازی خطا و پیام خطا

چرا خطاها و پیام های خطا را استاندارد می کنیم؟

سازگاری: تضمین استفاده از اصطلاحات یکسان در سراسر سیستم.

سهولت تعمیر و نگهداری: تغییرات ایجاد شده در یک مکان، بدون نیاز به جستجو و جایگزینی در سراسر کد.

بین المللی شدن: ترجمه را با متمرکز کردن پیام ها و نمونه سازی آنها فقط یک بار تسهیل می کند.

آزمایش پذیری: پیام های قابل پیش بینی اعتبار سنجی استثناها را در تست ها آسان تر می کند.

استفاده مجدد: پیام های خطا را می توان به صورت یکسان در قسمت های مختلف برنامه استفاده کرد.

export const createErrors = (error_message: string, status_code: number) => {
const error = new Error(error_message);

return {
error: error,
status_code,
};
};

export const ErrorMessages = {
INVALID_PASSWORD: “Invalid password”,
USER_ALREADY_EXISTS: “User already exists”,
} as const;

export const createUser = async ({
email,
password,
}: {
email: string;
password: string;
}) => {
const validPassword = validatePassword(password);

if (!validPassword) {
return createErrors(ErrorMessages.INVALID_PASSWORD, 422); // breakpoint
}

const userExists = await findUserByEmail(email);

if (userExists) {
return createErrors(ErrorMessages.USER_ALREADY_EXISTS, 412); //breakpoint
}
};

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نرمال سازی داده ها

نرمال سازی داده ها چیست؟

نرمال‌سازی داده‌ها فرآیند تبدیل داده‌های بدون ساختار به قالبی سازگار و ساختاریافته قبل از استفاده از آن در بقیه سیستم است. این کمک می کند تا اطمینان حاصل شود که سیستم به طور قابل پیش بینی، پیوسته و جدا از اجزای خارجی کار می کند. این برای هر مؤلفه خارجی، منبع حافظه پنهان، منبع داده، پیام رسانی، ذخیره سازی … اعمال می شود.

چرا داده ها را عادی کنیم؟

تفکیک مسئولیت ها: تصمیم گیری فقط برای اجزای خارجی امکان پذیر است، برنامه به عنوان یک موجودیت مستقل در نظر گرفته می شود.

آزمایش پذیری: انواع و واسط هایی را ایجاد می کند که به طور کامل به برنامه مرتبط هستند، پیش بینی پذیری و تمسخر نتایج را تسهیل می کند.

مستندات: عادی سازی یک مستند ضمنی از قالب داده مورد انتظار ایجاد می کند.

const orders = await db.order.findMany();

// [
// {
// “id”: 1,
// “customer_id”: 101,
// “product_id”: 202,
// “quantity”: 2,
// “total_price”: 59.99,
// “created_at”: “…”,
// “status”: “shipped”,
// “delivery_date”: “…”,
// “notes”: “FRÁGIL”
// },
// …
// ]

type NormalizedOrder = {
orderId: number;
customerId: number;
productId: number;
quantity: number;
totalPrice: number;
status: string;
deliveryDate: string | null;
notes?: string;
};

// normalizando generalizando
function normalizeOrders(orders: any[]): NormalizedOrder[] {
return orders.map((order) => ({
orderId: order.id,
customerId: order.customer_id,
productId: order.product_id,
quantity: order.quantity,
totalPrice: Number(order.total_price),
status: order.status,
deliveryDate: order.delivery_date
? new Date(order.delivery_date).toISOString()
: null,
notes: order.notes,
}));
}

// normalizando por adapter

import { Order as PrismaOrder } from “@prisma/client”;
import { Order as MongoOrder } from “mongodb”;

function normalizePrismaOrder(order: PrismaOrder[]): NormalizedOrder {
return {
orderId: order.id,
customerId: order.customer_id,
productId: order.product_id,
quantity: order.quantity,
totalPrice: Number(order.total_price),
status: order.status,
deliveryDate: order.delivery_date
? new Date(order.delivery_date).toISOString()
: null,
notes: order.notes,
};
}

function normalizeOrmOrder(order: MongoOrder[]): NormalizedOrder {
return {
orderId: order._id,
customerId: order.customerId,
productId: order.productId,
quantity: order.quantity,
totalPrice: order.totalPrice,
status: order.status,
deliveryDate: order.deliveryDate ? order.deliveryDate.toISOString() : null,
notes: order.notes,
};
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

تردمیل دستورالعمل هایی که منطق بیش از حد پیچیده ای ندارند.

import client as mailClient from “@sendgrid/mail”;
import { db } from “./db”;
import dotenv from ‘dotenv’

dotenv.config()

const processOrder = async(data: {
order,
user_id
}, {
order: {
quantity: number;
item: string
}[],
user_id: string
}) => {

const user = await db.user.findUnique({
where: {id: user_id}
})
if (order.quantity 0) {
console.log(“Invalid quantity”);
return;
}

const validItems = [“Laptop”, “Smartphone”, “Tablet”];
if (!validItems.includes(order.item)) {
console.log(“Invalid item”);
return;
}

const message = {
from: “store@gmail.com”,
to: user.email,
subject: “Compra realizada”,
body: `corpo do email`,
};

const mailClient = mailClient.setApiKey(process.env.SENDGRID_KEY);

const data = await client.send(message);

return {ok: true}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

import client as mailClient from “@mail”;
import { db } from “./db”;
import dotenv from ‘dotenv’

dotenv.config()

const ErrorMessages = {
INVALID_QUANTITY: “Invalid quantity”,
INVALID_ITEM: “Invalid item”,
USER_NOT_FOUND: “User not found”,
MAIL_NOT_SENT: “Mail not sent”,
} as const; // mensagem de erros instanciadas em uma única fonte

const getUserById = async(id: number) => {
const user = await db.user.findUnique({
where: { id }
})

if (!user) {
console.log(ErrorMessages.USER_NOT_FOUND) // mensagem de erro padronizada
return null
}

return user;
}

const validateOrder = (order: {
quantity: number;
item: string;
}) => {
if (order.quantity 0) {
console.log(ErrorMessages.INVALID_QUANTITY); // mensagem de erro padronizada
return false;
}

const validItems = [“Laptop”, “Smartphone”, “Tablet”];
if (!validItems.includes(order.item)) {
console.log(ErrorMessages.INVALID_ITEM);
return false;
}

return true;
}

const sendOrderRequestedMail = async (email_to: string) => {
const message = {
from: “store@gmail.com”,
to: email_to,
subject: “Compra realizada”,
body: “corpo do email”,
}

const mailClient = mailClient.setApiKey(process.env.MAIL_CLIENT_KEY);

const mailSent = await client.send(message);

if(!mailSent) {
console.log(ErrorMessages.MAIL_NOT_SENT)
return { ok: false }
}
}

const processOrder = async({
orders,
user_id
}: {
orders: {
quantity: number;
item: string;
}[],
user_id: number;
} ) => {

const user = await getUserById(user_id); // desacoplamento do db

if (!user) {
return {ok: false} // breakpoint
}

for( const order of orders) {
if (!validateOrder(order)) {
return {ok: false} // breakpoint
}
}

await sendOrderRequestedMail(user.email); // desacoplamento do mail

return { ok: true }
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مزایای بازسازی مجدد

انعطاف پذیری : با معماری ماژولار و پیام های خطای استاندارد، اضافه کردن ویژگی های جدید و ایجاد تغییرات در کد بدون تأثیرگذاری بر سایر قسمت های سیستم آسان تر است.
قابلیت استفاده مجدد: توابع را می توان در زمینه های مختلف استفاده کرد (به عنوان مثال getUserById)
بیضه ها: تقسیم کد به این صورت باعث می شود که ساخت mock، stub و spy و سناریوهای آزمایشی کامل با یک تشک ساده و واضح آسان شود، علاوه بر این امکان تست در محدوده های کوچک تشک را نیز فراهم می کند که اساساً از علل ایجاد نقاط شکست هستند.

کد با قابلیت تست بیشتر

مشاهدات اولیه

  • بهبود در نوشتن تست و قابلیت تست کد بستگی به بلوغ تیم با موارد زیر دارد:

    • محیط توسعه
    • توسعه تست
    • معماری سیستم
    • درک و وضوح الزامات
  • نکات تحت پوشش در این سند بر اساس مطالعه نظری و دانش کسب شده در طول تمرین جامع توسعه آزمون است:

    • چنین شیوه‌هایی مفید و مرتبط خواهند بود، با این حال، استانداردهایی باید توسط تیم‌ها ایجاد و حفظ شود تا گردش کار توسعه کد و تست‌های نوشتن به یک جریان رایج تبدیل شود که برای همه آسان است.

آزمایش ها برای چیست؟

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

    • درک و نگهداری از محصولات یک شرکت را تسهیل می کند.
  • آزمایش‌ها اطمینان را در توسعه و نگهداری جریان محصول نشان می‌دهند.

  • تست ها نشان می دهد که آیا کد شما به خوبی نوشته شده است یا خیر:

    • اگر نوشتن تست برای کد دشوار است، ممکن است بد نوشته، پیچیده، پرمخاطب، بیش از حد همراه یا پیچیده تر از آن چیزی باشد که می تواند باشد.
  • تست ها در مرحله توسعه، خرابی ها را تشخیص می دهند:

    • آنها از مواجه شدن کاربران با این نقص ها جلوگیری می کنند.

چرا داشتن کد قابل آزمایش برای عمر مفید یک نرم افزار مثبت است؟

  • جریان نرم افزار همیشه برای هماهنگی با دنیای واقعی و نیازهای کاربر تغییر می کند:

    • حفظ فرهنگ تست زنی یک تیم به نسبت زمان/فایده نوشتن تست ها بستگی دارد.
  • داشتن کد قابل آزمایش به معنای پذیرش و نگهداری یک ساختار/معماری پایه کد است که باعث ایجاد:

    • درک و توسعه آسان
    • کاهش زمان برای یافتن عیوب
    • درک بهتر جریان های پیچیده
    • گردش کار کم هزینه، خسته کننده و بی نظم

استراتژی هایی برای بهبود تست پذیری کد

  • درک کامل الزامات و طراحی سیستم

    • این شامل زیرساخت ارائه شده توسط کد می شود
    • نحوه تعامل اجزای داخلی و خارجی
  • استانداردسازی خطا (نحوه بازگرداندن آن)

    • استاندارد سازی پیام خطا
  • تعریف “نقاط شکست”.

  • استانداردسازی خطا (قالب)

    • استانداردسازی پیام خطا
  • نرمال سازی داده ها

  • جداسازی اجزای داخلی و خارجی به خوبی جدا شده است


بیایید نکات بالا را تجزیه کنیم:

زیرساخت های موجود

درک زیرساخت مستلزم دانستن منابع موجود، مانند سرورها، سرویس‌های ابری، پایگاه‌های داده و نحوه اجرای کد است. این امر بر تصمیمات طراحی، تصمیم گیری توسعه و سناریوهای آزمایش تأثیر می گذارد.

تعامل بین اجزای داخلی و خارجی

دانستن نحوه تعامل داخلی (ماژول ها، خدمات) و اجزای خارجی (API ها، پایگاه های داده، سیستم های شخص ثالث) برای تصمیم گیری، طراحی سیستم و سناریوهای آزمایش بسیار مهم است. به عنوان مثال، هنگام ادغام با یک API شخص ثالث، مهم است که به وضوح نحوه رسیدگی به خرابی ها یا تأخیرها را برای حفظ استحکام سیستم تعریف کنید. در مورد دوم، اجزای داخلی را به خوبی از اجزای خارجی جدا نگه دارید
به ما کمک می کند تا مسخره کنیم، احتمالات را شبیه سازی کنیم و کنترل همه سناریوهای ممکن در ویژگی را داشته باشیم.
که در حال توسعه هستیم


استانداردسازی خطا و پیام خطا

چرا خطاها و پیام های خطا را استاندارد می کنیم؟

  • سازگاری: تضمین استفاده از اصطلاحات یکسان در سراسر سیستم.
  • سهولت تعمیر و نگهداری: تغییرات ایجاد شده در یک مکان، بدون نیاز به جستجو و جایگزینی در سراسر کد.
  • بین المللی شدن: ترجمه را با متمرکز کردن پیام ها و نمونه سازی آنها فقط یک بار تسهیل می کند.
  • آزمایش پذیری: پیام های قابل پیش بینی اعتبار سنجی استثناها را در تست ها آسان تر می کند.
  • استفاده مجدد: پیام های خطا را می توان به صورت یکسان در قسمت های مختلف برنامه استفاده کرد.
export const createErrors = (error_message: string, status_code: number) => {
  const error = new Error(error_message);

  return {
    error: error,
    status_code,
  };
};

export const ErrorMessages = {
  INVALID_PASSWORD: "Invalid password",
  USER_ALREADY_EXISTS: "User already exists",
} as const;

export const createUser = async ({
  email,
  password,
}: {
  email: string;
  password: string;
}) => {
  const validPassword = validatePassword(password);

  if (!validPassword) {
    return createErrors(ErrorMessages.INVALID_PASSWORD, 422); // breakpoint
  }

  const userExists = await findUserByEmail(email);

  if (userExists) {
    return createErrors(ErrorMessages.USER_ALREADY_EXISTS, 412); //breakpoint
  }
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید


نرمال سازی داده ها

نرمال سازی داده ها چیست؟

  • نرمال‌سازی داده‌ها فرآیند تبدیل داده‌های بدون ساختار به قالبی سازگار و ساختاریافته قبل از استفاده از آن در بقیه سیستم است. این کمک می کند تا اطمینان حاصل شود که سیستم به طور قابل پیش بینی، پیوسته و جدا از اجزای خارجی کار می کند. این برای هر مؤلفه خارجی، منبع حافظه پنهان، منبع داده، پیام رسانی، ذخیره سازی … اعمال می شود.

چرا داده ها را عادی کنیم؟

  • تفکیک مسئولیت ها: تصمیم گیری فقط برای اجزای خارجی امکان پذیر است، برنامه به عنوان یک موجودیت مستقل در نظر گرفته می شود.
  • آزمایش پذیری: انواع و واسط هایی را ایجاد می کند که به طور کامل به برنامه مرتبط هستند، پیش بینی پذیری و تمسخر نتایج را تسهیل می کند.
  • مستندات: عادی سازی یک مستند ضمنی از قالب داده مورد انتظار ایجاد می کند.
const orders = await db.order.findMany();

// [
//   {
//     "id": 1,
//     "customer_id": 101,
//     "product_id": 202,
//     "quantity": 2,
//     "total_price": 59.99,
//     "created_at": "...",
//     "status": "shipped",
//     "delivery_date": "...",
//     "notes": "FRÁGIL"
//   },
//   ...
// ]

type NormalizedOrder = {
  orderId: number;
  customerId: number;
  productId: number;
  quantity: number;
  totalPrice: number;
  status: string;
  deliveryDate: string | null;
  notes?: string;
};

// normalizando generalizando
function normalizeOrders(orders: any[]): NormalizedOrder[] {
  return orders.map((order) => ({
    orderId: order.id,
    customerId: order.customer_id,
    productId: order.product_id,
    quantity: order.quantity,
    totalPrice: Number(order.total_price),
    status: order.status,
    deliveryDate: order.delivery_date
      ? new Date(order.delivery_date).toISOString()
      : null,
    notes: order.notes,
  }));
}

// normalizando por adapter

import { Order as PrismaOrder } from "@prisma/client";
import { Order as MongoOrder } from "mongodb";

function normalizePrismaOrder(order: PrismaOrder[]): NormalizedOrder {
  return {
    orderId: order.id,
    customerId: order.customer_id,
    productId: order.product_id,
    quantity: order.quantity,
    totalPrice: Number(order.total_price),
    status: order.status,
    deliveryDate: order.delivery_date
      ? new Date(order.delivery_date).toISOString()
      : null,
    notes: order.notes,
  };
}

function normalizeOrmOrder(order: MongoOrder[]): NormalizedOrder {
  return {
    orderId: order._id,
    customerId: order.customerId,
    productId: order.productId,
    quantity: order.quantity,
    totalPrice: order.totalPrice,
    status: order.status,
    deliveryDate: order.deliveryDate ? order.deliveryDate.toISOString() : null,
    notes: order.notes,
  };
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید


تردمیل دستورالعمل هایی که منطق بیش از حد پیچیده ای ندارند.

import client as mailClient from "@sendgrid/mail";
import { db } from "./db";
import dotenv from 'dotenv'

dotenv.config()

const processOrder = async(data: {
  order,
  user_id
}, {
  order: {
    quantity: number;
    item: string
  }[],
  user_id: string
}) => {


  const user = await db.user.findUnique({
    where: {id: user_id}
  })
  if (order.quantity  0) {
    console.log("Invalid quantity");
    return;
  }

  const validItems = ["Laptop", "Smartphone", "Tablet"];
  if (!validItems.includes(order.item)) {
    console.log("Invalid item");
    return;
  }

  const message = {
    from: "store@gmail.com",
    to: user.email,
    subject: "Compra realizada",
    body: `corpo do email`,
  };

  const mailClient = mailClient.setApiKey(process.env.SENDGRID_KEY);

  const data = await client.send(message);

  return {ok: true}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید


import client as mailClient from "@mail";
import { db } from "./db";
import dotenv from 'dotenv'

dotenv.config()


const ErrorMessages = {
  INVALID_QUANTITY: "Invalid quantity",
  INVALID_ITEM: "Invalid item",
  USER_NOT_FOUND: "User not found",
  MAIL_NOT_SENT: "Mail not sent",
} as const; // mensagem de erros instanciadas em uma única fonte


const getUserById = async(id: number) => {
  const user = await db.user.findUnique({
    where: { id }
  })

  if (!user) {
    console.log(ErrorMessages.USER_NOT_FOUND) // mensagem de erro padronizada
    return null
  }

  return user;
}

const validateOrder = (order: {
  quantity: number;
  item: string;
}) => {
  if (order.quantity  0) {
    console.log(ErrorMessages.INVALID_QUANTITY); // mensagem de erro padronizada
    return false;
  }

  const validItems = ["Laptop", "Smartphone", "Tablet"];
  if (!validItems.includes(order.item)) {
    console.log(ErrorMessages.INVALID_ITEM);
    return false;
  }

  return true;
}

const sendOrderRequestedMail = async (email_to: string) => {
  const message = {
    from: "store@gmail.com",
    to: email_to,
    subject: "Compra realizada",
    body: "corpo do email",
  }

  const mailClient = mailClient.setApiKey(process.env.MAIL_CLIENT_KEY);


  const mailSent = await client.send(message);

  if(!mailSent) {
    console.log(ErrorMessages.MAIL_NOT_SENT)
    return { ok: false }
  }
}

const processOrder = async({
  orders,
  user_id
  }: {
  orders: {
    quantity: number;
    item: string;
  }[],
  user_id: number;
  } ) => {

  const user = await getUserById(user_id); // desacoplamento do db

  if (!user) {
    return {ok: false} // breakpoint
   }



  for( const order of orders) {
    if (!validateOrder(order)) {
      return {ok: false} // breakpoint
    }
  }

  await sendOrderRequestedMail(user.email); // desacoplamento do mail

  return { ok: true }
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مزایای بازسازی مجدد

  • انعطاف پذیری : با معماری ماژولار و پیام های خطای استاندارد، اضافه کردن ویژگی های جدید و ایجاد تغییرات در کد بدون تأثیرگذاری بر سایر قسمت های سیستم آسان تر است.

  • قابلیت استفاده مجدد: توابع را می توان در زمینه های مختلف استفاده کرد (به عنوان مثال getUserById)

  • بیضه ها: تقسیم کد به این صورت باعث می شود که ساخت mock، stub و spy و سناریوهای آزمایشی کامل با یک تشک ساده و واضح آسان شود، علاوه بر این امکان تست در محدوده های کوچک تشک را نیز فراهم می کند که اساساً از علل ایجاد نقاط شکست هستند.


نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا