강의를 들으면서 정리한 TDD
너무 맘에 와닿았다. Service로직 짜는데는 10분이면 되는데 그걸 테스트 할려고 그에 배에 대한 시간이 들어가고. 뿐만 아니라 DB, File 등 여러가지 복잡한 테스트들은 테스트 짠다고 하루 이틀 쓴적도 많이 있다. 테스트를 하니까 오히려 속도가 느려진다. 진짜 내가 겪는 상황이라 공감이 갔다.
결론은 TDD 이전에 테스트가 가능한 구조로 코드가 변경되어야 한다는 것이다
나는 뭔가 지금까지 테스트를 위한 테스트만 짜고 있었던것 같다.
일단 이제부터 테스트를 필요한 부분만 짜야겠다
시스템이 잘 돌아가는지 검정
1. 인수테스트 - 직접 하기
2. 자동테스트 - 테스트 코드로 자동화
로 나뉜다.
테스트를 돌려서 잘 돌아가는지 확인. 문제가 생기면 문제 해결.
테스트 주도 개발. 3가지 순서를 거친다.
1. RED - 테스트가 실패한 뒤.
2. GREEN - 테스트를 성공하게 바꾸고
3. BLUE - 성공한 코드를 리팩토링을 한다.
를 끝날때 까지 돌아간다.
테스트를 작성하면서 내가 작성한 코드의 문제점을 파악 가능하다.
둘은 약간 상호 보완적인 관계라고 말한다.
테스트를 짜다보면 하나의 테스트에 너무 많은 기능이 있다
-> 너무 많은 책임을 하나의 클래스가 지고 있다 == 이제 클래스를 분할해야 한다.
보통 이런 과정을 밟게 된다고 한다.
테스트용, 프로덕트용 컴포넌트가 분리되면서 원하는 곳에 탈부착을 할 수 있게 한다.
이상적인 테스트는 모든 케이스를 커버하고 있다 따라서 서브 클래스에 대한 치환 여부를 테스트가 판단해 준다.
테스트를 하면서 인터페이스를 직접 사용해 본다.
따라서 코드를 작성하면서 불필요한 의존성을 실제로 확인 할 수 있다.
가짜 객체를 이용해 테스트를 작성하려면 의존성이 역전되어 있어야 하는 경우가 생긴다.
보통 Regression을 막기위해 사용한다.
내가 짠 코드때문에 모든게 멈춘다면 아마 나같아도 불안할 것이다.
만약 내가 은행앱을 건드린다면...? 멈춘다면 끔찍할것 같다.
전통적으로 1. 유닛 2. 통합 3. API 테스트로 나뉜다.
구글에서는 1. 소형 2. 중형 3. 대형 테스트로 나눈다.
위는 애매해 구글로 설명을 하자면
소형테스트가 중요하고 이를 많이 늘리는게 목표이다.
@BeforeEach
쓰는거, 테스트가 한눈에 잘 안들어와서 사용을 안하려고 하는게 좋은듯?구글에서 가져온 규칙
7. 비욘세 규칙 - 유지하고 싶은 상태나 정책이있다 -> 테스트를 만들어야 한다.
어떤 정책을 정했고, 이를 열심히 개발문서에 적고 설명을 해줘도 한계가 있다.
하지만 테스트로 작성해두면 버그가 나 아! 하고 알게된다.
유지하고 싶은 모든 상태는 테스트 작성 == 곧 정책이 됨
비욘세 규칙 엄청 좋은 것 같다.
의존성 - 특정 객체의 함수등을 사용하는 상태
의존성 주입 - 외부에서 객체를 주입받아 사용 이를 통해 유연하게 구성, 자세한건 앞에서 적어둔 내용 참조
의존성 역전 - 사용할려는 객체를 인터페이스를 통해 일을 시킴, 좀 격하게 적용을 하면 import는 무조건 Interface만 해야 한다는 의견도 있다. -> 추상적인 것에만 의존하도록 하자
테스트를 잘 하려면 위 주입과 역전을 잘 사용해야한다.
ex)
로그인 기록을 저장한다고 할 때, Clock
을 사용한다고 하자.
public void login(){
this.loginTimestamp = Clock.systemUTC().mils();
}
이러면 의존성이 숨겨져 있다고 할 수 있다.
위와 같은 경우 테스트를 하려면 시간 비교가 사실상 불가능 하다.
테스트를 할려면 stub 라이브러리를 써야한다 -> Test가 주는 경고
이를 해결해 보자
public void login(Clock clock){
this.loginTimestamp = clock.systemUTC().mils();
}
이번에는 시계를 주입받았다. 이러면 테스트 시에 Clock
자체에서 fixed
를 이용해 테스트가 가능하다.
1. 숨겨진 의존성은 테스트가 힘들다.
2. 의존성은 드러나는게 좋다.
하지만 항상 정답이 아니다. 어딘가에서는 항상 고정된 값을 입력해 줘야 한다.(Service 단이나 어딘가에서, 아니면 결국 폭탄돌리기임)
-> 이를 해결하기 위해 의존성 역전 사용
interface ClockHolder{
long getMillis();
}
이렇게 인터페이스 작성 후 clockHolder.getMillis();
를 통해 가져온다 이때는
public void login(ClockHolder clockHolder){
this.loginTimestamp = clockHolder.getMillis();
}
이렇게 의존성 역전과 주입을 이용했다.
테스트 때는 TestClockHolder라는 구현체를 만들어 사용하고.
실사용 시에는 SystemClockHolder를 만들어 사용하면 된다.
쉽게 데이터를 넣고, 쉽게 검증이 가능한가? 이다
의존성을 감춘다 == 가능성이 떨어진다.
해결로 Mock 을 이용해서 테스트를 한다. == 하지만 모든게 Mock을 이용해서 해결 될 순 없다.(테스트가 신호 보내는 중)
의존성 역전으로 해결한다 == 해결
앞으로 필요한 내용을 적었다
자신의 의견을 중간중간 이야기 해주시는것 보니까 아직 명확한 정답이 없는 부분이 많은것 같다.
하지만 지금까지 작성한 테스트를 생각해 보면 벌써 의미하는 바가 많은것 같다.
테스트 코드가 문제가 생기면 항상 Mock으로 해결을 해왔는데 다 듣고 나면 수정할 부분이 많이 생길것 같다
정보 감사합니다.