[1주차] 자동차 경주 과제 강의를 듣고나서
1. 기능 목록을 작성한 후, 테스트 가능한 부분을 찾아서 TDD 로 도전하기
참여자의 이름 split 하고 자동차를 생성한다.
1자이상, 5자 이하의 정상적인 이름인지 확인한다.
자동차의 이동 유무를 체크한다.
자동차의 이동 거리에 따라서 "-" 생성한다.
경주에 참여한 자동차 중에서 우승자를 찾는다.
우승자의 이름을 출력한다.
1-1. util 성격의 기능이 TDD로 도전하기에 좋다.
1자이상, 5자 이하의 정상적인 이름인지 확인한다.
자동차의 이동 거리에 따라서 "-" 생성한다.
1-2. 테스트 가능한 부분에 대해서 TDD로 도전한다.
참여자의 이름 split 하고 자동차를 생성한다.
경주에 참여한 자동차 중에서 우승자를 찾는다.
1-3. 테스트하기 어려운 부분을 찾아서 가능한 구조로 개선한다.
자동차의 이동 유무를 체크한다.
우승자의 이름을 출력한다.
테스트할 수 있는 부분까지만 테스트한다.
테스트 가능한 구조로 개선하는 법
object graph 를 그리고 다른 object 와 의존관계를 맺지 않는 마지막 노드를 먼저 찾는다.
예를 들면, RacingMain -> RacingGame -> Car 에서 마지막 노드인 Car 에 대해서 먼저 테스트 가능한지 확인하고 테스트 코드를 작성한다.
테스트 힘든 코드와의 의존관계를 정리하고, 상위 레벨에서 주입하는 방식으로 끌어 올린다. 테스트 가능한 구조로 개선한다.
1-4. 프로그래밍 요구사항을 힌트로 리펙토링 한다
규칙 1 : 한 메서드에 오직 한 개의 indent 를 한다.
규칙 2 : else 예약어를 쓰지 않는다.
규칙 3 : 모든 원시값과 문자열을 포장한다.
규칙 8 : 일급 콜렉션을 쓴다.
2. 테스트하기 힘든 코드를 리펙토링 하는 법
protected 로 접근제시어를 변경하여 test code 에서 다시 오버라이딩 하여 테스트 가능한 결과값으로 받는다.
특정 부분에 대한 로직이 자꾸 변경된다고 한다면, 그 부분에 대해서 인터페이스로 추출하여 전략 패턴으로 접근하는 것도 좋다.
처음부터 오버하여 프로그래밍하지 않고, 필요성이 생겼을 때, 변경하도록 한다.
3. 테스트 하는 데이터를 선택하는 법
테스트 하는 데이터는 항상 경계값을 넣어 발생할 수 있는 오류를 확인한다.
에를 들면 랜덤값이 4이상이면 움직이고, 4보다 작으면 멈춘다고 한다면, 4와 3을 값으로 테스트를 해본다.
4. 모든 원시값과 문자열을 포장한다.
instance 변수인 int, String 과 같은 원시 타입을 모두 클래스로 포장한다.
자동차 경주 문제를 예로 든다면, int position 에 대해서 Position을, String name 에 대해서 Name 을 클래스로 만들어서 각각 자동차의 위치, 이름과 관련된 로직을 모두 해당 클래스 내부로 점진적으로 이동시켜서, 테스트하기 좋은 코드로 만들 수 있다.
노드의 가장 마지막 값은 의존성이 적어지므로, 테스트하기가 용이하기 때문이다.
자연스럽게 객체지향적인 설계가 가능해진다.
기존에는 객체에서 getter 로 값을 꺼내와서 비교했다면, 이와 같은 설계에서는 equals와 hash 값을 오버라이딩하여 객체와 객체를 비교하게 된다.
객체에서 값을 꺼내지 말고, 메시지를 보내야 한다.
불변 객체(immutable object) 가 되어 값이 안전해진다.
값에 대한 유효범위 역시 해당 클래스 내부에서 처리한다.
프로그램내에서 그 값을 사용하는 모든 곳에서 매우 안전하게 다루어진다.
이때, 매번 새로운 객체가 발생하여 garbage collecttion 이 잘 되지 않아 성능이 떨어질 수 있다.
성능상 이슈가 발생하게 되면, 개선해도 괜찮다. 너무 성능만 고려하지 말고, 읽기 좋은 코드, 좋은 구조의 코드를 만드는 것도 같이 균형감 있게 고려하는 것이 좋다.
변경 지점을 한 곳으로 모을 수 있다.
position 값을 꺼내서 이곳 저곳에서 변경하지 않고, Position 도메인 객체 내부에서만 변경하도록 한다.
모든 곳에서 해당 매소드를 통해서만 포지션 값을 변경하려고 하기 때문에, 변경 지점이 한 곳으로 모여진다. 안전한 코드가 된다.
사용자의 입력값을 받는 부분에서 이미 유효성 검사를 하는데, 포장한 클래스 내부에서도 다시 유효성 검사를 해야하는가?
예) 자동차경주의 경우, 사용자로부터 자동차의 이름을 입력받을 때, 이미 1이상 5이하인지를 검사하는데, Name 도메인 객체에서 해당 값을 다시 한 번 검사해야하는가?
유효성 검사는 바깥쪽에서 해야하는 것과 안쪽에서 해야하는 것이 있을 수 있다.
또한 도메인 객체 자체에서 유효성 검사를 한 번 더 하면, 개발자에 의해 객체가 잘못 사용되는 것을 막을 수 있다.
중복될지라도 같은 유효성 체크를 할 수 있고, 이 경우에는 Validator 등 유효성 검사 util class 를 통해서 중복을 제거해볼 수 있겠다.
5. 일급 콜렉션을 사용한다.
List<Car> 와 같은 컬렉션도 별도의 Cars 클래스를 구현하여 관리할 수 있다.
Cars 와 관련된 로직이 모두 해당 클래스 내부로 이동하게 된다.
예를 들면 우승자를 구하는 로직과 같은 코드들의 경우, 테스트하기가 훨씬 쉬워진다.
이전 같았으면, 우승자 로직을 테스트하기 위해서 car.move() 를 우승자 횟수만큼 구현했어야 했다. 테스트를 위한 준비 코드가 너무 길어지는 셈이다.
만약 차의 위치값까지 초기화할 수 있다면, 우승자를 구하는 로직은 훨씬 쉬워질 것이다.
static method 를 통해서 List<Car> 를 주입받도록 한다. 그러면 생성자와 메소드 두 곳에서 영향받던 코드가 메소드 1개로 줄어들어 테스트하기 쉬운 코드가 된다.
Last updated