-
목차
1. TDD
1.1. TDD 장점
1.2. TDD 개발 패턴
1.3. TDD 단점
2. TDD는 언제 사용해야 할까?
2.1. 프로젝트 중반에 TDD를 도입하려면
2.2. 그럼 도입하면 안되나?
3. 테스트
3.1. 테스트 규칙
3.2. 테스트 작성방법
3.3. 테스트 패턴
3.4. 테스트 종류
부록1 단위 테스트
부록2 Mock 객체 테스트
부록3 통합 테스트
TDD
- 테스트 코드를 먼저 작성한 뒤 실제 코드를 작성하는 개발방식
- 예상되는 결과와 실제 결과 사이에 존재하는 차이를 줄일 수 있는 기술
TDD장점
- 개발자가 놓치고 지나간 것을 확인하여 프로그램의 결함을 낮출 수 있다.
- 개발자의 자신감을 높여준다.
TDD는 개발자의 자신감을 위해서 사용한다.
- TDD가 없다면 일년이 이상 지나게 될 경우 코드가 썩어가는게 보이지만 TDD는 시간이 지남에 따라 코드에 대한 자신감을 더 쌓게 해준다.
TDD 개발 패턴
TDD를 사용하여 개발하는 패턴으로는 대표적으로 빨강-초록-리팩토링 패턴이 존재
- 빨강 : 실패하는 작은 테스트를 작성 처음에는 컴파일 조차 되지 않을 수 있음
- 초록 : 빨리 테스트가 통과하도록 작성 이를 위해 어떤 죄악을 저질러도 좋다.
죄악의 방법
상수를 직접 반환
- 리팩토링 : 일단 테스트를 통과하게만 하는 와중에 생겨난 모든 중복을 제거
TDD단점
당연히 장점만 존재한다면 안쓰고 있을 이유가 없다.
- 개발 시간이 증가한다.
10~30%
- 여태 개발해왔던 방식을 변경해야 한다.
테스트 먼저 개발
TDD는 언제 사용해야 할까?
TDD는 불확실함이 존재하는 프로그램을 개발할 때 사용하는 것이 좋다.
- 기존에 진행해본 것이 아닌 처음 개발해볼 경우
프로젝트 중반에 TDD를 도입하려면?
- 인터페이스 설계가 되어 있지 않기에 일부분 격리 후 실행하는게 어렵다.
고치면 되잖아
TDD를 위해 테스트 코드를 작성 후 리팩토링을 다시 하는 것은 많은 시간이 소요된다.- 테스트 작성 후 리팩토링을 하려면 에러를 확인하는 테스트 코드가 있어야 한다.
딜레마
- 테스트 작성 후 리팩토링을 하려면 에러를 확인하는 테스트 코드가 있어야 한다.
그럼 도입하면 안되나?
그런 것은 아니다
순차적으로 TDD를 도입할 수 있다.- 변경의 범위를 제한 하여 새롭게 추가한 기능 혹은 리팩토링 필요한 코드에 대한 부분만 TDD적용 하는 것이다.
- 테스트 - 리팩토링 사이의 교착상태를 풀어 테스트가 아닌 다른 방법으로도 리팩토링에 대한 피드백을 얻을 수 있다.
테스트
테스트란 내가 동작을 실행 시킬 때
값이 원하는 형태로, 원하는 동작으로
실행 되는가를 확인하는 것이다.테스트 규칙
- 테스트는 외부의 문제가 아닌 이상 항상 일관적인 결과를 제공해주어야 한다.
- 반환이 없어야 한다.
void
- 파라미터가 없어야 한다.
@Test
어노테이션이 붙어 있어야 한다.public
접근 제어자가 있어야 한다.@Deprecated @Since JUnit5
테스트 작성 방법
내가 생각하는 기능이 모든 정답 요소를 반영하여 정상동작 하는지에 대한 생각을 글로 표현하는 것이다.
테스트 패턴
테스트코드 작성 패턴은 given-when-then 패턴을 이용한다.
- given : 테스트를 준비하는 과정 (변수, 입력 값 정의)
- when : 실제로 행동
로직
을 실행하는 과정 - then : 테스트를 통해 예상한 값, 실제 값을 검증하는 과정
예제 이상한 계산기
- 이상한 계산기는 플러스, 마이너스 2가지의 기능을 가지고 있다.
- 플러스는 일반 계산기의 마이너스 기능을 처리한다.
- 마이너스는 일반 계산기의 플러스 기능을 처리한다.
void strangeCalculatorPlusTest() { //이상한 계산기를 생성 StrangeCalculator cal = new Strangecalculator(); //이상한 계산기 플러스 기능에 2개의 값을 입력 int result = cal.plus(5, 2); //내가 생각한 결과와 플러스 기능을 통해 반환된 값이 일치한지 확인 assertEquals(3, result); } void strangeCalculatorMinusTest() { //이상한 계산기를 생성 StrangeCalculator cal = new Strangecalculator(); //이상한 계산기 마이너스 기능에 2개의 값을 입력 int result = cal.minus(5, 2); //내가 생각한 결과와 마이너스 기능을 통해 반환된 값이 일치한지 확인 assertEquals(7, result); }
테스트의 종류
테스트는 스프링 기준으로 크게 단위, 통합 테스트로 구분이 된다. 구분 되는 기준은 명확한 것은 아니다.
- 단위 테스트 : 외부 의존성 없이 사용되는 테스트
Spring
- 통합 테스트 : 외부 의존성 있이 사용되는 테스트
단위 테스트
테스트의 핵심이다. 주로 스프링을 사용하지 않고 모든 기능에 대한 테스트를 작성한다.
장점- 내 코드가
기능이
제대로 동작하는지 확인 가능 - 클린코드
Mock 객체 테스트
제한된 환경을 만들기 위하여 객체들을 고립시키는 위하여 사용된다.
- Mock 객체는 실제 객체의 요청을 인터셉트하여 처리한다.
예제
- 단위/통합/목 객체 테스트 예제 샘플
- 클래스 다이어그램
//단위 테스트 public class BeanServiceUnitTest { private SimpleBeanRepository simpleBeanRepository; private BeanService beanService; @Before public void setUp() { //given simpleBeanRepository = new SimpleBeanRepository(); //when simpleBeanRepository.add(new Bean("coffeeBean")); //then beanService = new BeanService(simpleBeanRepository); } @Test public void getBeanByName() { //when Bean bean = beanService.findByName("coffeeBean"); //then assertEquals("coffeeBean", bean.getName()); } @Test public void getBeanByName() { // given BeanRepository beanRepository = Mockito.mock(BeanRepository.class); BeanService beanService = new BeanService(beanRepository); Mockito.when(beanRepository.findByName("coffeeBean")) .thenReturn(new Bean("coffeeBean")); // when Bean actualBean = beanService.findByName("coffeeBean"); // then assertEquals("coffeeBean", actualBean.getName()); } }
통합 테스트
스프링의 의존도가 높기에 단위 테스트 하나만으로는 100% 신뢰를 할 수 없기에 작성하는 테스트 이다.
@SpringBootTest
어노테이션을 통해 스프링 의존성을 사용할 수 있다.- 테스트를 위한 Bean조절 가능하다.
//통합 테스트 @SpringBootTest public class BeanServiceTest { @Autowired private BeanService beanService; @MockBean private SimpleBeanRepository simpleBeanRepository; @Before public void addBean() { BDDMockito.given(this.simpleBeanRepository.findByName("coffeeBean")) .willReturn(new Bean("anyBean")); } @Test public void getBeanByName() { Bean bean = beanService.findByName("coffeeBean"); assertEquals("anyBean", bean.getName()); } }