안녕하세요 개발이 체질의 최원혁 입니다.
Hardhat을 활용해서 내가 만든 Solidity코드의 TDD를 작성하는 방법에 대해 알아보겠습니다.
우선 이번 게시글에서는 Hardhat을 설치하고 베포 과정을 테스트하는 TDD를 작성해보겠습니다. 함수 & 변수 & 애러 테스트는 다름 게시글에서 알아보겠습니다.
# 1. Hardhat 이란?
hardhat은 이더리움 소프트웨어 개발환경으로 스마트컨트랙트와 Dapp 개발에 필요한 환경을 제공합니다. Solidity 컴파일, 디버깅, 배포 등 다양한 기능을 지원하며 반복되는 작업을 간단한 명령어로 통해 자동화하여 Dapp을 유연하게 개발할 수 있도록 도와줍니다.
또한 Hardhat을 통해 만들어진 많은 플러그인(plugin list : https://hardhat.org/hardhat-runner/plugins)이 있으며, 이를 통해 편리한 기능을 찾아 나의 개발 환경에 추가할 수 있습니다.
# 2. TDD(Test Driven Development)란?
TDD란 Test Driven Development의 약자로 "테스트 주도 개발"라고 합니다. 반복되는 테스트를 소프트웨어로 작성하는 하나의 방법론으로, 작은 단위의 테스트 케이스를 작성하여 검증을 통과하는 프로세스를 만들고, 반대로 악의적인 애러 상황을 내가 만든 프로그램에 발생시켜 대처방안을 테스트할 수도 있습니다.
TDD와 일반적인 개발방식과 큰 차이는 테스트 코드를 먼저 작성하고, 뒤에 실제 코드를 작성하는 것입니다.
디자인(설계)단계에서 개발의 목적과 정책을 정한 후, 이를 위해 구현되는 기능들을 간략하게 개발하여 테스트를 진행합니다.
그리고 작성된 코드가 테스트를 통과하게 되면 코드를 개발하고, 여기서 다시한번 똑같은 테스트를 진행하여 문제를 파악합니다. 통과가되면 그대로 개발이 진행되고, 문제가 발견되면 리펙토링을 통해 다시 테스트 코드를 구현합니다.
# 3. Solidity TDD 작성하기
지금부터 Hardhat을 활용하여 Solidity에 대한 테스트 코드를 작성하는 방법에 대해 알아 보겠습니다.
1. Hardhat.ts 설치하기
npx hardhat init
hardhat.ts 라이브러리를 설치할 폴더에서 npx hardhat init 명령어를 입력합니다. 만약, 최초의 실행이라면 설치가 진행 된후 위와 같은 화면이 나타나게 될 겁니다.
그리고 다음과 같인 hardhat을 설치하기 위한 세팅을 물어보는데, 이번 게시글에서는 Typescript를 활용할 예정이니
[Create a Typescript project]를 클릭해주세요.
그리고 설치할 root와 .gitignore, harthat tool box 설치에 대해 물어보는데, 모두 [Yes]해주세요.
*Github에 안올리실 분들은 .gitignore는 설치하지 않아도 됩니다.
설치가 끝나면 위와 같은 메세지가 나타나게됩니다. 이러면 성공적으로 설치를 완료하게 되었습니다!
2. 테스트용 SimpleCode.sol 코드 작성 및 컴파일(compile)
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
contract SimpleCode {
string private message;
address public owner;
constructor(string memory _message) {
message = _message;
owner = msg.sender;
}
}
여기 아주 간단한 Solidity 코드가 있습니다. 이번 TDD는 Solidity가 배포하는 과정에서의 테스트 코드를 작성것이기에, 배포에 필요한 간단한 코드를 구현했습니다.
SimpleCode.sol 파일은 contracts 폴더 안에 저장하면 됩니다.
2.1 hardhat compile
npx hardhat compile
npx hardhat compile 명령어를 통해 방금 작성한 코드를 컴파일(compile)해줍니다.
컴파일에 성공하면 artifacts cache 폴더가 생성된걸 볼 수 있습니다. 컴파일시 생성되는 파일들이 보관되어 있습니다.(ex abi,bytescode 등등...)
2.2 hardhat clean
npx hardhat clean
위에 생성된 폴더를 삭제하고 싶은 경우, npx hardhat clean명령어를 입력하면 됩니다.
3. TDD 작성하기
TDD를 작성할 폴더는 test/ 폴더에 저장하면됩니다. 테스트 코드를 작성하는 파일명은 주로 app.test.js, app.spec.js와 같이 작명됩니다.
3.1 SimpleCode.spec.ts 코드구현
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { expect,assert } from "chai";
import { ethers } from "hardhat";
import { SimpleCode } from "../typechain-types";
describe("SimpleConde.sol TDD 테스트 진행", function () {
async function deploySimpleCondeFixture() {
// SimpleCode.sol 배포하기
}
describe("배포 테스트", async () => {
it('owner에 저장된 데이터 테스트 진행 : ',async () => {
// owner 테스트 코드 작성
})
})
});
SimpleCode.sepc.ts의 코드 구조를 보면
describe("SimpleConde.sol TDD 테스트 진행")안에 function deploySimpleCondeFixture와 describe("배포 테스트")가 포함되 어 있고,
describe("배포 테스트")안에는 it('owner에 저장된 데이터 테스트 진행")가 포함되어 있습니다.
테스트 로그를 보면 각 함수에 적힌 메시지가 순차적으로 나타나는걸 알 수 있습니다. TDD는 작은 단위 케이스를 작성하여 테스트를 진행합니다. 케이스의 단위를 구조적으로 표현하면 SimpleCode.sepc.ts의 코드 구조가 됩니다.
3.2 컨트렉트 배포(deploy) 및 테스트 변수 생성
async function deploySimpleCondeFixture() {
const [owner,otherAccount] = await ethers.getSigners();
const _message = "TDD 배포 테스트 Contract"
const SimpleCondeFactory = await ethers.getContractFactory('SimpleCode');
const simpleConde:SimpleCode = await SimpleCondeFactory.connect(owner).deploy(_message);
return { simpleConde,owner,otherAccount };
}
• const [owner,otherAccount] = await ethers.getSigners()
- 랜덤으로 지갑주소를 생성해주는 함수입니다. 여러개의 지갑주소를 랜덤으로 생성할 수 있으며, 이를 활용하여 테스트를 진행할 수 있습니다. hardhat은 ethers 라이브러리를 사용합니다.
• const SimpleCondeFactory = await ethers.getContractFactory('컨트렉트 이름')
- SimpleCondeFactory는 SimpleCode.sol에 적힌 contract SimpleCode를 배포하기 위해 필요한 데이터를 만드는 함수 입니다.
• const simpleConde:SimpleCode = await SimpleCondeFactory.connect(owner).deploy(_message);
- contract SimpleCode를 배포하는 함수입니다.
- 배포하는 지갑주소는 .connect()의 파라미터에 담아주면 됩니다. *private key가 포함된 signer 지갑주소 데이터만 가능합니다.
- 우리는 SimpleCode에 constructor(string message)를 구현했으며, message에 들어가는 파라미터는 deploy()의 파라미터로 담아주면 됩니다.
• return { simpleConde,owner,otherAccount }
- 마지막에 지금까지 실행했던 함수를 returun으로 반환합니다. 그 이유는, 각 단위의 테스트 시나리오에서 활용하기 위함입니다. 3.3에서 다뤄보겠습니다.
3.3 컨트렉트 배포(deploy) 시나리오 TDD 작성
describe("배포 테스트", async () => {
it('owner주소 검증 테스트(성공할 경우): ',async () => {
const {simpleConde,owner,otherAccount} = await loadFixture(deploySimpleCodeFixture)
const currentOwner = await simpleConde.connect(owner).owner()
assert(currentOwner === owner.address,"조건이 실패할 경우 나타나는 메시지")
})
it('owner주소 검증 테스트(실패할 경우): ',async () => {
const {simpleConde,owner,otherAccount} = await loadFixture(deploySimpleCodeFixture)
const currentOwner = await simpleConde.connect(owner).owner()
assert(currentOwner === otherAccount.address,"조건이 실패할 경우 나타나는 메시지")
})
})
address public owner에 저장된 주소가 owner or otherAccount와 일치한지 검증하는 시나리오를 만들었습니다. 한줄 한줄 보면서 알아보겠습니다.
• const {simpleConde,owner,otherAccount} = await loadFixture(deploySimpleCodeFixture)
- 3.2에서 만든 배포 함수와 테스트 변수입니다.
• const currentOwner = await simpleConde.connect(owner).owner()
- contract SimpleCode에서 msg.sender가 저장된 변수 address public owner를 조회하는 get()를 호출합니다.
• assert(A === B,"조건이 실패할 경우 나타나는 메시지")
- TDD 시나리오를 작성하는 기능중 하나인 assert()함수입니다. 이는 자바스크립트 라이브러리 chai를 활용한 기능입니다.
- 현재 TDD는 assert에 currentOwner === owner.address || otherAccount.address 를 넣어 owner에 저장된 주소를 비교하는 시나리오를 만들었습니다.
4. TDD 파일 SimpleCode.sepc.ts 실행하기
npx hardhat test
위 명령어를 입력하면 위와 같은 테스트 로그를 확인할 수 있습니다.
우리는 3.3에서 두가지 시나리오를 작성하였고, assert()안에 owner에 저장된 주소를 고의로 틀린 주소와 비교하여 애러를 발생시켰습니다.
그 과정에서 우리가 작성한 메시지를 통해 진행된 TDD의 결과를 쉽게 파악할 수 있습니다.
# 4. 전체 코드 :
Hardhat.ts를 통해 TDD를 작성하였고, 작은 단위 별로 시나리오 케이스를 작성하여 단계별로 또는 별개의 상황별로 테스트를 진행 할 수 있었습니다.
댓글