كيفية محاكاة العقود الذكية في Solidity للاختبار
تُعد الكائنات الوهمية (Mock objects) (opens in a new tab) نمط تصميم شائع في البرمجة كائنية التوجه. تأتي من الكلمة الفرنسية القديمة "mocquer" والتي تعني "السخرية من"، وتطورت لتعني "تقليد شيء حقيقي" وهو في الواقع ما نفعله في البرمجة. يرجى السخرية من عقودك الذكية فقط إذا كنت ترغب في ذلك، ولكن قم بمحاكاتها كلما استطعت. فهذا يجعل حياتك أسهل.
اختبار الوحدة للعقود باستخدام المحاكاة
تعني محاكاة العقد بشكل أساسي إنشاء إصدار ثانٍ من ذلك العقد يتصرف بشكل مشابه جدًا للإصدار الأصلي، ولكن بطريقة يمكن للمطور التحكم فيها بسهولة. غالبًا ما ينتهي بك الأمر بعقود معقدة حيث تريد فقط إجراء اختبار الوحدة لأجزاء صغيرة من العقد. تكمن المشكلة في ماذا لو كان اختبار هذا الجزء الصغير يتطلب حالة عقد محددة للغاية يصعب الوصول إليها؟
يمكنك كتابة منطق إعداد اختبار معقد في كل مرة يضع العقد في الحالة المطلوبة أو يمكنك كتابة محاكاة. محاكاة العقد أمر سهل باستخدام الوراثة. ما عليك سوى إنشاء عقد محاكاة ثانٍ يرث من العقد الأصلي. الآن يمكنك تجاوز (override) الدوال في محاكاتك. دعونا نرى ذلك بمثال.
مثال: ERC-20 خاص
نستخدم مثالًا لعقد ERC-20 يحتوي على وقت خاص أولي. يمكن للمالك إدارة المستخدمين الخاصين وسيُسمح لهؤلاء فقط بتلقي الرموز في البداية. بمجرد مرور وقت معين، سيُسمح للجميع باستخدام الرموز. إذا كنت مهتمًا، فنحن نستخدم الخطاف (hook) _beforeTokenTransfer (opens in a new tab) من عقود أوبن زبلن (OpenZeppelin) الجديدة الإصدار الثالث (v3).
pragma solidity ^0.6.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract PrivateERC20 is ERC20, Ownable {
mapping (address => bool) public isPrivateUser;
uint256 private publicAfterTime;
constructor(uint256 privateERC20timeInSec) ERC20("PrivateERC20", "PRIV") public {
publicAfterTime = now + privateERC20timeInSec;
}
function addUser(address user) external onlyOwner {
isPrivateUser[user] = true;
}
function isPublic() public view returns (bool) {
return now >= publicAfterTime;
}
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override {
super._beforeTokenTransfer(from, to, amount);
require(_validRecipient(to), "PrivateERC20: invalid recipient");
}
function _validRecipient(address to) private view returns (bool) {
if (isPublic()) {
return true;
}
return isPrivateUser[to];
}
}
والآن دعونا نقوم بمحاكاته.
pragma solidity ^0.6.0;
import "../PrivateERC20.sol";
contract PrivateERC20Mock is PrivateERC20 {
bool isPublicConfig;
constructor() public PrivateERC20(0) {}
function setIsPublic(bool isPublic) external {
isPublicConfig = isPublic;
}
function isPublic() public view returns (bool) {
return isPublicConfig;
}
}
ستحصل على إحدى رسائل الخطأ التالية:
PrivateERC20Mock.sol: TypeError: Overriding function is missing "override" specifier.PrivateERC20.sol: TypeError: Trying to override non-virtual function. Did you forget to add "virtual"?.
نظرًا لأننا نستخدم إصدار Solidity الجديد 0.6، يجب علينا إضافة الكلمة المفتاحية virtual للدوال التي يمكن تجاوزها، وكلمة override للدالة التي تقوم بالتجاوز. لذا دعونا نضيفها إلى كلتا دالتي isPublic.
الآن في اختبارات الوحدة الخاصة بك، يمكنك استخدام PrivateERC20Mock بدلاً من ذلك. عندما تريد اختبار السلوك خلال وقت الاستخدام الخاص، استخدم setIsPublic(false) وبالمثل setIsPublic(true) لاختبار وقت الاستخدام العام. بالطبع في مثالنا، يمكننا فقط استخدام مساعدات الوقت (time helpers) (opens in a new tab) لتغيير الأوقات وفقًا لذلك أيضًا. لكن فكرة المحاكاة يجب أن تكون واضحة الآن ويمكنك تخيل سيناريوهات حيث لا يكون الأمر سهلاً مثل مجرد تقديم الوقت.
محاكاة العديد من العقود
قد يصبح الأمر فوضويًا إذا كان عليك إنشاء عقد آخر لكل محاكاة على حدة. إذا كان هذا يزعجك، يمكنك إلقاء نظرة على مكتبة MockContract (opens in a new tab). إنها تسمح لك بتجاوز وتغيير سلوكيات العقود بسرعة (on-the-fly). ومع ذلك، فهي تعمل فقط لمحاكاة الاستدعاءات لعقد آخر، لذلك لن تعمل مع مثالنا.
يمكن أن تكون المحاكاة أكثر قوة
لا تنتهي قوى المحاكاة عند هذا الحد.
- إضافة دوال: ليس فقط تجاوز دالة معينة هو المفيد، بل أيضًا مجرد إضافة دوال إضافية. من الأمثلة الجيدة للرموز هو مجرد وجود دالة
mintإضافية للسماح لأي مستخدم بالحصول على رموز جديدة مجانًا. - الاستخدام في شبكات الاختبار (testnets): عندما تقوم بنشر واختبار عقودك على شبكات الاختبار مع تطبيقك اللامركزي (dapp)، فكر في استخدام إصدار محاكى. تجنب تجاوز الدوال إلا إذا كنت مضطرًا لذلك حقًا. فأنت تريد اختبار المنطق الحقيقي في النهاية. لكن إضافة دالة إعادة تعيين (reset) على سبيل المثال يمكن أن يكون مفيدًا حيث تقوم ببساطة بإعادة تعيين حالة العقد إلى البداية، دون الحاجة إلى نشر جديد. من الواضح أنك لن ترغب في وجود ذلك في عقد على الشبكة الرئيسية (Mainnet).