نحوه تست قراردادهای هوشمند (smart Contracts) اتریوم

پیش نیازها: درک اولیه از بلاک چین، اتریوم و جاوا اسکریپت.کد کامل پروژه کار را می توان در Github یافت.اهمیت تست نرم افزاراگر می‌خواهید کد به روشی که در نظر گرفته شده است کار کند، آزمایش نرم‌افزار بسیار مهم است. دو نوع کلی تست نرم افزار وجود دارد: تست های واحد (unit tests) و تست های یکپارچه سازی (integration tests).بر روی هر تابع به صورت مجزا تمرکز کنید. : Unit tests تمرکز بر حصول اطمینان از کارکرد چندین بخش کد با هم همانطور که انتظار می رود. : Integration testsنرم افزار بلاک چین نیز تفاوتی ندارد. ممکن است استدلال شود که برنامه های بلاک چین به دلیل تغییر ناپذیری نیاز به تاکید بیشتری بر آزمایش دارند.تست های بلاک چینمجموعه Truffle دو راه برای آزمایش قراردادهای هوشمند استحکام در اختیار ما قرار می دهد: تست های سالیدیتی و تست های جاوا اسکریپت. سوال این است که از کدام یک استفاده کنیم؟پاسخ هر دو است.Figure 1: Test structure diagram تست های سالیدیتینوشتن تست ها در 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 &quot./Background.sol&quot 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 &quottruffle/Assert.sol&quot import &quottruffle/DeployedAddresses.sol&quot import &quot../../../contracts/Background.sol&quot 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, &quotIt should store the correct value&quot); } // Test that it gets the correct number of values function testItGetsCorrectNumberOfValues() public { background.storeValue(99); uint newSize = background.getNumberOfValues(); Assert.equal(newSize, 1, &quotIt should increase the size&quot); } // 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, &quotIt should store the correct value for multiple values&quot); } } }Figure 4: TestBackground.solقرارداد،Backgroundما را آزمایش می‌کند تا مطمئن شود:یک مقدار جدید در آرایهvaluesخود ذخیره می کند.مقادیر را با ایندگس آنها برمی گرداند.چندین مقدار را در آرایهvaluesخود ذخیره می کند.اندازه آرایهvaluesآن را برمی‌گرداند.اینTestEntryPointاست، با یک unit test به نام ()testItHasCorrectBackground برای قرارداد EntryPointما:pragma solidity >=0.5.0; import &quottruffle/Assert.sol&quot import &quottruffle/DeployedAddresses.sol&quot import &quot../../../contracts/Background.sol&quot import &quot../../../contracts/EntryPoint.sol&quot 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, &quotIt should set the correct background&quot); } }Figure 5: TestEntryPoint.solاین تابع تزریق وابستگی را تست می‌کند. همانطور که قبلا ذکر شد، سایر توابع در قراردادEntryPointما نیاز به تعامل باBackgroundدارند، بنابراین ما نمی توانیم آنها را به صورت مجزا آزمایش کنیم.این توابع در integration tests ما آزمایش می شوند:pragma solidity >=0.5.0; import &quottruffle/Assert.sol&quot import &quottruffle/DeployedAddresses.sol&quot import &quot../../../contracts/Background.sol&quot import &quot../../../contracts/EntryPoint.sol&quot 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, &quotValue 1 should be correct&quot); Assert.equal(result2, value2, &quotValue 2 should be correct&quot); } // 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, &quotIt should call getNumberOfValues&quot); } } // 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(&quot./EntryPoint.sol&quot); require('chai') .use(require('chai-as-promised')) .should(); contract(&quotEntryPoint&quot, accounts => { describe(&quotStoring Values&quot, () => { it(&quotStores correctly&quot, async () => { const entryPoint = await EntryPoint.deployed(); let numberOfValues = await entryPoint.getNumberOfValues(); numberOfValues.toString().should.equal(&quot0&quot); await entryPoint.storeTwoValues(2,4); numberOfValues = await entryPoint.getNumberOfValues(); numberOfValues.toString().should.equal(&quot2&quot); }); }); });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