3 د للقراءة
بيانات اختبار تقول الحقيقة
الـ fixtures تكذب بالإغفال. mongoose-test-factory يقرأ مخطّطك ويولّد بيانات تحترمه. كيف يقرّر ما يولّده، والأنماط الثلاثة التي أستعملها كل يوم.
كل مشروع Mongoose انضممت إليه كان فيه مجلد fixtures/، وكل واحد من تلك المجلدات كان خاطئًا بهدوء. حقل أُضيف إلى المخطّط ولم يُضَف إلى JSON. تعداد (enum) اكتسب قيمة لم يختبرها أحد. علامة required ظهرت فتوقّف نصف الـ fixtures عن الحفظ، فوسم أحدهم الاختبار بـ skip ومضى.
الـ fixtures لقطة لما كان عليه المخطّط يوم كُتبت. أما المخطّط فهو ما تعتقده الشيفرة اليوم. وُجد mongoose-test-factory ليغلق تلك الفجوة: يقرأ المخطّط ويولّد بيانات صالحة بحسبه، الآن.
#ابدأ من المخطّط
الحزمة إضافة (plugin). طبّقها، غلّف النموذج، فيحصل النموذج على factory().
import mongoose, { Schema } from 'mongoose'
import mongooseTestFactory, { withFactory } from 'mongoose-test-factory'
const userSchema = new Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
age: { type: Number, min: 18, max: 120 },
isActive: { type: Boolean, default: true },
})
userSchema.plugin(mongooseTestFactory)
export const User = withFactory(mongoose.model('User', userSchema))const user = User.factory().build()
// { name: 'John Doe', email: 'john.doe@example.com', age: 28, isActive: true }لم يُضبط شيء. مشت الإضافة على المخطّط، فرأت سلسلة إلزامية باسم name، وسلسلة فريدة باسم email، ورقمًا محصورًا بين 18 و120، وولّدت وفق ذلك. غيّر المخطّط فيتغيّر التشغيل التالي معه.
#كيف يقرّر ما يولّده
ثلاث طبقات، بترتيب الأولوية.
factoryTypeصريح على الحقل يفوز. نحو أربعين نوعًا مدمجًا:emailوphoneوpriceوslugوuuidوbirthdateوtagsوغيرها.- اسم الحقل.
userEmailوcontactEmailوemailكلها تبدو بريدًا إلكترونيًا.priceوcostوamountتبدو مالًا.createdAtيبدو طابعًا زمنيًا. - النوع ومُحقِّقاته.
Numberمعminوmaxيبقى داخل الحدود.Stringمعenumتختار منه.requiredيُحترم دائمًا.
حين يكون الاسم ملتبسًا، قُل ذلك في المخطّط:
const productSchema = new Schema({
name: { type: String, factoryType: 'title' },
vendor: { type: String, factoryType: 'company' },
price: { type: Number, factoryType: 'price' },
isActive: { type: Boolean, factoryType: 'active' }, // true about 80% of the time
website: { type: String, factoryType: 'url' },
})#ثلاثة أنماط، ثلاثة أنواع من الاختبارات
| الدالة | تُعيد | تمسّ قاعدة البيانات | استعملها في |
|---|---|---|---|
build() | كائنات عادية | لا | اختبارات الوحدة، أجسام الطلبات |
make() | نُسَخ Mongoose | لا | الحقول الافتراضية، الدوال، الخطافات |
create() | مستندات محفوظة | نعم | اختبارات التكامل |
const body = User.factory().build() // fast, no connection needed
const doc = User.factory().make() // an instance, not persisted
const saved = await User.factory(50).create() // fifty real rowsمعظم اختبارات الوحدة عندي لا تفتح اتصالًا أبدًا. build() سريع بما يكفي لأتوقّف كليًا عن تخزين بيانات الاختبار مؤقتًا.
#التجاوزات والعلاقات
القيمة المولّدة نقطة بداية. وما يهمّ الاختبار حقًا، حدّده صراحةً.
const admin = User.factory().with({ name: 'Admin', role: 'admin' }).build()
const author = await User.factory().create()
const posts = await Post.factory(5).with({ author: author._id }).create()المثال الثاني هو النمط الذي أستعمله أكثر: أصل واحد حقيقي، وN من الأبناء المولّدين يشيرون إليه. يملأ المصنع كل ما لم أذكره، فتُقرأ التوكيدات كنيّة لا كتحضير.
#ما الذي لن يفعله
لن يخترع قواعد عملك. إذا كان مجموع الطلب يجب أن يساوي مجموع بنوده، فالمخطّط لا يقول ذلك، والمصنع لا يمكنه أن يعرف. اكتب ذلك كتجاوز، أو كمُساعد صغير في إعداد الاختبار يبني طلبات متّسقة. ينبغي للبيانات المولّدة أن تكون صالحة بحسب المخطّط وصادقة فيما عداه؛ والتظاهر بفهم المجال سيكون نوعًا آخر من الكذب.
هل أفادك هذا؟
النقاش
تبني شيئًا من هذا القبيل؟
أخبرني بما تعمل عليه. أردّ في غضون يوم.