-
돌돌설 (돌고 돌아 설계 문서 작성부터 다시)Today I Learned 2022. 11. 8. 20:34
돌고 돌아 설계 문서 작성부터 다시
현재 테스트 코드 없이 애플리케이션이 동작하고 있는 상태이다.
이런 상황에서는 기존에 있던 작업 Task에서 어떤 작업을 빼와서 스프린트 계획을 세우고, 스프린트를 진행해도 안정적인 애플리케이션을 만들었다는 확신을 줄 수 없겠다는 생각이 들었다.
작업 Task도 마찬가지였다. 하나의 작업 Task를 꺼내와서 작업을 진행할 때마다 거의 항상 서브 작업이 생성되었다. 작업 단위의 수준이 올바르지 못하다는 지점으로 생각이 이동했다.
작업 단위는 사용자 스토리를 기반으로 작성되었었다. 작업 단위를 수행 가능한 수준으로 나누기 위해서는 사용자 스토리를 다시 잡고 가는 것부터 시작되어야 할 것 같다는 필요성이 느껴졌다.
사용자 스토리를 고치고, 이를 바탕으로 인수 테스트, URI, REST API 구조 설계를 다시 한 뒤 작업 단위를 재분배하는 계획을 세웠다.
어제 아샬님이 말씀해주셨던 것들 중에는 지금의 사용자 스토리가 화면 지향적이라는 부분과, 기능의 가치로 제시한 내용이 동작을 위해 끼워맞춰져 있다는 느낌이 강하다는 내용이 있었다.
사용자 스토리를 As, I, So 구조를 바탕으로 누가, 어떤 동작을 하고, 왜 그 동작을 수행해야 하는지? 무슨 가치를 위해 그런 동작을 해야 하는지를 다시 작성했다.
사용자 스토리를 다시 정의하면서 기존에 작성했던 것들 중 가치를 드러내기가 애매하다고 느껴졌던 기능들이 있었다. '특정 기능으로 이동한다'와 같이 단순 링크를 위한 기능들이 대표적이었다. 해당 기능들을 사용자 스토리에서 배제시켰다.
일차적으로 수정한 일부 사용자 스토리를 기반으로 인수 테스트를 다시 작성했다.
게시글 목록 보기 (리스트)
As: 운동 팀을 찾는 사람이
I: 참가자 모집 게시글을 확인할 수 있다.
So: 내가 뛰고 싶은 팀을 게시글 목록을 보면서 미리 구분하기 위해
시나리오 #1 - 게시글 목록 보기 (리스트, 운동 목록 존재)
Given: 데이터베이스에 운동 목록이 존재한다.
When: 사용자가 게시글 목록 보기 화면에 접속한다.
Then: 데이터베이스에 존재하는 모든 운동 게시글 썸네일들을 최신 등록 순으로 확인할 수 있다.
운동 게시글들은 페이지네이션되어 제공된다.
시나리오 #2 - 게시글 목록 보기 (리스트, 운동 목록 존재하지 않음)
Given: 데이터베이스에 운동 목록이 존재하지 않는다.
When: 사용자가 게시글 목록 보기 화면에 접속한다.
Then: 운동이 존재하지 않는다는 안내를 확인할 수 있다.게시글 목록 보기 (지도)
As: 운동 팀을 찾는 사람이
I: 참가자 모집 게시글을 지도에 표시된 장소 마커로 확인할 수 있다.
So: 내가 있는 곳에서 가까운 데 있는 뛰고 싶은 팀을 찾기 위해
시나리오 #1 - 게시글 목록 보기 (지도)
Given: 데이터베이스에 운동 목록이 존재한다.
When: 사용자가 게시글 목록 지도로 보기 화면에 접속한다.
Then: 지도를 확인할 수 있다. 존재하는 운동 목록들의 좌표에 출력되는 마커들을 확인할 수 있다.거대해진 Given을 어떻게 줄일 수 있을까?
지나치게 비대해진 테스트 코드의 크기를 어떻게 줄이고, 이해하기 어렵지 않게 바꿀 수 있을까?
다른 계층으로부터 데이터를 받아와 동작을 검증해야 하는 로직의 테스트 코드는, 다른 계층으로부터 받아와야 하는 데이터가 지나치게 많아질 때 mocking 데이터가 지나치게 커지는 것을 확인할 수 있다.
아샬님의 이틀에 걸친 리팩터링 강의를 통해 Given의 크기를 줄이기 위해 고려해볼 수 있는 방법을 정리해본다.
1. 객체에 fake 메서드를 정의해 이용한다.
Factory 메서드와 비슷한 형태로 객체가 객체 자신을 새로 생성해 반환하게 하는 fake 메서드를 정의하고 테스트 코드에서 사용한다. 이때 차이를 보여야 하는 데이터는 파라미터로 전달받게 할 수 있다.
이 방법은 테스트해야 하는 로직의 구조를 유지한 상태에서, 멤버 필드를 많이 갖고 있는 객체 인스턴스를 테스트 코드에서 생성자로 직접 생성할 때 생기는 인지 부하를 줄일 수 있다.
2. 모델이 가지고 있는 요소들을 값 객체로 전환한다.
Primitive type을 랩핑하는 타입을 사용하는 요소들은 객체를 생성자로 생성할 때 그 요소가 어떤 의미를 갖는지 생성자의 소스코드만 봐서는 알기 어렵다.
값 객체를 이용하면 모델을 생성할 때 값 객체 역시 생성자를 이용해 생성해줘야 한다. 값 객체의 이름을 통해 객체가 테스트 코드 상에서 어떤 의미를 갖고 생성되는지 나타낼 수 있다.
3. 모델이 가지고 있는 요소가 정말 그 모델에 필요한 요소인지 고민한다.
상품 모델을 생각해보자. 조회수, 재고, 가격 등의 요소들은 값 객체로, 더 나아가서는 별도의 객체로 분리될 수도 있다.
모델에서 지속적으로 요소들을 값 객체로, 별도의 객체로 분리하는 이유는 객체에게 정말로 본질적인 요소들과 잠정적으로 변할 가능성이 있는 객체들을 식별하기 위해서이다.
값 객체들이 지금에 있어서 단순히 field만을 갖는 객체만으로 치환하는 게 당장 의미가 없을 수는 있어도, 기능이 확장되어 어떤 객체의 책임이 늘어날 때, 그 책임을 값 객체나 분리된 다른 객체에게 부여할 수 있다. Money 객체가 존재한다면 추후에 상품 가격 할인 같은 기능의 책임을 Product에 있는 Money 객체에게 부여할 수 있다.
요소를 값 객체로, 별도의 객체로 빼내는 것은 개발자의 의도에 달렸지만, 굳이 적절한? 시점을 따져보자면, '지금 모델에 이 기능을 더하면 왠지 모델의 동작이 너무 복잡해질 것 같다'는 느낌이 들 때 정도를 들 수 있을 것 같다.
'Today I Learned' 카테고리의 다른 글
최소한의 기능으로 다시 시작하기 (0) 2022.11.10 마카오 기프트에서는 되던 게 왜 프로젝트에서는 안돼? - useEffect, Guard Clause (0) 2022.11.09 프로젝트 설계 문서 공개 리팩터링 (1) 2022.11.07 하나의 메서드를 두 개 이상의 기능이 공유하면 발생할 수 있는 문제 (0) 2022.11.06 풀리지 않는 문제가 있다면 문제의 범위를 좁혀보자 (0) 2022.11.05