Develog
코드스테이츠 56일차 본문
학습 목표
- Mock의 의미를 이해할 수 있다.
- Mockito의 기본 사용법을 이해할 수 있다.
- 비즈니스 로직의 단위 테스트에 Mockito를 적용할 수 있다.
- Controller의 슬라이스 테스트에 Mockito를 적용할 수 있다.
- TDD(Test Driver Development)의 개념을 이해할 수 있다.
Mock이란?
목업(Mock-up)은 실제 제품이 나오기 전에 내부적으로 사용하기 위한 모형 제품을 의미한다.
✔ 테스트 세계에서의 Mock
테스트 세계에서의 Mock은 바로 가짜 객체를 의미한다.
그리고 단위 테스트나 슬라이스 테스트 등에 Mock 객체를 사용하는 것을 바로 Mocking이라고 한다.
테스트에서 Mock 객체를 사용하는 이유
우리가 그냥 테스트를 진행하게 되면 서비스 계층을 거쳐서 데이터 액세스 계층 그리고 데이터베이스까지 그 동작 흐름이 끝까지 이어졌다가 되돌아 오기때문에 슬라이스 테스트라기 보다는 통합 테스트에 가깝다고 볼 수 있다.
슬라이스 테스트의 목적은 해당 계층 영역에 대한 테스트에 집중하기 위해서이기 때문에 Mock 객체를 사용하는 것이다.
✅ Mock 객체를 사용한 슬라이스 테스트 프로세스
Mockito란?
그런데 이렇게 Mock 객체를 생성하고, 해당 Mock 객체가 진짜처럼 동작하게 하는 역할을 구체적으로 누가 담당할까?
Mock 객체로 Mocking을 할 수 있게 해주는 여러 오픈 소스 라이브러리가 있지만 그 중에서 가장 많이 사용되고, Spring Framework 자체적으로도 지원하고 있는 Mocking 라이브러리는 바로 Mockito이다.
우리는 Mockito의 Mocking 기능을 이용해서 테스트하고자 하는 대상에서 다른 영역(다른 계층 또는 외부 통신이 필요한 서비스 등)을 단절시켜 오로지 테스트 대상에만 집중할 수 있다.
슬라이스 테스트에 Mockito 적용
✔ MemberController의 postMember() 테스트에 Mockito 적용
@SpringBootTest
@AutoConfigureMockMvc
class MemberControllerMockTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private Gson gson;
// (1)
@MockBean
private MemberService memberService;
// (2)
@Autowired
private MemberMapper mapper;
@Test
void postMemberTest() throws Exception {
// given
MemberDto.Post post = new MemberDto.Post("hgd@gmail.com",
"홍길동",
"010-1234-5678");
Member member = mapper.memberPostToMember(post); // (3)
member.setStamp(new Stamp()); // (4)
// (5)
given(memberService.createMember(Mockito.any(Member.class)))
.willReturn(member);
String content = gson.toJson(post);
// when
ResultActions actions =
mockMvc.perform(
post("/v11/members")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content(content)
);
// then
MvcResult result = actions
.andExpect(status().isCreated())
.andExpect(jsonPath("$.data.email").value(post.getEmail()))
.andExpect(jsonPath("$.data.name").value(post.getName()))
.andExpect(jsonPath("$.data.phone").value(post.getPhone()))
.andReturn();
// System.out.println(result.getResponse().getContentAsString());
}
}
- @MockBean 애너테이션은 Application Context에 등록되어 있는 Bean에 대한 Mockito Mock 객체를 생성하고 주입해주는 역할을 한다.
(1)과 같이 @MockBean 애너테이션을 필드에 추가하면 해당 필드의 Bean에 대한 Mock 객체를 생성한 후, 필드에 주입(DI)한다.
(1)에서는 MemberService 빈에 대한 Mock 객체를 생성해서 memberService 필드에 주입한다.
- (2)에서 MemberMapper를 DI 받는 이유는 MockMemberService(가칭)의 createMember()에서 리턴하는 Member 객체를 생성하기 위함이다.
- (3)에서 MemberMapper를 이용해 post(MemberDto.Post 타입) 변수를 Member 객체로 변환하고 있다.
MemberMapper를 굳이 사용하지 않고 new Member() 와 같이 Member 객체를 생성해도 되지만 여기서는 post 변수를 재사용하기 위해 MemberMapper로 변환을 했다.
- 실제 MemberService의 createMember()에서 회원 정보 등록 시, Stamp 정보도 등록되며, createMember() 의 리턴 값(Member 객체)에 Stamp 정보 포함이 된다.
따라서 MockMemberService(가칭)의 createMember()에서도 리턴 값으로 Stamp 정보가 포함된 Member 객체를 리턴하도록 (4)와 같이 Stamp 객체를 추가해 준다.
만약 Stamp 객체를 추가해주지 않으면 MemberResponseDto 클래스 객체가 JSON으로 변환되는 과정 중에 Stamp에 대한 정보가 없다는 예외가 발생한다.
- (5)는 Mockito에서 지원하는 Stubbing 메서드이다.
- given(memberService.createMember(Mockito.any(Member.class)))
- given()은 Mock 객체가 특정 값을 리턴하는 동작을 지정하는데 사용하며, Mockito에서 지원하는 when()과 동일한 기능을 한다.
여기서는 Mock 객체인 memberService 객체로 createMember() 메서드를 호출하도록 정의하고 있다.
createMember()의 파라미터인 Mockito.any(Member.class) 는 Mockito에서 지원하는 변수 타입 중 하나이다.
MockMemberService(가칭)가 아닌 실제 MemberService 클래스에서 createMember()의 파라미터 타입은 무엇일까? 바로 Member 타입이다.
따라서 Mockito.any()에 Member.class를 타입으로 지정해 주었다. - .willReturn(member)
MockMemberService(가칭)의 createMember() 메서드가 리턴 할 Stub 데이터이다.
Stubbing이란?
Stubbing은 테스트를 위해서 Mock 객체가 항상 일정한 동작을 하도록 지정하는 것을 의미한다.
위 코드의 (5)에서는 MockMemberService(가칭)의 createMember() 메서드가 항상 동일한 member 객체를 리턴하고 있다.
실행 결과를 보면, MockMemberService(가칭)의 createMember() 메서드가 호출되므로, 데이터 액세스 계층쪽의 로직은 실행이 되지 않는다.
즉, MockMemberService(가칭) 클래스는 우리가 테스트하고자 하는 Controller의 테스트에 집중할 수 있도록 다른 계층과의 연동을 끊어주는 역할을 하는 것이다.
✔ MemberService의 createMember() 테스트에 Mockito 적용
@Transactional
@Service
public class MemberService {
private final MemberRepository memberRepository;
private final ApplicationEventPublisher publisher;
public MemberService(MemberRepository memberRepository,
ApplicationEventPublisher publisher) {
this.memberRepository = memberRepository;
this.publisher = publisher;
}
public Member createMember(Member member) {
verifyExistsEmail(member.getEmail()); // (1)
Member savedMember = memberRepository.save(member);
publisher.publishEvent(new MemberRegistrationApplicationEvent(this, savedMember));
return savedMember;
}
...
...
private void verifyExistsEmail(String email) {
Optional<Member> member = memberRepository.findByEmail(email); // (2)
// (3)
if (member.isPresent())
throw new BusinessLogicException(ExceptionCode.MEMBER_EXISTS);
}
}
위 코드의 목적은 createMember() 메서드의 (1)과 같이 DB에 존재하는 이메일인지 여부를 검증하는 verifyExistsEmail() 메서드가 정상적인 동작을 수행하는지를 테스트하는 것이다.
그런데 verifyExistsEmail() 메서드의 내부를 보면 (2)와 같이 verifyExistsEmail() 메서드의 파라미터로 전달 받은 email을 조건으로 한 회원 정보가 있는지 memberRepository.findByEmail(email)을 통해 DB에서 조회하고 있다.
하지만 verifyExistsEmail() 메서드가 DB에서 Member 객체를 잘 조회하는지 여부를 테스트 하려는게 아니라, 어디서 조회해 왔든 상관없이 조회된 Member 객체가 null이면 BusinessLogicException 을 잘 던지는지 여부만 테스트하면 된다.
따라서 DB에서 회원 정보를 조회하는 (2)의 memberRepository.findByEmail(email) 은 Mocking의 대상이 된다.
즉, Mockito 를 통해 DB에서 회원 정보를 조회하지 않고, Mocking을 통해서 Member 객체를 제공할 수 있다는 것이다.
그렇다면 Mockito를 이용해서 Mocking을 한다면?
// (1)
@ExtendWith(MockitoExtension.class)
public class MemberServiceMockTest {
@Mock // (2)
private MemberRepository memberRepository;
@InjectMocks // (3)
private MemberService memberService;
@Test
public void createMemberTest() {
// given
Member member = new Member("hgd@gmail.com", "홍길동", "010-1111-1111");
// (4)
given(memberRepository.findByEmail(member.getEmail()))
.willReturn(Optional.of(member)); // (5)
// when / then (6)
assertThrows(BusinessLogicException.class, () -> memberService.createMember(member));
}
}
- Spring을 사용하지 않고, Junit에서 Mockito의 기능을 사용하기 위해서는 (1)과 같이 @ExtendWith(MockitoExtension.class)를 추가해야 한다.
- (2)와 같이 @Mock 애너테이션을 추가하면 해당 필드의 객체를 Mock 객체로 생성한다.
- 그리고 (3)과 같이 @InjectMocks 애너테이션을 추가한 필드에 (2)에서 생성한 Mock 객체를 주입해 준다.
- 즉, (3)의 memberService 객체는 주입 받은 memberRepository Mock 객체를 포함하고 있다.
- (4)에서는 (2)에서 생성한 memberRepository Mock 객체로 Stubbing을 하고 있다.
memberRepository.findByEmail(member.getEmail())의 리턴 값으로 (5)와 같이 Optional.of(member)를 지정했기 때문에 테스트 케이스를 실행하면 결과는 “passed”이다.
Optional.of(member) 의 member 객체에 포함된 이메일 주소가 memberRepository.findByEmail(member.getEmail()) 에서 파라미터로 전달한 이메일 주소와 동일하기 때문에 검증 결과가 “passed”라는 사실을 잘 이해하자
총정리
- 테스트 세계에서의 Mock은 바로 가짜 객체를 의미한다.
- Mockito는 Mock 객체를 생성하고, 해당 Mock 객체가 진짜처럼 동작하게 하는 기능을 하는 Mocking framework(또는 라이브러리)이다.
- @MockBean 애너테이션은 Application Context에 등록되어 있는 Bean에 대한 Mockito Mock 객체를 생성하고 주입해주는 역할을 한다.
- Junit에서 Spring을 사용하지 않고 순수하게 Mockito의 기능만을 사용하기 위해서는 @ExtendWith(MockitoExtension.class)를 클래스 레벨에 추가해야 한다.
- @Mock 애너테이션을 추가하면 해당 필드의 객체를 Mock 객체로 생성한다.
- @Mcok 애너테이션을 통해 생성된 Mock 객체는 @InjectMocks 애너테이션을 추가한 필드에 주입된다.
학습 목표
- TDD가 무엇인지 이해할 수 있다.
- TDD의 개발 흐름을 이해할 수 있다.
- TDD의 장단점을 이해할 수 있다.
TDD(Test Driven Development, 테스트 주도 개발)란?
TDD의 개념을 한마디로 요약하자면 ‘테스트를 먼저 하고 구현은 그 다음에 한다’ 로 요약할 수 있다.
TDD가 아닌 전통적인 개발 방식
- 서비스 제작에 관여하는 이해 당사자(기획자, 프런트엔드 개발자, 백엔드 개발자, 웹 디자이너 등)가 모여 서비스에 대한 컨셉과 해당 컨셉에 따른 요구 사항을 지속적으로 수집한다.
- 수집된 요구 사항에 맞춰 서비스를 화면으로 제공하기 위한 UI(User Interface)를 설계하면서 구체적인 기능 요구 사항들을 정의한다.
- 프런트엔드 개발자는 기능 요구 사항과 UI를 통해 프런트엔드 측 개발을 진행하고, 웹 디자이너는 화면을 디자인 하며, 백엔드 개발자는 역시 기능 요구 사항에 맞춰 백엔드 애플리케이션을 디자인 한다.
1번, 2번, 3번 중에서 일반적으로 3번의 과정을 진행하면서 요구 사항이 수정되기도 하고 그에 따라 UI가 변경되기도 하며, 프런트엔드와 백엔드 측 설계가 변경되는 경우가 많다.
3번 과정에서 백엔드 개발자의 개발 흐름은 일반적으로 다음과 같다.
- 이해 당사자들 간에 수집된 요구 사항과 설계된 화면(UI 설계서 등) 등을 기반으로 도메인 모델을 도출한다.
- 도출된 도메인 모델을 통해 클라이언트의 요청을 받아들이는 엔드포인트와 비즈니스 로직, 데이터 액세스를 위한 클래스와 인터페이스 등을 설계해서 큰 그림을 그려본다.
- 클래스 설계를 통해 애플리케이션에 대한 큰 그림을 그려보았다면 클래스와 인터페이스의 큰 틀을 작성한다.
- 클래스와 인터페이스의 큰 틀이 작성되었다면 클래스와 인터페이스 내에 메서드를 정의하면서 세부 동작을 고민하고, 코드로 구현한다.
- 해당 메서드의 기능 구현이 끝났다면 구현한 기능이 잘 동작하는지 테스트 한다.
- 테스트에 문제가 발생한다면 구현한 코드를 디버깅하면서 문제의 원인을 찾는다.
여기서 백엔드 개발자의 개발 흐름을 예로 들었지만 프런트엔드 개발자든 백엔드 개발자든 간에 애플리케이션 개발 흐름은 ‘선 구현, 후 테스트’가 일반적인 흐름이라고 볼 수 있다.
TDD 방식으로 개발하며 TDD의 특성 알아보기
그렇다면 TDD 방식으로 개발을 어떻게 진행하는 걸까?
회원 등록 시, 입력하는 로그인 인증용 패스워드의 유효성을 검증하는 기능을 TDD 방식을 개발하여 이해해보자
패스워드 유효성을 검증에 통과하는 조건은 다음과 같다.
- 패스워드 길이는 8 ~ 20 사이의 길이(length)여야 한다.
- 패스워드는 알파벳 소문자 + 알파벳 대문자 + 숫자 + 특수 문자 형태로 구성되어야 한다.
- 알파벳 대/소문자와 숫자를 제외한 모든 문자는 특수문자라고 가정한다.
먼저 패스워드 유효성 검증을 수행 할 테스트 클래스와 테스트 케이스의 이름을 정한다.
public class PasswordValidatorTest {
@DisplayName("패스워드 유효성 검증 테스트: 모든 조건에 만족")
@Test
public void validatePassword() {
}
}
이 상태에서 테스트 케이스를 실행한다면 구체적인 테스트 코드가 전혀 없기 때문에 일단 테스트 케이스의 실행 결과는 “passed”일 것이다.
✅ 모든 유효성 검증 조건을 만족하는 테스트
public class PasswordValidatorTest {
@DisplayName("패스워드 유효성 검증 테스트: 모든 조건에 만족")
@Test
public void validatePasswordAllCriteria() {
// given
String password = “Abcd1234!”
// when
PasswordValidator validator = new PasswordValidator();
Executable executable = () -> validator.validate(password);
// then
assertDoesNotThrow(executable);
}
}
이렇게 하면 유효성 검증에 통과하지 못하는 실패한 테스트 케이스를 성공하는 테스트로 단계적으로 수정하기 때문에 모든 조건에 만족하기 위해 한꺼번에 너무 많은 기능을 구현하지 않고 점진적으로 수정해 갈 수 있다.
⭐ 즉 모든 조건에 만족하는 테스트를 먼저 진행한 뒤에 조건에 만족하지 않는 테스트를 단계적으로 진행하면서 실패하는 테스트를 점진적으로 성공시켜나간다.
위 코드를 실행한다면 아직 존재하지 않는 PasswordValidator 클래스 때문에 컴파일 에러가 발생한다.
컴파일 에러를 해결하는 방법은 매우 단순하다.
존재하지 않는 PasswordValidator 클래스를 생성하면 된다.
public class PasswordValidator {
}
위와같이 같이 존재하지 않는 PasswordValidator 클래스를 생성함으로써 클래스가 존재하지 않아서 발생하는 컴파일 에러는 해결될 것이다.
하지만 위와 같이 코딩을 해도 아직 빨간 글자가 사라지지 않기 때문에 당연히 컴파일 에러가 발생할 것이다.
PasswordValidator 클래스에 validate() 메서드가 없어서 발생하는 에러를 해결해보자
public class PasswordValidator {
public void validate(String password) {
}
}
PasswordValidator 클래스에 validate() 메서드를 추가한다면 빨간줄이 사라질 것이고, 이 상태에서 테스트 케이스를 실행하면 “passed”를 확인할 수 있다.
⭐ “failed”인 테스트 케이스를 지속적으로 그리고 단계적으로 수정하면서 테스트 케이스 실행 결과가 “passed”가 되도록 만들고 있다.
이러한 방식은 TDD 개발 방식이라고 하는 것이다.
✅ (알파벳 소문자 + 알파벳 대문자 + 숫자 + 특수 문자) 조건에서 특수 문자가 빠진 경우 테스트
public class PasswordValidatorTest {
@DisplayName("모든 조건에 만족")
@Test
public void validatePasswordAllCriteria() {
// given
String password = "Abcd1234!";
// when
PasswordValidator validator = new PasswordValidator();
Executable executable = () -> validator.validate(password);
// then
assertDoesNotThrow(executable);
}
// (1)
@DisplayName("특수 문자 포함 안됨 테스트")
@Test
public void validatePasswordWithoutSpecialCharacter() {
// given
String password = "Abcd1234";
// when
PasswordValidator validator = new PasswordValidator();
Executable executable = () -> validator.validate(password);
// then
assertDoesNotThrow(executable);
}
}
현재 상태에서 위 코드의 두 개의 테스트 케이스를 모두 실행하면 실행 결과는 “passed”일 것이다.
PasswordValidator 클래스에 패스워드를 검증하는 조건이 하나도 없기 때문이다.
그렇다면 패스워드가 어떤 조건에 만족하든지 간에 무조건 Exception을 던지게 하면 될 것이다.
public class PasswordValidator {
public void validate(String password) {
throw new RuntimeException("Invalid password");
}
}
PasswordValidator.validate() 메서드가 무조건 RuntimeException을 던지도록 수정했다.
이 상태에서는 모든 테스트 케이스를 실행하면 두 개의 테스트 케이스 모두 “failed”이 될 것이다.
이제 패스워드에 특수 문자를 포함하지 않는 경우에만 “failed” 되도록 기능을 수정해보자
public class PasswordValidator {
public void validate(String password) {
// (1)
boolean containSpecialCharacter =
password.chars()
.anyMatch(ch -> !(Character.isDigit(ch) || Character.isAlphabetic(ch)));
// (2)
if (!containSpecialCharacter) {
throw new RuntimeException("Invalid password");
}
}
}
(1)과 같이 특수 문자를 포함하고 있는지의 여부를 체크한 뒤에, (2)에서 특수 문자를 포함하고 있지 않을 경우에만 예외를 던지도록 기능을 수정했다.
테스트를 다시 실행하면 validatePasswordWithoutSpecialCharacter() 테스트 케이스는 특수 문자가 없기 때문에 “failed”가 된다.
validatePasswordWithoutSpecialCharacter() 의 실행 결과를 “passed”가 되도록 해보자
public class PasswordValidatorTest {
...
...
@DisplayName("특수 문자 포함 안됨")
@Test
public void validatePasswordWithoutSpecialCharacter() {
// given
String password = "Abcd1234&!"; // (1)
// when
PasswordValidator validator = new PasswordValidator();
Executable executable = () -> validator.validate(password);
// then
assertDoesNotThrow(executable);
}
}
(1)과 같이 특수 문자가 포함이 되도록 패스워드를 수정한다면 이제 테스트의 실행 결과는 “passed”가 되어야할 것이다.
수정된 코드가 잘 동작하는지를 체크하기 위해서 전체 테스트 케이스를 다시 실행한다면 “passed”가 될 것이다.
그런데 수정된 코드가 잘 동작하는지 여부를 정확하게 테스트하기 위해서는 특수 문자를 포함하지 않은 패스워드를 한번 더 테스트 해서 “failed”인지 확인하는 것이 좋다.
public class PasswordValidatorTest {
...
...
@DisplayName("특수 문자 포함 안됨 테스트")
@Test
public void validatePasswordWithoutSpecialCharacter() {
// given
String password1 = "Abcd1234&!";
String password2 = "Abcd1234"; // (1)
// when
PasswordValidator validator = new PasswordValidator();
Executable executable1 = () -> validator.validate(password1);
Executable executable2 = () -> validator.validate(password2); // (2)
// then
assertDoesNotThrow(executable1);
assertDoesNotThrow(executable2); // (3)
}
}
(1)과 같이 특수 문자를 포함하지 않은 패스워드를 테스트 데이터로 추가 한 후, (2)에서 동작을 정의하고 (3)에서 추가된 테스트 데이터에 대한 검증을 실시한다면 테스트 결과는 “failed”될 것이다.
이제 특수 문자가 포함되지 않은 패스워드는 유효성 검증에서 실패한다는 것을 확실히 검증할 수 있게 되었다.
이제 다음 조건에 대해 앞에서 특수 문자 포함 여부를 검증한 방식과 마찬가지로 테스트와 검증, 리팩토링 단계를 반복해서 진행하면 될 것이다.
⭐ TDD의 개발 방식은 ‘실패하는 테스트 → 실패하는 테스트를 성공할 만큼의 기능 구현 → 성공하는 테스트 → 리팩토링 → 실패하는 테스트와 성공하는 테스트 확인’ 이라는 흐름을 반복한다.
TDD의 특징 정리
- TDD는 모든 조건에 만족하는 테스트를 먼저 진행한 뒤에 조건에 만족하지 않는 테스트를 단계적으로 진행하면서 실패하는 테스트를 점진적으로 성공시켜 간다.
- TDD는 테스트 실행 결과가 “failed”인 테스트 케이스를 지속적으로 그리고 단계적으로 수정하면서 테스트 케이스 실행 결과가 “passed”가 되도록 만들고 있다.
- TDD는 테스트가 “passed” 될 만큼의 코드만 우선 작성한다.
- TDD는 ‘실패하는 테스트 → 실패하는 테스트를 성공할 만큼의 기능 구현 → 성공하는 테스트 → 리팩토링 → 실패하는 테스트와 성공하는 테스트 확인’ 이라는 흐름을 반복한다.
TDD의 장점과 단점
✔ TDD의 장점
TDD의 장점을 정리하자면 다음과 같습니다.
- 테스트를 통과 할 만큼의 기능을 구현하므로 한번에 너무 많은 기능을 구현할 필요가 없다.
- 테스트의 코드가 추가되면서 검증하는 범위가 넓어질 수록 기능 구현도 점진적으로 완성되어 간다.
ㄴ즉, 단순한 기능에서 복잡한 기능으로 확장 되면서 그때 그때 검증을 빼먹지 않고 할 수 있다.
- 리팩토링 할 부분이 눈에 보이면 그때 그때 리팩토링을 빠르게 진행하기 때문에 리팩토링의 비용이 상대적으로 적어진다.
ㄴ우리가 실무에서 하나의 기능을 완성해놓고 보면 리팩토링이 필요한 부분이 눈에 들어오는 경우가 많다.
ㄴ그런데 일정 상의 이유로 리팩토링을 하지 않고, 대충 넘어가는 경우가 상당히 많은데, TDD에 익숙해지면 그때 그때 리팩토링을 진행하게 되므로 리팩토링에 대한 비용이 상대적으로 적어질 가능성이 높다.
- TDD 방식에서는 항상 테스트 케이스가 존재하기 때문에 기존 코드를 수정 하더라도 상대적으로 심리적 불안감이 줄어들 수 있다.
- 리팩토링을 통해 꾸준히 코드를 개선하므로 코드의 품질을 일정 부분 유지할 수 있다.
ㄴ리팩토링이 전혀 이루어지지 않은 상태에서 코드 품질이 점점 나빠져가는 상태로 애플리케이션이 구현되고있다면 그만큼의 유지 보수 비용이 높아진다는 사실을 알 수 있다.
- 코드 수정 이 후, 바로 테스트를 진행할 수 있으므로 코드 수정 결과를 빠르게 피드백 받을 수 있다.
ㄴ수정 결과를 그때 그때 확인할 수 있으므로 잘못된 코드가 남아있을 가능성이 상대적으로 줄어든다.
✔ TDD의 단점
그렇다면 TDD의 단점은 무엇일까?
- 가장 큰 단점은 TDD의 개발 방식이 익숙하지 않다는 것이다.
ㄴ우리가 일반적으로 진행했던 익숙한 개발 방식은 단번에 버리고 TDD 방식으로 개발을 진행한다는건 상당히 어려운 일이다.
- 테스트 코드의 작성에 익숙하지 않은 사람, 테스트 코드를 작성하길 원치 않는 사람들에게는 부정적인 방식일 수 있다.
ㄴ하지만 기능 구현이 끝나더라도 테스트 없이 하나의 애플리케이션이 릴리즈 되지 않는다.
ㄴ따라서 어떤 식으로든 개발자가 테스트를 할 수 밖에 없고, 기능 구현이 다 끝난 상태에서 테스트를 수동으로 하게되면 전체적으로 개발 완료 시간이 더 늘어날 가능성이 높다.
ㄴ결국 테스트를 미리 미리하는게 좋으며, 테스트 시간을 줄이기 위해서는 테스트를 자동화 할 수 밖에 없다.
ㄴ테스트를 자동화 하려면 결국 테스트 코드를 만들 수 밖에 없다.
- 팀 단위로 개발을 진행해야 하므로 팀원들 간 사전에 협의가 되어야 한다.
ㄴ개발을 혼자서 할 수는 없다.
ㄴTDD 방식을 적용하기 위해서는 팀 차원에서 TDD 방식에 대한 논의가 필요하고, 팀원들 각자가 TDD를 수용하고, 적용해 보려는 합의가 필요하다.
총정리
- TDD는 테스트가 개발을 주도하는 방식이다.
- TDD는 테스트 코드를 먼저 작성하고, 그 다음에 기능을 구현한다.
- TDD 특징
ㄴTDD는 모든 조건에 만족하는 테스트를 먼저 진행한 뒤에 조건에 만족하지 않는 테스트를 단계적으로 진행하면서 실패하는 테스트를 점진적으로 성공시켜 나간다.
ㄴTDD는 테스트 실행 결과가 “failed”인 테스트 케이스를 지속적으로 그리고 단계적으로 수정하면서 테스트 케이스 실행 결과가 “passed”가 되도록 만든다.
ㄴTDD는 테스트가 “passed” 될 만큼의 코드만 우선 작성한다.
ㄴTDD는 ‘실패하는 테스트 → 실패하는 테스트를 성공할 만큼의 기능 구현 → 성공하는 테스트 → 리팩토링 → 실패하는 테스트와 성공하는 테스트 확인’ 이라는 흐름을 반복한다.
'코드스테이츠' 카테고리의 다른 글
코드스테이츠 58일차 (0) | 2022.07.18 |
---|---|
코드스테이츠 57일차 (0) | 2022.07.15 |
코드스테이츠 55일차 (0) | 2022.07.13 |
코드스테이츠 54일차 (0) | 2022.07.12 |
코드스테이츠 53일차 (0) | 2022.07.11 |