TDD는 반복 학습이다

  • 코드를 작성하기전에 먼저 객체 설계부터 진행하자
  • 만약 그게 어렵다면, 기능 피쳐리스트부터 작성하자
  • 피드백을 강의를 듣고 아에 다 삭제한 뒤 코드를 다시 구현해보자
  • 여러개를 해보는것보다 하나를 심도있게 이해하는 것이 중요하다

테스트 가능한 구조로 설계하자

제일 의존성이 적은 단위에서 테스트할 때, 테스트하기 힘든 구조가 있다면 아래와 같은 방법을 고려해보자

a. 접근지정자를 조절해서 테스트하기 힘든 부분을 재정의하기

// 처음에 테스트를 하기 쉬운 구조로 작성할 때는 `방법 a`를 이용
// 의존되는 관계를 일괄 수정은 어려우니, 레거시를 처음 리펙토링 할 때도 이 방법이 용이하다
Car car = new Car() {
@Override
protected int getRandomNo() {
return 1;
}
}

b. 파라미터로 받기

// 요구사항이 변하지 않는 상태로 처음 설계할 때는 `방법 b`가 유리
Car car = new Car();
car.move(1);

c. 인터페이스로 받기

Car car = new Car();
// 요구사항이 계속 변할 때는 `방법 c`처럼 인터페이스로 빼서 여러 전략을 만들 수 있도록 하는것도 좋다
car.move(new randomStrategy());
car.move(() -> true);

인스턴스 변수들을 클래스로 포장하자

기존에는 그저 int, String과 같은 변수들이지만, 클래스로 포장할 경우 다음과 같은 이점을 얻을 수 있다

  • 특정 객체의 유효범위를 책임질 수 있다
  • 프로그램 전체에서 일관된 객체를 사용할 수 있다
    • 버그를 줄일 수 있다
    • 여러 유효성이 프로그램 곳곳마다 정의된다면 단일 책임의 원칙을 위배할 수 있다

객체 지향 원칙에 따라서 지켜야할 것들

  • 내부 상태를 나타내는 값들을 getter/setter를 통해 주입하지 말고 명령하는 메서드를 만들자
// 나쁜 예
int position = RANDOM_POSITION;
for (Car car : cars) {
if (car.getPosition() < position) {
car.setPosition(position);
}
}

// 좋은 예
int position = RANDOM_POSITION;
for (Car car : cars) {
car.move(position);
}
  • 객체의 특정 상태값을 비교할 때 getter로 꺼내서 비교하지말고 객체자체를 비교하자
  • 버그의 원인이 상태값을 객체 밖에서 수정할 수 있을 때 많이 발생하니, Immutable하게 사용하는걸 고려해보자
  • 가능한 getter를 사용하지 않도록 해보자

참고

  1. 위는 도메인 객체 설계에 관한 내용으로, VIEW LAYER <-> DOMAIN LAYER과 같은 DTO는 getter/setter를 허용함

  2. Car와 CarDto를 만들어야 한다면 두 클래스를 구분짓기 위해 생각해 볼 것들

    • CarDto 클래스에만 position을 가지는 생성자를 만든다 (도메인 객체와 다르다는 것을 표시하기 위한 관점에서)
    • Car 클래스에 position을 가지는 생성자를 만든다 (테스트 관점에서)