상세 컨텐츠

본문 제목

(스프링을 모르는 자의) 넥스트스텝 ATDD, 클린 코드 with Spring 교육 후기

소프트웨어

by moonionn 2024. 8. 19. 17:07

본문

https://edu.nextstep.camp/c/R89PYi5H/

 

ATDD, 클린 코드 with Spring

 

edu.nextstep.camp

올 상반기 넥스트스텝에서 주관하는 ATDD 클린코드 교육을 들었습니다. 그리고 교육이 끝난지 반 년이 지난 지금, 이제서야 후기를 작성해보려 합니다. (상반기에 개인적으로 너무 바빴다고 핑계를...)

참고로 스프링은커녕, 자바도 잘 모르는 상태로 교육에 참여했기 때문에 "스프링을 해본 적이 없어 고민"인 분들에게 이 후기가 조금이나마 도움이 될 수 있길 바라봅니다. 결론부터 말하자면, 고민하지 말고 신청하세요!

 

교육에 참여하게 된 이유

신입으로 입사한 첫 회사에서 2년을 쭉 다니다 보니, 슬슬 바깥세상이 궁금해지기 시작했습니다. 컨퍼런스에 참석해 연사의 이야기들을 듣는 것보다, 직접 부딪혀 무언가를 만들 수 있는 경험이 필요했습니다. 때마침 개발자 교육 플랫폼인 넥스트스텝에서 "ATDD, 클린코드 with Spring"이라는 강의가 오픈했고, 스프링 경험이 전무했지만 이래저래 따지면 할 수 있는 게 없을 것 같아 무작정 교육에 참여하였습니다.

 

ATDD란 무엇인가

누군가 "ATDD"를 언급하자, 어떤 사람이 "ATDD는 TDD의 오타인가요?"라고 되물은 걸 본 적이 있습니다. 이렇듯 ATDD는 TDD에 비해 개발자들에겐 낯선 존재입니다. 그렇다면 ATDD는 도대체 뭘까요? 쉬운 설명을 위해 예시 상황을 가져와 보았습니다.

 

여기 개발자, 기획자, QA엔지니어, 디자이너로 구성된 팀이 있다고 가정해 보겠습니다.

그리고 그들에게는, 한 달 내에 새로운 기능을 개발하고 배포까지 해야 하는 미션이 주어졌습니다.

 

빠듯한 스케줄을 두고 이 팀은 분주해지기 시작합니다. 커뮤니케이션은 점점 산만해지고, 누군가는 업무 공유를 받지 못할 수도 있습니다. 떨어지는 업무 효율성만큼 결과물의 퀄리티도 낮아질 건 불 보듯 뻔합니다.

이 상황은 이 팀원들이 능력이 없어서 발생한 게 아닙니다. 아직 협력 시스템이 갖춰지지 않거나 총괄 매니저가 없는 팀이라면 어디서든 일어날 수 있는 상황입니다. 그렇다고 시스템 탓만 하며 이를 방치할 수는 없는 노릇입니다.

 

상황을 심각하게 받아들이고 있던 개발자가 어느 날, "우리 이러지 말고 ATDD를 도입해 봅시다."라는 제안을 하며 유저 시나리오라는 걸 작성해 옵니다. 이 유저 시나리오에 모두가 수긍한다면, 이것은 이제 해당 기능의 "인수 조건(Acceptance Criteria)"이 됩니다. 

 

ATDD의 기본, 인수조건

인수 조건은  Given, When, Then 세 가지로 크게 나눌 수 있습니다.

 

Given

ex: 로그인한 회원이 배송상품을 구매한 뒤

시나리오를 진행하기 위한 최초 전제 조건입니다. 

 

When

ex: 내 배송현황을 조회하면

시나리오가 테스트하고자 하는 바를 드러내는 메인 액션입니다. 

 

Then

ex: 구매한 상품의 배송상태를 조회할 수 있다.

시나리오의 예상결과입니다. 즉, 테스트의 예측 결괏값입니다.

 

인수 조건에 따라 작성된 테스트를 인수 테스트라 명칭 하며, 기능 개발 전 인수 테스트를 먼저 작성하는 걸 ATDD(Acceptance Test Driven Development)라 칭합니다.

 

백엔드 API 개발 중 작성된 인수 테스트는 아마 아래와 비슷한 모양새를 지닐 겁니다.

const VALID_USER = {
  email: "test@test.com",
  password: "test1234"
};

const DELIVERY_PRODUCT = {
  id : 1,
  price: 100000
};

// Given : 로그인한 회원이 배송상품을 구매한 뒤
// When  : 내 배송현황을 조회하면
// Then  : 구매한 상품의 배송상태를 조회할 수 있다.
test('배송현황 조회 성공', () => {
  const app = startTestServer();
  
  // Given
  const token = app.post('auth/token', VALID_USER);
  app.post(`purchase/${DELIVERY_PRODUCT.id}`, { Authorization: token.accessToken });
  
  // When
  const result = app.get('my/deliveries', { Authorization: token.accessToken });
  
  // Then
  expect(result).contains(DELIVERY_PRODUCT);
})

 

인수 테스트를 작성함으로써 얻을 수 있는 이점은 아래와 같습니다.

  1. 기능 명세에 대한 합의점이 명확해진다. (앞선 예시와 같은 상황)
  2. API 호출 흐름이 명확해져 개발자들끼리의 의사소통이 원활해진다.
  3. 빠르게 개발할 수 있다.

 

1번에 대한 예시는 이미 서술했으므로 2번부터 순차적으로 그 이유를 예시와 같이 설명해 보겠습니다. 

 

API 호출 흐름이 명확해져 개발자들끼리의 의사소통이 원활해진다

위 예시의 백엔드 테스트코드는 문서의 역할도 할 수 있습니다. 일반적인 경우 스웨거 같은 오픈 API가 제공되곤 합니다. 그러나 시나리오가 복잡한 기능이라면 API 호출 흐름도 복잡해집니다. 이 경우 인수테스트가 있다면, 상황에 따른 API 호출순서가 가시화된다는 장점이 있습니다.

 

빠르게 개발할 수 있다.

- 병합에 대한 명확한 기준점 제공

팀, 혹은 팀원에 따라 PR에 대한 승인 기준이 제각각인 경우는 흔합니다. 프로토타입을 개발 중인 팀이라면 더더욱 그럴 겁니다. 이럴 때 ATDD는 PR이 통과되어도 되는지에 대한 기준점을 제공해 줍니다. PR에 포함된 인수테스트가 통과한다면, 그 PR은 병합되어도 되는 것이죠. 기술적으로 아쉬운 부분은 추후 보완할 기회가 제공될 때 수정하도록 합니다.

 

- 테스트 케이스를 먼저 공유하면 엉뚱한 길로 빠지지 않는다

이건 TDD 개념이 보편적으로 주장하는 사안이기도 합니다. 실무에 적용했을 때 가장 효용성을 느낀 게 바로 이 부분인데, 코드를 짜기 전 테스트 코드를 합의하는 시간을 가지면 업무 방향성을 빠르게 잡는 데 도움이 되더랍니다.

왜냐하면 기능 명세에 대한 합의가 이루어졌다 하더라도 이를 구현하는 방향은 가지각색이기 때문입니다. API 엔드포인트 디자인은 물론, 어느 레포지토리에 이 기능을 작성할지 등등... 그렇기 때문에 인수 테스트 껍데기를 먼저 첨삭받는다면 엉뚱한 길로 샐 일을 막을 수 있습니다.

describe('인증/인가 API', () => {
  // Given: 액세스 토큰으로 로그인 한 뒤
  // When: 내 정보 페이지로 접속하면
  // Then: 내 정보를 조회할 수 있다
  test('로그인 성공' async () => {
    // Given
    const login = api.post(`/login`)
                     .query({ email: 'test@test.co.kr', password: '1234' });
	// When
    const mypage = api.get(`/me`)
                      .query({ accessToken: login.accessToken })
    // Then           
    expect(mypage.body.email).toBe('test@test.co.kr');
  });
});

 

예를 들어 어떤 동료가 위와 같은 테스트 파일을 먼저 작성했다고 가정해 봅시다.

어떤 리뷰를 해주고 싶은가요? 저라면 아래와 같은 부분에 대해 코멘트할 것 같습니다.

 

  • POST /login이라는 엔드포인트는 /auth라는 대분류 엔드포인트 하위로 두었으면 좋겠고
  • 로그인할 때 이메일과 패스워드 정보를 쿼리스트링으로 보내는 것도 고쳤으면 싶고
  • 내 정보 조회할 때 액세스 토큰을 헤더가 아니라 쿼리스트링에 담는 것도 고쳤으면 싶네요.

 

이 테스트 케이스를 작성하고 첨삭받는 데까지 걸리는 시간은 매우 짧습니다. 하지만 잘못 디자인된 API를 수정하는 데에는 엄청난 비용이 듭니다. 테스트 케이스를 미리 합의한다면 이런 불필요한 추가작업에 리소스를 낭비하지 않아도 됩니다.

 

회사 프로젝트에 도입해 보기

마침 복잡한 기능의 데모 프로덕트 개발을 진행하고 있던 터라 배운 바를 바로 적용해 보았습니다. 

혹시 몰라 중요부분들 블러 처리 ㅎㅎ

 

위 코드는 유닛테스트입니다. 이렇게 코어 모듈 개발만 담당하는 경우에도 Given/When/Then 템플릿을 활용해 함수의 사용법과 기능을 명시하면 테스트 설명이 좀 더 명확해집니다. 특히 해당 프로젝트가 비교적 복잡하고 낯선 도메인에 해당되었기 때문에 이런 방식은 개발자들 간 도메인 지식 간극을 줄이는 데 큰 도움이 되었습니다.

 

개인적인 후기

자바/스프링을 못하는 사람도 할 수 있는가?

- YES, but...

남들보다 두 배 가량 애를 써야 비슷한 코드 흉내라도 낼 수 있습니다. 이 말인즉슨 회사 7시 퇴근해도 새벽 2시까지는 코딩해야 한다는 뜻

 

얼마나 힘든가?

- 자바/스프링을 할 줄 안다면 주말 포함 매일 1~2시간 정도만 투자하면 될 거 같은데,

만약 그게 아니라면...

 

다음에도 넥스트스텝 교육과정에 참여할 의향이 있는가?

- YES

관련글 더보기

댓글 영역