من میدانم که هیچ نمیدانم.
نحوه تست قراردادهای هوشمند (smart Contracts) اتریوم
پیش نیازها: درک اولیه از بلاک چین، اتریوم و جاوا اسکریپت.
کد کامل پروژه کار را می توان در Github یافت.
اهمیت تست نرم افزار
اگر میخواهید کد به روشی که در نظر گرفته شده است کار کند، آزمایش نرمافزار بسیار مهم است. دو نوع کلی تست نرم افزار وجود دارد: تست های واحد (unit tests) و تست های یکپارچه سازی (integration tests).
- بر روی هر تابع به صورت مجزا تمرکز کنید. : Unit tests
- تمرکز بر حصول اطمینان از کارکرد چندین بخش کد با هم همانطور که انتظار می رود. : Integration tests
نرم افزار بلاک چین نیز تفاوتی ندارد. ممکن است استدلال شود که برنامه های بلاک چین به دلیل تغییر ناپذیری نیاز به تاکید بیشتری بر آزمایش دارند.
تست های بلاک چین
مجموعه Truffle دو راه برای آزمایش قراردادهای هوشمند استحکام در اختیار ما قرار می دهد: تست های سالیدیتی و تست های جاوا اسکریپت. سوال این است که از کدام یک استفاده کنیم؟
پاسخ هر دو است.
تست های سالیدیتی
نوشتن تست ها در Solidity به ما این امکان را می دهد که آزمایش های لایه بلاک چین را اجرا کنیم. آنها به آزمایشها اجازه میدهند تا قراردادها و عملکردها را طوری فراخوانی کنند که انگار خودشان روی بلاک چین هستند. برای آزمایش رفتار داخلی قراردادهای هوشمند می توانیم:
- تست های واحد Unit tests را بنویسید تا مقادیر بازگشتی تابع و مقادیر متغیر حالت را بررسی کنید.
- تست های یکپارچه سازی integration tests را بنویسید که تعاملات بین قراردادها را آزمایش می کند. اینها تضمین می کنند که مکانیسم هایی مانند وراثت و تزریق وابستگی مطابق انتظار عمل کنند.
تست های جاوا اسکریپت
ما همچنین باید مطمئن شویم که قراردادهای هوشمند رفتار خارجی درستی از خود نشان می دهند. برای آزمایش قراردادهای هوشمند خارج از بلاک چین، از Web3js استفاده می کنیم، درست همانطور که DApp ما، این کار را می کند. هنگام فراخوانی قراردادهای هوشمند، باید اطمینان داشته باشیم که قسمت فرانت اند DApp ما به درستی کار خواهد کرد. اینها تحت آزمون های یکپارچه سازی integration tests قرار می گیرند.
پروژه نمونه
کد کامل پروژه را میتوانید در Github پیدا کنید
ما دو قرارداد هوشمند داریم: Background و EntryPoint.
Background
یک قرارداد داخلی است که بخش فرانت اند DApp ما با آن تعامل ندارد.
EntryPoint
قراردادی است که برای DApp ما طراحی شده است تا با آن تعامل داشته باشد. در کد خود به Background
رفرنس می دهد.
قراردادهای هوشمند
pragma solidity >=0.5.0;
contract Background {
uint[] private values;
function storeValue(uint value) public {
values.push(value);
}
function getValue(uint initial) public view returns(uint) {
return values[initial];
}
function getNumberOfValues() public view returns(uint) {
return values.length;
}
}
Figure 2: Background.sol
در بالا، قرارداد Background
خود را می بینیم. این سه تابع را نشان می دهد: storeValue(uint)
، getValue(uint)
و ()getNumberOfValues
. همه این توابع دستورالعمل های ساده ای دارند، بنابراین آزمایش واحد آنها آسان است.
pragma solidity >=0.5.0;
import "./Background.sol"
contract EntryPoint {
address public backgroundAddress;
constructor(address _background) public{
backgroundAddress = _background;
}
function getBackgroundAddress() public view returns (address) {
return backgroundAddress;
}
function storeTwoValues(uint first, uint second) public {
Background(backgroundAddress).storeValue(first);
Background(backgroundAddress).storeValue(second);
}
function getNumberOfValues() public view returns (uint) {
return Background(backgroundAddress).getNumberOfValues();
}
}
Figure 3: EntryPoint.sol
این قرارداد EntryPoint
ما است. یک آدرس برای قرارداد Background
ما به سازنده constructor تزریق می شود و به عنوان یک متغیر حالت به نام backgroundAddress
استفاده می شود. EntryPoint
سه تابع را نشان می دهد: getBackgroundAddress()
، storeTwoValues(uint, uint)
و ()getNumberOfValues
.
toreTwoValues(uint, uint)
یک تابع را در قرارداد Background
دو بار فراخوانی می کند، بنابراین آزمایش واحد این تابع به صورت مجزا دشوار خواهد بود. همین امر برای getNumberOfValues()
. اینها موارد خوبی برای تست های ادغام هستند.
سالیدیتی
در Solidity، ما می خواهیم unit tests و integration tests را برای قراردادهای هوشمند خود بنویسیم. بیایید با unit tests شروع کنیم زیرا آنها ساده تر هستند.
این اولین unit tests ما است : TestBackground :
pragma solidity >=0.5.0;
import "truffle/Assert.sol"
import "truffle/DeployedAddresses.sol"
import "../../../contracts/Background.sol"
contract TestBackground {
Background public background;
// Run before every test function
function beforeEach() public {
background = new Background();
}
// Test that it stores a value correctly
function testItStoresAValue() public {
uint value = 5;
background.storeValue(value);
uint result = background.getValue(0);
Assert.equal(result, value, "It should store the correct value");
}
// Test that it gets the correct number of values
function testItGetsCorrectNumberOfValues() public {
background.storeValue(99);
uint newSize = background.getNumberOfValues();
Assert.equal(newSize, 1, "It should increase the size");
}
// Test that it stores multiple values correctly
function testItStoresMultipleValues() public {
for (uint8 i = 0; i < 10; i++) {
uint value = i;
background.storeValue(value);
uint result = background.getValue(i);
Assert.equal(result, value, "It should store the correct value for multiple values");
}
}
}
Figure 4: TestBackground.sol
قرارداد،Background
ما را آزمایش میکند تا مطمئن شود:
- یک مقدار جدید در آرایه
values
خود ذخیره می کند. - مقادیر را با ایندگس آنها برمی گرداند.
- چندین مقدار را در آرایه
values
خود ذخیره می کند. - اندازه آرایه
values
آن را برمیگرداند.
اینTestEntryPoint
است، با یک unit test به نام ()testItHasCorrectBackground
برای قرارداد EntryPoint
ما:
pragma solidity >=0.5.0;
import "truffle/Assert.sol"
import "truffle/DeployedAddresses.sol"
import "../../../contracts/Background.sol"
import "../../../contracts/EntryPoint.sol"
contract TestEntryPoint {
// Ensure that dependency injection working correctly
function testItHasCorrectBackground() public {
Background backgroundTest = new Background();
EntryPoint entryPoint = new EntryPoint(address(backgroundTest));
address expected = address(backgroundTest);
address target = entryPoint.getBackgroundAddress();
Assert.equal(target, expected, "It should set the correct background");
}
}
Figure 5: TestEntryPoint.sol
این تابع تزریق وابستگی را تست میکند. همانطور که قبلا ذکر شد، سایر توابع در قراردادEntryPoint
ما نیاز به تعامل باBackground
دارند، بنابراین ما نمی توانیم آنها را به صورت مجزا آزمایش کنیم.
این توابع در integration tests ما آزمایش می شوند:
pragma solidity >=0.5.0;
import "truffle/Assert.sol"
import "truffle/DeployedAddresses.sol"
import "../../../contracts/Background.sol"
import "../../../contracts/EntryPoint.sol"
contract TestIntegrationEntryPoint {
BackgroundTest public backgroundTest;
EntryPoint public entryPoint;
// Run before every test function
function beforeEach() public {
backgroundTest = new BackgroundTest();
entryPoint = new EntryPoint(address(backgroundTest));
}
// Check that storeTwoValues() works correctly.
// EntryPoint contract should call background.storeValue()
// so we use our mock extension BackgroundTest contract to
// check that the integration workds
function testItStoresTwoValues() public {
uint value1 = 5;
uint value2 = 20;
entryPoint.storeTwoValues(value1, value2);
uint result1 = backgroundTest.values(0);
uint result2 = backgroundTest.values(1);
Assert.equal(result1, value1, "Value 1 should be correct");
Assert.equal(result2, value2, "Value 2 should be correct");
}
// Check that entry point calls our mock extension correctly
// indicating that the integration between contracts is working
function testItCallsGetNumberOfValuesFromBackground() public {
uint result = entryPoint.getNumberOfValues();
Assert.equal(result, 999, "It should call getNumberOfValues");
}
}
// Extended from Background because values is private in actual Background
// but we're not testing background in this unit test
contract BackgroundTest is Background {
uint[] public values;
function storeValue(uint value) public {
values.push(value);
}
function getNumberOfValues() public view returns(uint) {
return 999;
}
}
Figure 6: Solidity Integration test
میتوانیم ببینیم کهTestIntegrationEntryPoint
از یکBackground
به نامBackgroundTest
استفاده میکند که در خط 43 تعریف شده است تا به عنوان قرارداد ساختگی ما عمل کند. این به تستهای ما امکان میدهد بررسی کنند که آیاEntryPoint
توابع صحیح را در قراردادbackgroundAddress
که به آن ارجاع میدهد فراخوانی میکند یا خیر.
تست فایل ها در جاوا اسکریپت
در جاوا اسکریپت، یک integration test مینویسیم تا مطمئن شویم که قراردادها همانطور که انتظار داریم عمل میکنند تا بتوانیم یک DApp بسازیم که از آنها استفاده میکند.
در اینجا تست جاوا اسکریپت ما،entryPoint.test.js
آمده است:
const EntryPoint = artifacts.require("./EntryPoint.sol");
require('chai')
.use(require('chai-as-promised'))
.should();
contract("EntryPoint", accounts => {
describe("Storing Values", () => {
it("Stores correctly", async () => {
const entryPoint = await EntryPoint.deployed();
let numberOfValues = await entryPoint.getNumberOfValues();
numberOfValues.toString().should.equal("0");
await entryPoint.storeTwoValues(2,4);
numberOfValues = await entryPoint.getNumberOfValues();
numberOfValues.toString().should.equal("2");
});
});
});
Figure 7: entryPoint.test.js
با استفاده از توابع موجود در قراردادEntryPoint
، تست های جاوا اسکریپت اطمینان حاصل میکند که میتوان مقادیر خارج از بلاک چین را با ایجاد تراکنشهایی که تابعstoreTwoValues(uint, uint)
را هدف قرار میدهند به قرارداد هوشمند ارسال کرد (خط 15). بازیابی تعداد مقادیر ذخیره شده در بلاک چین با فراخوانی()getNumberOfValues
در خطوط 12 و 16 تست ها، ذخیره شدن آنها را تضمین می کند.
جمع بندی
وقتی نوبت به تست قراردادهای هوشمند میرسد، هرچه بیشتر بهتر. هنگام حصول اطمینان از اینکه همه مسیرهای ممکن اجرا نتایج مورد انتظار را برمیگردانند. از تست های Solidity سطح بلاک چین برای unit tests و integration tests استفاده کنید و از تست های جاوا اسکریپت برای integration tests در سطح DApp استفاده کنید.
نکاتی در این پروژه وجود دارد که میتوانست تستهای unit یا integration بیشتری نوشته شود، بنابراین اگر فکر میکنید میتوانید به این پروژه اضافه کنید، حتماً یک درخواست کشش به repo on Github ارسال کنید!
منبع : medium
مطلبی دیگر از این انتشارات
خدمات همتا به همتا (P2P) چیست؟
مطلبی دیگر از این انتشارات
چند نکته مهم در خرید و فروش رمزارز
مطلبی دیگر از این انتشارات
بررسی یکی از برترین بازی های متاورسی