[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