Solidity / Smart contract тест бичих

Smart contract тест бичих

Smart contract тест нь онцгой чухал — deploy хийсний дараа алдаа засах боломжгүй. Hardhat нь Mocha тест framework болон Chai assertion library-г ашиглана.

Тест файлын бүтэц

test/SimpleStorage.js файл үүсгэнэ:

javascript
const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("SimpleStorage", function () {
  // Тест бүрийн өмнө шинэ contract deploy хийнэ
  let storage;
  let owner;
  let otherAccount;

  beforeEach(async function () {
    // Тест хаягуудыг авна
    [owner, otherAccount] = await ethers.getSigners();

    // Contract factory авна
    const SimpleStorage = await ethers.getContractFactory("SimpleStorage");

    // Deploy хийнэ
    storage = await SimpleStorage.deploy();
    await storage.waitForDeployment();
  });

  // Тест бүлэг
  describe("Байрлуулалт", function () {
    it("Эзэмшигчийг зөв тохируулах ёстой", async function () {
      expect(await storage.owner()).to.equal(owner.address);
    });

    it("Анхны утга 0 байх ёстой", async function () {
      expect(await storage.get()).to.equal(0);
    });
  });

  describe("set функц", function () {
    it("Утгыг зөв хадгалах ёстой", async function () {
      await storage.set(42);
      expect(await storage.get()).to.equal(42);
    });

    it("Утгыг давтан өөрчлөх боломжтой байх ёстой", async function () {
      await storage.set(10);
      await storage.set(20);
      expect(await storage.get()).to.equal(20);
    });
  });
});

Тест ажиллуулах:

bash
npx hardhat test
код
  SimpleStorage
    Байрлуулалт
      ✓ Эзэмшигчийг зөв тохируулах ёстой
      ✓ Анхны утга 0 байх ёстой
    set функц
      ✓ Утгыг зөв хадгалах ёстой
      ✓ Утгыг давтан өөрчлөх боломжтой байх ёстой

  4 passing (1s)

describe ба it

javascript
describe("Бүлгийн нэр", function () {
  // Дэд бүлэг
  describe("Дэд бүлэг", function () {
    it("Тест нэр", async function () {
      // Тест код
    });
  });
});
  • describe — тестүүдийг бүлэглэнэ
  • it — нэг тест тодорхойлно
  • beforeEach — тест бүрийн өмнө ажиллах код

expect — assertion

javascript
// Тэнцүү байх
expect(value).to.equal(42);

// Үнэн байх
expect(isActive).to.be.true;
expect(isActive).to.be.false;

// Хаяг тэнцүү байх
expect(addr).to.equal(owner.address);

// BigInt харьцуулалт
expect(balance).to.be.greaterThan(0n);
expect(balance).to.be.lessThanOrEqual(1000n);

// ETH хэмжээ харьцуулалт
expect(balance).to.equal(ethers.parseEther("1.0"));

Event тест

Үйл явдлыг зөв emit хийж байгааг шалгах:

javascript
describe("ValueChanged үйл явдал", function () {
  it("set дуудахад ValueChanged гарах ёстой", async function () {
    // Эхний утга
    await storage.set(10);

    // emit хийгдэх үйл явдлыг шалгана
    await expect(storage.set(20))
      .to.emit(storage, "ValueChanged")
      .withArgs(10, 20); // (oldValue, newValue)
  });
});

Revert тест

Алдааны нөхцлийг шалгах:

solidity
// Contract-д нэмсэн функц
function setPositive(uint256 value) public {
    require(value > 0, "Утга 0-ээс их байх ёстой");
    storedValue = value;
}
javascript
describe("Алдааны нөхцлүүд", function () {
  it("0 утга оруулахад revert хийх ёстой", async function () {
    await expect(storage.setPositive(0)).to.be.revertedWith(
      "Утга 0-ээс их байх ёстой",
    );
  });

  it("Custom error шалгах", async function () {
    await expect(storage.restrictedFunction())
      .to.be.revertedWithCustomError(storage, "Unauthorized")
      .withArgs(otherAccount.address);
  });
});

ETH гүйлгээ тест

ETH илгээж авдаг contract-ийн тест:

solidity
// contracts/Bank.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Bank {
    mapping(address => uint256) public balances;

    function deposit() public payable {
        require(msg.value > 0, "ETH илгээх шаардлагатай");
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint256 amount) public {
        require(balances[msg.sender] >= amount, "Үлдэгдэл хүрэлцэхгүй");
        balances[msg.sender] -= amount;
        (bool success,) = payable(msg.sender).call{value: amount}("");
        require(success, "Илгээхэд алдаа");
    }
}
javascript
// test/Bank.js
const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("Bank", function () {
  let bank;
  let owner;
  let user;

  beforeEach(async function () {
    [owner, user] = await ethers.getSigners();
    const Bank = await ethers.getContractFactory("Bank");
    bank = await Bank.deploy();
    await bank.waitForDeployment();
  });

  describe("deposit", function () {
    it("ETH хийж, баланс нэмэгдэх ёстой", async function () {
      const depositAmount = ethers.parseEther("1.0");

      await bank.connect(user).deposit({ value: depositAmount });

      expect(await bank.balances(user.address)).to.equal(depositAmount);
    });

    it("ETH хийхгүй бол revert хийх ёстой", async function () {
      await expect(bank.connect(user).deposit({ value: 0 })).to.be.revertedWith(
        "ETH илгээх шаардлагатай",
      );
    });

    it("Contract-ын баланс нэмэгдэх ёстой", async function () {
      const depositAmount = ethers.parseEther("2.0");
      const bankAddress = await bank.getAddress();

      await bank.connect(user).deposit({ value: depositAmount });

      expect(await ethers.provider.getBalance(bankAddress)).to.equal(
        depositAmount,
      );
    });
  });

  describe("withdraw", function () {
    beforeEach(async function () {
      // Withdraw тест хийхийн өмнө эхлээд deposit хийнэ
      await bank.connect(user).deposit({ value: ethers.parseEther("1.0") });
    });

    it("Зөв дүн татан авах ёстой", async function () {
      const withdrawAmount = ethers.parseEther("0.5");
      const userBalanceBefore = await ethers.provider.getBalance(user.address);

      const tx = await bank.connect(user).withdraw(withdrawAmount);
      const receipt = await tx.wait();
      const gasUsed = receipt.gasUsed * receipt.gasPrice;

      const userBalanceAfter = await ethers.provider.getBalance(user.address);

      expect(userBalanceAfter).to.equal(
        userBalanceBefore + withdrawAmount - gasUsed,
      );
    });

    it("Үлдэгдэлгүй бол revert хийх ёстой", async function () {
      const tooMuch = ethers.parseEther("999.0");
      await expect(bank.connect(user).withdraw(tooMuch)).to.be.revertedWith(
        "Үлдэгдэл хүрэлцэхгүй",
      );
    });
  });
});

Цаг хугацааны тест

Contract-д block.timestamp ашигладаг бол Hardhat-ын цаг удирдах функцийг хэрэглэнэ:

javascript
const { time } = require("@nomicfoundation/hardhat-network-helpers");

it("Хугацаа дуусмагц татаж авах боломжтой байх ёстой", async function () {
  const deadline = (await time.latest()) + 7 * 24 * 60 * 60; // 7 хоног

  // ... contract deploy, deposit ...

  // Цагийг 7 хоног шилжүүлнэ
  await time.increaseTo(deadline + 1);

  // Одоо татан авах боломжтой
  await expect(contract.withdraw()).to.not.be.reverted;
});

Тестийн coverage

bash
npx hardhat coverage

Ямар мөрийг тест хамарсан, хамраагүйг харуулна. 100% coverage зорино.

Дараагийн хичээлд:

Sepolia testnet-д contract deploy хийж, Etherscan дээр verify хийнэ.