Project coin 경과
니코 선생님의 coin 클론코딩이 거의 막바지이다. 강의가 몇개 남지 않았지만 테스트 코드를 싹싹 핥아 먹으며 공부하기 위해 강의를 보기 전 테스트 코드를 만들고 강의를 보고 있다.
문제 발생
스스로 테스트 코드를 만들며 아무리 생각해도 이상한 현상을 발견했다.
테스트하려는 코드
func Blockchain() *blockchain {
once.Do(func() {
b = &blockchain{
Height: 0,
}
checkpoint := 블록체인_불러오기()
if checkpoint == nil {
//새로운 블록 추가
} else {
//기존 블록체인 변수 b에 할당
}
})
return b
}
테스트 코드
func TestBlockchain(t *testing.T) {
t.Run("불러올 블록체인 없을 때", func(t *testing.T) {
dbStorage = fakeDB{
fakeLoadChain: func() []byte {
return nil
},
}
bc := Blockchain()
if bc.Height != 1 {
t.Error("Height가 1인 블록체인이 만들어 져야합니다.")
}
})
t.Run("불러올 블록체인 있을 때", func(t *testing.T) {
dbStorage = fakeDB{
fakeLoadChain: func() []byte {
b := &blockchain{
Height: 2,
}
return utils.ToBytes(b)
},
}
bc := Blockchain()
if bc.Height != 2 {
t.Errorf("Height가 %d인 블록체인이 불려져야 하지만, Height가 %d인 블록체인이 불려집니다.", 2, bc.Height)
}
if reflect.TypeOf(bc) != reflect.TypeOf(&blockchain{}) {
t.Error("Blockchain()이 반환해야 하는 타입이 옳지 않습니다.")
}
})
}
현상
testing.T가 가진 t.Run을 통해 sub test를 만들어 테스트를 하는 모습이다.
나는 JetBrain의 goLand를 사용하고 있으며 이 Idle의 기능 중 sub test를 하나씩 실행할 수 있는 기능이 있다. 이 기능을 사용하여 sub test를 하나씩 실행하면 결과는 PASS. 하지만 TestBlockchain 전체를 실행하면 FAIL이 뜬다..?
=== RUN TestBlockchain
=== RUN TestBlockchain/불러올_블록체인_없을_때
=== RUN TestBlockchain/불러올_블록체인_있을_때
chain_test.go:53: Height가 2인 블록체인이 불려져야 하지만, Height가 1인 블록체인이 불려집니다.
--- FAIL: TestBlockchain (0.00s)
--- PASS: TestBlockchain/불러올_블록체인_없을_때 (0.00s)
--- FAIL: TestBlockchain/불러올_블록체인_있을_때 (0.00s)
FAIL
원인
강의를 보자마자 '아! 왜 기존 코드를 제대로 살피지 않았을까!' 라는 생각이 바로 들었다. 원인은 once.Do() 함수 때문이었다. 이 함수의 기능은 2021.08.26 - [Study/Design Pattern] - [Design Pattern] 싱글톤 패턴(Singleton Pattern) with Golang 에 기입해 두었었다. 글이 너무 구리다. 옛날에 포스팅한 내용들은 어떻게 해야하는것일까..
그렇다. 결국 이들은 blockchain의 코드를 공유하기에 테스트 해야하는 함수를 잘 살피고 이해할 필요가 있다. (너무 당연한 얘기이지만 아직 테스트 코드에 미숙하기에 너무 테스트 코드만 집중해 버렸다.)
해결 방법
해결 방법은 굉장히 간단했다.
func TestBlockchain(t *testing.T) {
t.Run("불러올 블록체인 없을 때", func(t *testing.T) {
dbStorage = fakeDB{
fakeLoadChain: func() []byte {
return nil
},
}
bc := Blockchain()
if bc.Height != 1 {
t.Error("Height가 1인 블록체인이 만들어 져야합니다.")
}
})
t.Run("불러올 블록체인 있을 때", func(t *testing.T) {
once = sync.Once{} // 수정 부분
dbStorage = fakeDB{
fakeLoadChain: func() []byte {
b := &blockchain{
Height: 2,
}
return utils.ToBytes(b)
},
}
bc := Blockchain()
if bc.Height != 2 {
t.Errorf("Height가 %d인 블록체인이 불려져야 하지만, Height가 %d인 블록체인이 불려집니다.", 2, bc.Height)
}
if reflect.TypeOf(bc) != reflect.TypeOf(&blockchain{}) {
t.Error("Blockchain()이 반환해야 하는 타입이 옳지 않습니다.")
}
})
}
once를 다시 초기화시켜 마치 처음 불려지는 것 처럼 속이는 것이다.
수정 후 테스트
=== RUN TestBlockchain
=== RUN TestBlockchain/불러올_블록체인_없을_때
=== RUN TestBlockchain/불러올_블록체인_있을_때
--- PASS: TestBlockchain (0.00s)
--- PASS: TestBlockchain/불러올_블록체인_없을_때 (0.00s)
--- PASS: TestBlockchain/불러올_블록체인_있을_때 (0.00s)
PASS
느낀 점
테스트 코드를 작성할 때에 조심해야 할 부분을 배울 수 있었다. 테스트 코드는 기존의 함수를 테스트 하는 것이다. 테스트를 위한 코드이다. 테스트 코드가 중심이 되어선 안된다.
'개인 프로젝트 > coin' 카테고리의 다른 글
[Project Coin] 모니터링 시스템 whatap (0) | 2022.08.15 |
---|---|
[Project Coin] 단위 테스트(2) - 추상화를 적용한 함수 테스트, + 몽키 패치 (0) | 2022.08.12 |
[Project Coin] 단위 테스트(1) _ Unit Test란? & Table Test (0) | 2022.08.10 |