TDD는 Test Driven Development의 약자로 테스트 주도 개발이라고 한다.
테스트 주도 개발이란 반복 테스트를 이용한 소프트웨어 방법론으로 작은 단위의 테스트케이스를 작성하고 이를 통과하는 코드를 추가하는 단계를 반복하여 구현한다.
짧은 개발주기의 반복에 의존하는 개발 프로세스이며 애자일(Agile)방법론 중 하나인 eXtream Programming(XP)의 Test - Frist 개념에 기반을 둔 단순한 설계를 중시한다.
eXteam Programming - XP란?
미래 예측을 최소화하고 지속적으로 프로토타입을 완성하여 추가 요구사항이 생기더라도 실시간으로 반영할 수 있는애장리 기법중 하나이다.
<RED>
실패하는 테스트코드를 먼저 작성한다.
<Green>
테스트 코드를 성공시키기 위한 코드를 작성한다.
<Refactor>
중복 코드 제거, 일반화 등의 리펙토링을 수행한다.
주의사항
실패하는 테스트 코드를 작성할 때까지 실제 코드를 작성하지 않는다.
실패하는 테스트를 통과할 정도의 최소 실제 코드를 작성한다.
이를 통해 명확하게 정의할 수 있고 불필요한 설계를 피하고, 정확한 요구 사항에 집중이 가능해진다.
일반적인 개발 방법은 요구사항 분석 -> 설계 -> 테스트 -> 배포 의 형태인 개발 주기를 갖는다.
이러한 방법은 소프트웨어 개발을 느리게 하는 잠재적인 위험이 있다.
이러한 문제점이 발생되는 이유는 "어느 프로젝트든 초기 설계가 완벽하다고 말할 수 없기 때문이다."
고객의 요구사항 또는 디자인 오류등에 내외부 조건에 의해 재 설계하여 점진적 설계를 진행한다.
이때 재설계를 하는 과정에서 개발자는 코드의 삽입, 수정, 삭제하는 과정에서 불필요한 코드를 남기거나 중복처리 되는 가능성이 있다.
또한, 작은 부분의 수정에도 모든부분에서 기능테스트진행해야 하기 때문에 전체적인 버그를 검출하기 어려움이 있어 버그 검출 능력이 저하된다. 그 결과 어디서 어떤 버그가 발생될지 모르는 상황에서 코드를 수정하지 않는 형상이 나타나고 이러한 현상이 누적되는 결과 소스코드의 품질이 저하되는 문제가 발생한다.
일반 개발방법과 가장 큰 차이점은 테스트 코드를 작성한 뒤에 실제 코드를 작성한다는 점이다.
이러한 과정을 통해 자연스럽게 코드의 버그가 줄어들고 소스코드의 품질이 향상된다.
간단한 CRUD기능을 Spring Boot에서 TDD주도 개발을 구현하는 방법이다.
사용한 프레임워크는 Junit
과 Mockito
를 사용했다.
아직 Service Class의 CreateItem은 생성하지 않은 상태에서 테스트 코드를 작성을 진행한다.
//Test코드를 명시
@SpringBootTest
public class ItemServiceTest{
@Mock
private ItemRepository itemRepo;
@InjectMock
private ItemService itemService;
@Test //실행되는 테스트 코드
public void testCreateItem(){
//Given : 사전에 준비 조건, 테스트를 위한 객체
Item item = new Item("TestItem", "TestDescription");
//아이템이 저장될 때 더미 데이터로 저장되도록 설정
when(itemRepo.save(any(Item.class))).thenReturn(item);
//When : 아잍메을 생성하는 메서드 실행
Item createItem = itemservice.createItem(item);
//Then : 아이템 생성이 정상적으로 이루어졌는지 검증
assertEquals("TestItem", createItem.getName());
//save메서드가 한번 호출되었는지 확인
verify(itemRepo, times(1)).save(item);
}
}
ItemService
에서 createItem
메서드를 구현하여 테스트를 성공하도록 함.
@Service
public class ItemService{
@Autowired
private ItemRepository itemRepo;
public Item createItme(Item item){
return itemRepo.save(item);
}
}
코드가 성공적으로 테스트를 통과하면 코드의 구조나 성능을 개선하기 위해 진행한다.
리펙토링을 진행한 이후 테스트를 다시 실행하여 기능이 제대로 유지되는지 확인해야한다.
테스트를 구조화하고 가독성을 높이때 사용한다. 다양한 패턴이 있지만 예시코드에서 사용한 패턴은 "Given - When - Then"패턴이다.
Given
When
Then
assert
구문을 사용하여 원하는 결과가 실제로 출력이되는지 확인한다.Mocking은 테스트를 진행할 때 실제 구현 대신 가짜 객체(Mock Object)를 만들어 사용하는 기법이다.
해당 기법은 실제 객체의 동작을 시뮬레이션함으로써 테스트 대상과 의존성이 있는 외부 요소나 서비스에 대해 제어를 할 수 있게 된다.
실제 객체처럼 동작하지만 테스트하고자 하는 코드에 외부 종속성이 미치는 영향을 제거하거나 예외 상황을 테스트를 진행하도록 사용자설정이 가능하다.
의존성 분리
실제환경에서 동작하는 외부 시스템은 테스트 중에 제어하기어렵거나 실행시간이 오래 걸릴 수 있다. 이러한 점을 줄이기 위해 Mocking하여 실제 동작을 모방함으로써 시간을 단축하고 제어가 쉽도록 설정한다.
독립성 보장
Mock객체를 사용하면 테스트가 외부시스템에 의존하지 않는다. 각테스트가 독립적으로 실행가능하며 서로 간섭하지 않도록 해준다.
예외 테스트
외부 서비스가 실패하거나 예외를 던지는 상황에서 직접적으로 재현하기 어려운 점이 있다. 이때 Mock객체는 이러한 예외 상황을 쉽게 설정하고 테스트하도록 설정한다.
상태 검증
특정 메서드가 얼마나 호출되었는지, 특정 조건에 따라 동작했는지 검증이 가능하다.
@Mock
@InjectMock
when...thenReturn
when
구문을 사용하여 Mock객체의 특정 메서드 호출에 대해 가짜 응답을 설정한다.itemRepo.save()
가 호출되면 미리 설정한 item객체를 반환하도록 설정했다.verify
Mocking은 외부의 의존성을 제어하고, 테스트환경을 보다 쉽게 구성할 수 있도록 한다. 이를 통해 코드의 로직에 집중한 테스트가 가능하며 테스트의 독립성을 보장한다.