회귀 버그 : 코드변경으로 인해 기능이 동작하지 않았던 시절로 돌아가면서 생기는 버그
회귀버그에 효과적인 테스트 자동화를 해보자!
테스트 코드를 작성하는 어려움
외부 API 씀
데이터베이스 연동 등…
1.
레거시 코드(기존 코드)에 테스트를 넣는게 TDD가 아니다.
a.
본인의 코드에 테스트를 넣는 방법을 먼저 알아야 한다.
b.
테스트를 자연스럽게 넣는 방법을 알아야 한다
→ TDD와 테스트를 섞어서 쓰다가 혼란만 가중될 뿐이다. (다른 거다)
2.
레거시에 테스트를 넣을려면 코드 개선이 필요하다.
a.
테스트의 목적
i.
회귀 버그 방지
ii.
유연한 설계로 개선
1.
테스트를 쉽게 만들어줌
2.
테스트를 결정적이게 만들어
→ 테스트와 설계는 긴밀한 상호관계를 가지고 있다.
3.
커버리지에 집착하면 안된다
a.
코드를 짜다보면, 생각보다 테스트가 필요없는 부분들이 많다.
TDD를 논하기 전에 테스트가 가능한 구조로 변경되어야 한다.
TDD
테스트 주도 개발
1.
깨지는 테스트를 먼저 작성한다. (RED)
a.
함수에서 Exception을 날려 빨간줄이 뜨게 한다.
2.
깨지는 테스트를 성공시킨다 (exception 핸들링 등) (GREEN)
a.
여기서 기능개발이 들어간다.
3.
리팩토링한다. (BLUE)
a.
이미 그린단계를 끝냈기 때문에 리팩토링을 진행하고 테스트를 다시 돌려보면 이 리팩토링이 제대로 진행된 리팩토링인지 빠르게 파악이 가능하다.
위 과정을 무한반복 하는것이 TDD 개발이다.
TDD의 장점
1.
깨지는 테스트를 먼저 작성해야 하기 때문에 인터페이를 먼저 만드는것이 강제된다.
•
레드단계에서는 일단 컴파일이 되는 코드를 만들고 테스트가 실패하는 것까지 확인해야 한다.
◦
따라서 개발자는 구현체보다 인터페이스에 집중을 하게 되고 객체의 책임이 어디까지인지 생각하게 된다.
What/Who 사이클
객체가 어떤 행위(What)을 수행할 것인지를 결정한 후에 누가(Who) 그 행위를 수행할 것인지 결정해야 한다.
2.
장기적인 관점에서 개발 비용 감소
a.
새로운 기능을 개발하고 전체 테스트를 돌려보면 이 기능으로 문제가 발생하는지 안하는지 알 수 있다.
TDD의 단점
1.
초기 개발 비용이 높다.
2.
난이도가 높다.
a.
지금 해보면서 느끼고 있음…
테스트 코드 생각해봐야 할 점
1.
의미없는 테스트코드일 경우
JPA를 쓴다면 Repo클래스에서 단순히 조회하는걸 테스트코드 작성해야할까?
ORM을 사용하는 이유 중 하나는 “신뢰”이다..
2.
과연 이 테스트코드는 괜찮은 테스트 코드일까?
느리고 쉽게 깨지는 테스트 (H2를 사용하는 경우 느리다…)
3.
테스트가 불가한 코드
예를 들어 로그인을 한 시간을 DB에 저장한다.
그러면 로그인 함수를 호출하고 then문에서 검증을 해야하는데
로그인 함수를 호출한 시점을 어떻게 알 수 있지?
mock라이브러리를 통해 어거지로 테스트코드를 작성할 수 있지만
테스트코드에서의 빨간줄은 설계가 잘못된 신호일지도 모른다!
mock 라이브러리를 사용하여 위 신호를 보지 못하고 넘어간다면..?
테스트는 좋은 설계를 유도한다!
테스트의 필요성
레거시 코드
단순히 테스트 루틴이 없는 코드다. 이 정의는 다소 불완전하지만 말이다.
→ 시간과는 관계가 없는 개념이다.
테스트는 왜 필요할까
Regression때문
→ 잘 돌아가던 코드가 새로운 배포로 인해서 동작하지 않는 상
테스트의 필요성
1.
좋은 아키텍쳐를 유도한다.
좋은 아키텍쳐가 무엇일까?
•
SOLID 원칙을 잘 지켰는가
SOLID와 TEST는 상호보완적이다.
단, 테스트 코드를 작성한다고 저절로 SOLID원칙이 지켜지는 건 아니다!
S : 단일 책임 원칙
테스트가 너무 많아져서 이게 무슨 목적의 클래스인지 눈에 안들어오는 지점이 생긴다.
이 때가 클래스를 분리해야할 시점. 클래스를 분리하다보면 책임이 자연스럽게 분배된다.
O: 개방 폐쇄 원칙
테스트 컴포넌트와 프로덕션 컴포넌트를 나눠 작업하게 되고 필요에 따라 이 컴포넌트를 자유자재로 탈부착이 가능하게 개발할 수 있다. → OCP를 지켜줌
L: 리스코프 치환 원칙
이상적으로 테스트는 모든 케이스에 대해 커버하고 있으므로, 서브 클래스에 대한 치환 여부를 테스트가 알아서 판단해준다.
I: 인터페이스 분리 원칙
테스트는 그 자체로 인터페이스를 직접 사용해볼 수 있는 환경
불필요한 의존성을 확인할 수 있다.
D: 의존성 역전 원칙
가짜 객체를 이용하여 테스트를 작성하려면, 의존성이 역전되어 있어야 하는 경우가 생김
나중에 추가 설명 기재하자..
테스트 코드를 단순히 테스트만을 위해서 작성하지 말고 설계를 위한 도구라고 생각을 하면 테스트 코드의 가치가 올라간다!
테스트의 3분류
위처럼 나누는 경우도 있지만 소형/중형/대형 테스트로 나누는 경우도 존재한다.
a.
단위테스트 (소형 테스트)
단일서버, 단일 프로세스, 단일 스레드, 디스크 I/O 사용 x, Blocking call 허용 x
Thread.sleep() 있으면? → 단위테스트 아님!
b.
중형 테스트
서버 한대에서 동작은 단위테스트와 동일
멀티 프로세스, 멀티 스레드 사용 가능
→ h2같은 데이터베이스도 사용 가능하다!
c.
E2E 테스트 (대형 테스트)
멀티 서버 사용 가능
테스트에 필요한 개념
1.
SUT (System under test) : 테스트 하려는 대상
위 경우에는 User가 SUT이다.
위 방법은 좋은 테스트코드는 아님
어떤 메서드를 호출했는지만 검증을 하면되는것인데 내부 구현을 어떻게 했는지 감시하는 것임
캡슐화에 위배
2.
BDD (Behaviour driven development) : given - when - then
행동에 집중해라.
3.
상태 검증 vs 행위 검증
a.
상태 기반 검증
어떤 값을 시스템(함수)에 넣었을 때, 나오는 결과값을 기대값과 비교하는 방식
b.
행위 기반 검증 (BDD의 행위와는 다른 의미임) BDD ≠ 행위 기반 검증
i.
어떤 값을 시스템에 넣었을 때 협력 객체의 어떤 메서드를 실행하는가?
4.
테스트 픽스처
테스트에 필요한 자원을 생성하는것
@BeforeEach로 미리 User라는 객체를 만들었다.
테스트 픽스처는 sut가 될 수도 있고 sut의 일부일 수도 있다.
5.
테스트는 정책이고 계약이다.
유지하고 싶은 상태가 있다면 테스트코드를 작성해라
협업을 하는데 있어서 유지하고 싶은 상태에 대한 테스트 코드가 작성되어 있다면 질문이 들어오지않고 협업을 하는 사람에게도 해당 상태를 꼭 유지해야 된다는것을 인식시킬 수 있다.
6.
테스트 더블 (Test Double)
회원가입에 이메일 발송이 필요하다면?
가짜 객체를 만든다 = 테스트 대역(테스트 더블)
•
테스트 대역의 종류
◦
Dummy
▪
아무런 동작도 하지 않고, 그저 코드가 정상적으로 돌아가기 위해 전달하는 객체f
◦
Fake
▪
Dummy와 다르게 자체 로직을 가지고 있다.
▪
잘 만든 Fake객체는 실제 로컬코드에서 사용할 수도 있다!
◦
Stub
▪
주로 외부 연동하는 컴포넌트들에 사용을 많으 한다.
▪
아래와 같이 email에 해당하는 유저를 찾았다고 치고 유저를 만들어서 내려준다!
▪
mokito 프레임워크를 사용하면 아래와 같이 작성할 수 있음. (위와 같은 코드임)
◦
Mock
▪
메소드 호출을 확인하기 위한 객체, 자가 검증 능력을 갖춤
▪
사실상 테스트 더블과 동일한 의미로 사용된다.
→ Fake, Dummy, Stub은 다 Mock이다.
◦
Spy
▪
메소드 호출을 전부 기록했다가 나중에 확인하기 위한 객체
의존성
1.
의존성 역전
왼쪽의 그림과 오른쪽의 그림의 화살표 방향이 다르다.
이것을 의존성 역전이라 한다.
의존성 역전이란?
첫째, 상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.
둘째, 추상화는 세부 사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다.
좋은 테스트 코드를 작성하기 위해서는 input과 output을 숨겨선 안된다.
TDD에 대한 조언
1.
private 메소드는 테스트하지 않아도 된다.
만약 해당 메소드를 테스트해야 한다면 private 메소드가 아니여야 한다거나, 다른 클래스로 분리/책임을 위임하여 public으로 만들라는 신호이다.
→ 설계를 개선하라는 신호이다!
2.
final 메소드를 stub하는 상황을 피해야 한다.
final 메소드를 stub해야하는 상황이 생긴다면, 설계가 잘못된 상황이다!
3.
테스트 코드는 중복이되더라도 가독성이 있는 코드가 더 좋다.
4.
테스트에 논리로직을 넣지말자 ex) if, for문