본문 바로가기
코딩 아카이브/Golang

TDD와 cleancode로 만들면서 배우면서 Golang backend -3

by SteadyForDeep 2022. 4. 15.
반응형

지난 글

 

지난 글들을 통해서 TDD가 무엇인지 어떻게 하는지

그리고 그게 왜 CleanCode와 연관이 있고

어떻게 작업능력을 향상시키는지 알아봤다.

 

그러면 이제는 바로 서버를 하나 만들어보도록 하자.

 

HTTP 서버가 무엇이고 만든다는 것은 어떤 의미인지

다른 글에서도 포스팅한 적이 있으므로 이번 글에서는 생략한다.

 

Go에서는 공식적으로 지원하는 라이브러리들이 있으므로

그것들을 이용해서 서버를 만들어 보자.

 

본문에서도 지적하지만 API서버는

기본적으로 클라이언트의 '요청'을 바탕으로 어떤 작업을 수행할지

그리고 어떤 결과가 도출될지 결정이 되도록 설계되는데

TDD를 위해서는 테스트가 작성되어야 하고

이때 최소한의 설계를 원칙으로 한다면 클라이언트의 존재는 아직 없다.

 

그러니까 클라이언트를 만들고

이 클라이언트가 어떤 '요청'을 보내서

서버가 수행 결과를 얻는 방식이 아니라

정말 단지 기능적으로 확인하고자 하는 테스트로

서버가 잘 동작하는지 알아내야한다.

이런 과정이 어떻게 가능할까?

본문에서는 mocking 을 이용해서 테스트를 진행할 수 있다고 말한다.

 

mocking은 mock 이라는 객체를 이용해서 테스트를 하는 방식으로

프로젝트에서 어떤 객체가 특정 기능 수행에 있어 외부에 종속적일 때

즉 스스로 어떤 기능을 수행할 수 없고

반드시 소통을 통해서 수행이 발생할 때

소통에 필요한 최소한의 기능을 가진 임시 객체를 만들어서

기능을 테스트하는 것이 가능하고

이때 만들어지는 임시 객체를 mock이라고 한다.

 

이는 내가 테스트하고자 하는 대상의 기능이

프로젝트 전반에 걸쳐서 어떤 역할을 수행하는지는 관심이 없고

당장 원하는 결과를 반환하는지에만 관심을 가지는 것으로 이해할 수 있다.

이해에 도움을 주는 좋은 글을 하나 공유한다.

https://www.crocus.co.kr/1555

 

[Mockito] Mock 개념(Mock Object)

단위 테스트를 하기 위해서는 한번에 메서드 하나만을 실행해 보는 것인데 이러한 메서드가 다른 네트워크, 데이터베이스 등등 제어하기 어려운 것들에 의존하고 있다면 어떻게 단위 테스트를

www.crocus.co.kr

 

이렇게 mock 객체는 단지 기능을 수행하기 위해 임시로 흉내를 낸 객체이므로

mocking을 과하게 적용할 경우

특정 mock에 너무 편향된 기능을 구현할 가능성이 높아진다.

따라서 mock을 구성하거나 mocking 테스트를 진행할 때는

어느정도의 추상화까지 인터페이스로 노출할지 정하고

정말 필요한 부분만 mock으로 구현하는 것이 좋다고들 말한다.

 

이론은 여기까지 하고 내용을 잘 따라 가 보자.

본문의 내용은 게임의 플레이어들이 있을 때

우승 횟수를 조회하고 하록하는 서버를 만들어 보도록 짜여져 있다.

 

다른 모든 것들을 막론하고 TDD의 1원칙은 '테스트를 먼저 짜라'이다.

아래처럼 코드를 짜보도록 하자.

여기서는 서버가 하는 수행이 PlayerServer()로 실행된다.

이 수행이 뭔지 알기 위해서는 net/http에서 제공하는 기능을 알 필요가 있는데

우선 기본적으로 서버는 "요청"이 들어오면 "응답"을 내기 때문에

서버의 동작을 위해서는 이 두가지가 필수로 필요하다.

 

그리고 net/http에서 제공하는 서버는

HandleFunc 라는 함수를 통해서 서버가 수행할 기능을 지정할 수 있는데

이때 HandleFunc가 받는 함수는 반드시 foo(responseWriter, *request) 의 형태를 가져야 한다.

 

그래서 결국 foo는 response를 받은 후에 어떤 처리를 거치고

responseWriter에 그 내용을 담는 기능을 수행한다.

그래서 기능이 수행되면 responseWriter를 받아서 거기 있는 내용을 파싱하고

원하는 내용이 들어있는지 확인한다.

 

함수의 자세한 부분은 Go의 공식 docs를 확인해보면 바로 알 수 있다.

여기서는 http.NewRequest를 이용해서 '요청'에 해당하는 데이터를 생성하는 것이 가능하다.

 

원래는 클라이언트가 보내줘야 하는 부분이지만

http에 구현된 기능을 통해서 가상으로 요청을 생성해 내는 과정이다.

그리고 우리는 서버의 기능이 수행된 이후에 '응답'을 받아야 하므로

'응답'을 기록할 수 있는 NewRecorder를 테스트용으로 하나 생성한다.

 

여기서 주목할 점은 서버를 아직 빌드하지 않았고 프로세스를 실행하지 않았지만

요청이 있고 응답을 담을 수 있는 레코더가 있으므로

오로지 테스트 프로세스만을 이용해 in-memory 테스트를 진행할 수 있다는 것이다.

이것이 mocking의 장점이라고 할 수 있다.

 

이렇게 서버를 띄우지 않고도 서버의 기능을 테스트 하는 것이 가능하다.

그리고 이제 첫번째 Red 싸인을 얻었다.

이걸 Green으로 바꾸고 리팩토링 해보자.

리팩토링을 어차피 할거기 때문에 Green으로 달리는 것 까지는 하드코딩으로 진행한다.

 

여기서 Greet 함수 개념이 나오는데 잠깐 다음으로 미루고

Fprint를 이용해서 Writer에 내용을 적을 수 있다는 것만 알아두자.

 

이렇게 테스트가 성공하게 된다.

 

여기서 의문이 들 수 있다.

다른 player이름이 들어오면 어떡함?

그냥 20만 출력하는게 무슨 의미가 있음?

저렇게 한다고 우리가 원하는 기능이 충족된거임?

 

일단 대답부터 하자면 저렇게 해도 된다.

왜냐하면 Red에서 Green으로 바꼈기 때문이다.

 

코드를 짜는데 있어 3가지 기본 원칙으로 많이 이야기되는 것이

DRY, YAGNI, KISS 인데

TDD의 가장 좋은 장점은 YAGNI를 지킬 수 있다는 것이다.

YAGNI는 You Aren't Gonna Need It 의 줄임말로

필요하지도 않은 기능을 소프트웨어 개발자가 임의로 상상하여

오버엔지니어링이 발생하지 않도록 하라는 원칙이다.

 

즉 우리는 설계한 테스트를 만족하는 함수를 짜는것이 목표였기 때문에

다른 player들이 들어오는 경우를 가정하는 것은 오버엔지니어링이다.

 

이제 다른 player들이 들어오면 대처할 수 있도록 코드를 바꾸려면

소스코드를 수정하는 것이 아닌 테스트 케이스를 여러개 추가하여

다양한 케이스를 커버할 수 있는 하나의 기능을 구현하도록 해야한다.

 

다른 player들을 고려한다면 Red 싸인이 뜨도록 테스트를 바꿔보자.

이런식으로 무식하지만 동일한 테스트를 여러가지 케이스로 해볼 수 있다.

이렇게 에러를 확인할 수 있는데 여기서 중요한 부분은

우리가 원했던 에러가 나왔다는 것이다.

그렇다면 우리는 이제 어디를 어떻게 고쳐야할지 정확하게 아는 상태가 된다.

 

이게 테스트 코드

 

그리고 이게 소스코드가 된다.

 

앞서 말한 것 처럼 main 함수가 비어있는 것을 볼 수 있다.

mocking으로 테스트를 하기 때문에 서버를 따로 띄우고

클라이언트를 띄우고 요청을 기다릴 필요가 없기 때문이다.

 

계속해서 Green 상태를 유지하고 있다.

 

다음에는 Github Wiki를 이용해서 convention을 정리하고

다른 기능들을 좀 더 추가한 다음 서버를 올려서 완성해보겠다.

 

반응형

댓글