Quy trình Unit Test khi kiểm thử smart contract

Unit test là bước kiểm thử từng function riêng lẻ trong smart contract để đảm bảo mỗi phần hoạt động đúng, độc lập, và an toàn. Trong dự án blockchain – đặc biệt là các ứng dụng viết bằng Solidity – unit test là nền tảng không thể thiếu trước khi triển khai lên testnet hay mainnet.

Bài viết này sẽ hướng dẫn bạn quy trình unit test smart contract bằng Hardhat hoặc Foundry, đồng thời chỉ ra các lỗi thường gặp để bạn tránh ngay từ đầu.


Mục Tiêu Của Unit Test Trong Smart Contract

  • Đảm bảo từng hàm Solidity chạy đúng logic.
  • Phát hiện lỗi tính toán, quyền truy cập sai, revert không mong muốn.
  • Kiểm tra trạng thái contract sau mỗi hành động.

Quy Trình Unit Test Smart Contract

Bước 1: Xác Định Các Hàm Cần Test

Bạn cần viết test cho từng function công khai hoặc có ảnh hưởng đến logic chính, ví dụ:

  • deposit(), withdraw(), stake(), claimReward()
  • setAdmin(), pause(), emergencyWithdraw()

Đồng thời, cần test các biến trạng thái (mapping, balance, totalSupply) để đảm bảo cập nhật đúng.


Bước 2: Chuẩn Bị Môi Trường Test

  • Cài đặt công cụ:
    • Với Hardhat: bash npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
    • Với Foundry: bash curl -L https://foundry.paradigm.xyz | bash foundryup
  • Viết contract mẫu bằng Solidity (ví dụ dưới đây là Vault.sol):
solidity

contract Vault {
mapping(address => uint256) public balance;

function deposit() public payable {
require(msg.value > 0, "Invalid amount");
balance[msg.sender] += msg.value;
}

function withdraw(uint256 amount) public {
require(balance[msg.sender] >= amount, "Insufficient balance");
balance[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
}

Mục đích của bước này là gì?

Việc chuẩn bị môi trường test giúp bạn có:

  • Một sandbox riêng biệt, an toàn để kiểm thử smart contract mà không cần deploy lên testnet.
  • Một bộ công cụ hỗ trợ test tự động, log lỗi, đo gas, giả lập nhiều user khác nhau.
  • Khả năng viết và chạy test nhanh, lặp lại dễ dàng trong quá trình phát triển.

Tùy stack bạn dùng:

  • Hardhat: dễ dùng, nhiều plugin, phổ biến với dev mới.
  • Foundry: cực nhanh, viết test bằng Solidity, phù hợp dev dày dạn kinh nghiệm.

Bạn sẽ dùng môi trường này để:

  • Viết script kiểm thử.
  • Giả lập ví user khác nhau (signer).
  • Gọi thử hàm Solidity và so sánh kết quả thực tế với kết quả mong đợi.

Bước 3: Viết Test Cho Từng Trường Hợp

✅ Happy path (trường hợp đúng)

javascript

it("should deposit correctly", async () => {
await vault.connect(user1).deposit({ value: ethers.utils.parseEther("1") });
const balance = await vault.balance(user1.address);
expect(balance).to.equal(ethers.utils.parseEther("1"));
});

Giải thích:

  • Kiểm tra xem khi user nạp 1 ETH vào contract, thì contract có lưu đúng số dư cho user không.
  • Đây là trường hợp lý tưởng, xác nhận contract thực hiện đúng như thiết kế.

👉 Công dụng: Đảm bảo logic chính hoạt động bình thường.

❌ Input sai (trường hợp contract báo lỗi, revert case)

javascript

it("should revert when deposit is zero", async () => {
await expect(vault.connect(user1).deposit({ value: 0 })).to.be.revertedWith("Invalid amount");
});

Giải thích:

  • Kiểm tra nếu user cố tình nạp 0 ETH (hành vi không hợp lệ), thì contract phải từ chối và báo lỗi đúng thông điệp.

👉 Công dụng: Đảm bảo contract không bị lách luật bằng hành vi sai, bảo vệ an toàn tài chính.

⚠ Edge case (trường hợp đặc biệt dễ gây lỗi)

javascript

it("should revert withdraw if not enough balance", async () => {
await expect(vault.connect(user1).withdraw(ethers.utils.parseEther("1"))).to.be.revertedWith("Insufficient balance");
});

Giải thích:

  • User chưa từng nạp tiền mà lại muốn rút 1 ETH. Contract cần báo lỗi “Insufficient balance”.
  • Đây là edge case thường bị bỏ sót nếu không test kỹ.

👉 Công dụng: Đảm bảo logic kiểm tra điều kiện trước khi xử lý tiền, tránh mất mát quỹ.


Bước 4: Kiểm Tra Trạng Thái Sau Khi Gọi Hàm

Luôn kiểm tra:

  • Số dư người dùng (balanceOf)
  • Tổng token trong contract
  • Event emit đúng hay không
javascript

it("should emit Deposit event", async () => {
await expect(vault.connect(user1).deposit({ value: ethers.utils.parseEther("1") }))
.to.emit(vault, "Deposit")
.withArgs(user1.address, ethers.utils.parseEther("1"));
});

Bước 5: Tự Động Hóa Kiểm Thử (CI/CD)

  • Dùng npm test (Hardhat) hoặc forge test (Foundry) để chạy toàn bộ.
  • Kết hợp với GitHub Actions hoặc GitLab CI để kiểm thử tự động khi push code.
  • Bắt buộc test phải 100% pass trước khi deploy lên testnet.

Dễ Gặp Lỗi Khi Làm Unit Test

Lỗi thường gặpMô tả
Quên await khi gọi hàm asyncTest chạy sai hoặc skip
Chỉ test thành công, không test lỗiBỏ sót edge case nguy hiểm
Không kiểm tra trạng thái sau TXKhông phát hiện lỗi logic sai
Không dùng beforeEach() để reset trạng tháiTest bị phụ thuộc lẫn nhau
Sai address hoặc signer trong testKết quả không như mong muốn

Mẹo Để Viết Unit Test Hiệu Quả

  • Test cả success và fail case cho mỗi function.
  • Ưu tiên viết test song song với viết smart contract.
  • Đặt tên test rõ ràng: "reverts when user tries to withdraw without balance".
  • Sử dụng snapshot (Foundry) hoặc reset state (Hardhat) giữa các test case.

Kết Luận

Unit test là bước không thể thiếu khi kiểm thử smart contract viết bằng Solidity. Việc test từng hàm riêng biệt giúp bạn phát hiện bug sớm, đảm bảo độ tin cậy và tránh các sự cố nghiêm trọng sau khi triển khai lên mainnet.

Quay lại bài viết Tìm hiểu đầy đủ quy trình các bước kiểm smart contract.

Rate this post

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

[mwai_chatbot id="default"]