우테코 프리코스 2주차 후기

임현규·2022년 11월 12일
1

우테코 2주차 미션 - 숫자 야구

2주차 프리코스를 제출하고 코수다를 시청하면서 많은 반성을 했다. 우테코 2주차를 진행하면서 기본적이고 중요한 것을 캐치하지 못했다는 느낌을 받았기 때문이다. 기본적으로 우아한테크코스를 진행하면서 가장 중요한 것은 제시한 요구사항을 고민하고 충족하는 코드를 잘 짜야한다! 그러나 테스트 방식이나 리팩토링에 치중한 나머지 1-9 요구사항을 단순히 중복되지 않은 숫자로 보고 기능을 짯다. 뼈아프긴 하지만 좋은 개발자가 되기 위해서는 요구사항을 정확히 이해하고 개발할 수 있어야한다. 이번 기회에 어떻게 하면 요구사항의 실수를 줄일 수 있는 지, 그리고 2주차 공통 피드백에 대한 생각과 이번 주차 고민한 내용에 대해서 정리하고자 한다.

READ ME !!

이번 2주차 피드백에 기능 목록에 대해서 다음과 같은 피드백을 주셨다.

  1. README.md를 상세히 작성한다
    미션 저장소의 README.md는 소스코드에 앞서 해당 프로젝트가 어떠한 프로젝트인지 마크다운으로 작성하여 소개하는 문서이다. 해당 프로젝트가 어떠한 프로젝트이며, 어떤 기능을 담고 있는지 기술하기 위해서 마크다운문법을 검색해서 학습해보고 적용해 본다.
    기능 목록을 재검토한다

최근 주니어와 시니어의 개발 차이에 대한 어떤 해외 밈을 본 적 있다. 주니어는 문서없이 개발하다 바로 까먹고 프로젝트가 터지고, 시니어 개발자는 문서를 작성하고, 리팩토링하고, 효율적인 코드를 짜면서 쌓아가다가 결국엔 펑 터진다. 결국은 다 펑 터진다는 밈인데 주니어는 프로젝트를 쌓지도 못하고 터진다. 웃긴 밈이였지만 2주차의 나를 보는 것 같아 조금 씁쓸했다.

README.MD는 프로젝트를 소개하는 중요한 문서이다. 내가 유용한 프레임워크, 또는 어플리케이션을 개발했다고 가정하자.. 그러나 내부적으로 아무리 잘짜고 기능이 좋아도 문서화가 안되어있으면 그 어플리케이션을 쓸까?? 나라도 안쓸것 같다. 아무리 잘 만들면 뭐해... 쓰는 방법도 모르고 이해하기도 힘든데...

음.. 필요성은 알겠는 데 어떻게 해야 Readme 문서를 잘 표현 할 줄 몰라서 고민하던 중,
자바의 상징이라 불리는 Spring-boot 의 ReadMe를 살펴보았다.

spring-boot github 페이지


스프링 부트 readme의 시작 일부분만 캡처해왔다. 보시다시피 spring boot에 대해 간략하게 소개하고 있고 spring boot가 지향하는 목표 그리고 설치 방법 및 예제가 있다. 위의 그림 포함 조금 더 목록을 나열하면 다음과 같다.

  1. Spring boot 에 대한 소개
  2. 설치 및 사용 방법
  3. 호환 버전
  4. 모듈
  5. 가이드 문서 링크

등이 있었다. (음.. 깔끔하다.)

내가 해야할 프로젝트는 프로젝트가 어떤 구조이고 어떤 방식인지 보여줘야 하기 때문에 다음과 같은 목록을 고민해서 작성해봤다.
1. 숫자 야구에 대한 소개
2. 패키지 목록
3. 기능 목록
4. 사용 예제


훨씬 더 보기 좋아진 것 같다. 앞으로 3주차에는 이런 방식으로 좀 더 보기 편하게 README를 작성해봐야겠다.

  1. 기능 목록을 클래스 설계와 구현, 함수(메서드) 설계와 구현과 같이 너무 상세하게 작성하지 않는다.

기능 목록은 왜 클래스, 메소드 구현처럼 상세히 작성하면 안되는 걸까.. 고민을 해보았다.


이것은 내가 2주차 PR에 쓴 README 일부이다. 요구 사항을 클래스 별로 나눠서 기능을 적었다. 그러나 클래스에 구현에 집중한 나머지 전체 기능 목록의 흐름을 알기 어려웠고 결국 실수를 하게 되었다. 좀 더 핵심 기능 목록에 맞춰서 간결하게 작성했으면 오히려 이런 실수는 피할수 있지 않았나 싶다. 그리고 클래스 이름이나 메서드 이름은 언제든지 변경이 되기 때문이다. 이를 매번 문서에 동기화 하는 것도 쉽지 않다. 기능목록 구현 핵심 사항에 대해서 구체적으로 그리고, 간결하게 작성하자.

git

git에서 푸시가 안되는 현상이 있어서 꽤 당황했다.

intellij 를 활용해 push 하려는데 인증을 해도 인증이 되지 않았다. 혹시나 해서 git remote를 찍어봤는데..

fork 한 주소가 아니라서 push가 안되는 것이었다. 하하....

Remote를 변경하는 방법은 다음과 같다.

git remote -v // remote 확인하기
git remote remove origin // remote 삭제
git remote add origin https://github.com/계정/리포지토리

테스트

이번에 문제가 뷰와 서비스로 요구사항이 주어져 있어서 MVC 패턴을 사용하면 좋을 것 같다는 생각이 들었다. 그래서 이번 주차에는 Viewer, Controller, Service, Table(DB) 4가지 클래스 역할로 분리하고 비즈니스 로직을 짯다. 그러나 문제점은 클래스 간에 의존성이 있고 생성자 주입으로 구현되기 때문에 테스트하기가 까다로웠다. 그래서 Mocking이란 것을 활용했다.

Mock이란?

mock의 사전적 의미는 무엇인가 흉내낸 것, 또는 모조품이라는 뜻이다. mock은 테스트 Stub을 위한 도구이다.

test stub???

위키피디아에선 test stub을 다음과 같이 정의한다.

Test stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what’s programmed in for the test.
출처: https://en.wikipedia.org/wiki/Test_stub

간단히 해석하면 test stub이란 미리 답변을 준비해서 요청시 지정한 대답만을 호출하는 객체라고 한다.

숫자 야구 예제로 다시 살펴보자

stub을 적용하지 않으면 의존관계가 table까지 연결되어 있으므로 테이블까지 온전히 구현한 후 테스트를 수행해야 한다. 그러나 BaseballService에 stub을 적용하면 어떤 입력이든 간에 정해진 출력을 하도록 페이크 객체를 만들 수 있다. 이런 방식을 활용하면 다른 부분 구현을 최소화하고 Controller 클래스에 대해 온전히 테스트를 진행할 할 수 있다.

code

	private final BaseBallService baseBallService = mock(BaseBallService.class);
	private final BaseBallController baseBallController = new BaseBallController(baseBallService);

    @Test
    void matchNumber_validateInputTestWhenLargeInput() {
        // given
        when(baseBallService.matchNumber(anyString())).thenReturn(Response.keepOf("계속"));

        // expect
        assertThatThrownBy(() -> baseBallController.matchNumber(LARGE_INPUT)).isInstanceOf(
            IllegalArgumentException.class);
    }

사용방법은 다음과 같다.
1. mock(Class)를 이용해서 객체를 Mock 객체로 초기화해준다.
2. 모킹한 객체를 테스트하고자하는 클래스에 주입한다.
3. Mockito.when, Mockito.given, Mockito.doReturn등을 이용해서 mock의 리턴할 값을 정해준다.
4. assertJ를 활용해 검증한다.

우테코의 테스트 api

우테코에서 제공하는 2주차 미션에는 반드시 우테코에서 제공하는 Console과 Randoms 의 static 메서드를 반드시 써야한다고 한다. 그 이유는 뭘까? 우테코에서 제공하는 테스트 내부 살펴보면 알 수 있다.


assertRandomNumberInRageTest는 assertRandomTest를 호출한다. 이 때 가장 첫번째 인자가 람다식으로 작성이 되어있는데 파라미터 입력이 anyInt() (타입이 int 인 값)로 구성되어 있다.

조금 더 파고 들어가면 assertTimeoutPreemptively 메서드를 호출하는데, 이것은 Junit 메서드로 지정한 시간 내에 검증할 때 쓰는 메서드이다. 여기서 mockStatic을 활용하는 데 이를 활용해 static 메서드도 mocking하게 된다.

Creates a thread-local mock controller for all static methods of the given class or interface. The returned object's MockedStatic.close() method must be called upon completing the test or the mock will remain active on the current thread.

위 코드에선 mockStatic은 쓰레드 로컬을 활용하기 때문에 try-with-resource 구문으로 mockStatic 자원을 할당 해제한 것처럼 보인다.

결론적으로 난수를 활용한 통합 테스트는 난수를 제공하는 static 메서드를 mocking 함으로써 통합 테스트 자동화가 가능해진다. 조금 더 확장해서 생각해보면 이를 활용해 일차적으로 요구사항을 잘 지켰는지 테스트를 자동화해서 활용할 수도 있다고 생각했다.

느낀점 & 앞으로

  • 2주차 피드백대로 기능에 더 포커싱을 맞추고 조금 더 보기 좋은 문서를 만들어야겠다는 생각을 했다.
  • 테스트에 대해서 문서로만 이해하고 가슴으로 와닫지 않았는데 2주차 게임에 테스트를 적용해보니 좀 더 테스트에 대해 몸으로 체득하게 된 계기가 된 것 같다. 특히 Mocking을 적용하는 방법에 대해서 더 자세히 알게 된 것 같다.
  • 계속 성장하자
profile
엘 프사이 콩그루

0개의 댓글