1. 학습일지
1) 테스트 방법론

-
버그란?
- 소프트웨어가 예상하지 못한 결과를 내는 것으로
- '소스 코드'나 '설계과정에서의 오류' 때문에 발생함
-
개발 코드 배포 전, 버그를 찾아내는 방법
-
블랙박스 테스팅:
- 소프트웨어 내부 구조나 동작원리를 모르는 블랙박스와 같은 상태에서, 즉 웹 서비스의 사용자 입장에서 동작을 검사하는 방법입니다!
- 누구나 테스트 가능하다
- 기능이 증가될 수록 테스트의 범위가 증가
- 테스트 하는 사람에 따라 테스트 퀄러티가 다를 수 있음 → QA 직군이 있는 이유
-
개발자 테스트:
- 1) UI 테스트는 spring 기동하여 구현된 웹에서 직접하는 것: 클라이언트 코드가 다 작성 되어야만 검증 가능
- 2) API 호출 앱(ARC, Postman 등): 여러 API를 한번에 검증하긴 어려움
- 3) ✔스프링 테스트 프레임워크:
- 개발자가 직접 "본인이 작성한 코드"를 검증하기 위해 "테스트 코드"를 작성 "개발자 본인이 작성한 코드는 본인이 가장 잘 안다."
- 빠르고 정확한 테스트 가능 (예상 동작 VS 실제 동작)
- 테스트 자동화 가능 (배포 절차 시 테스트 코드가 수행되어 동작 검증)
- 리팩토링 후 기존 동작에 대한 보증수표!!!
- 개발 시간이 오래 걸림 / 테스트 코드를 유지보수하는 비용 발생
-> 스프링에서는 테스트 코드 작성을 잘 할 수 있는 환경을 제공해준다.
2) JUnit을 이용한 단위 테스트
- 단위테스트란? 프로그램을 작은 단위로 쪼개서 각 단위가 정확하게 동작하는지 검사하고 이를 통해 문제 발생 시 정확하게 어느 부분이 잘못되었는지를 재빨리 확인할 수 있게 해준다.

- Development: 개발
- Unit Tests (단위 테스트): 개발자 테스트
- QA Testing:
- 블랙박스 테스팅
- 주로 QA 팀이 Production 환경과 유사한 환경(Stage)에서 테스팅
- Production: 실 서비스 운영 환경
-> 버그 발견 시간이 늦어짐에 따라 비용이 기하급수적으로 커진다!!
- JUnit이란? 자바 프로그래밍 언어용 단위 테스트 프레임워크
- build.gradle에 JUnit 사용을 위한 환경설정이 이미 되어있다.
- (디폴트로 제공되는 것만 봐도 TDD의 효용성은 이미 입증된 것 같다.)
dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
- Edge case 고려하기
- Edge case는 알고리즘이 처리하는 데이터의 값이 알고리즘의 특성에 따른 일정한 범위를 넘을 경우에 발생하는 문제를 가리킨다.
- 따라서 테스트 시 엣지 케이스를 최대한 많이 넣어보는 것을 추천한다.

- TDD(Test-Driven Development)
- AS-IS) 설계 → 개발 → 테스트 (→ 설계 수정)
- TO-BE) 설계 → 테스트 (→설계 수정) → 개발
- 테스트를 먼저 만들고 테스트를 통과하기 위한 것을 짜는 것
- 즉, 만드는 과정에서 우선 테스트를 작성하고 그걸 통과하는 코드를 만들고를 반복하면서 제대로 동작하는지에 대한 피드백을 적극적으로 받는 것이다. (🔗출처 -> 좋은 글인 것 같다. 나중에 또 읽어보기❗)
3) Mock Object 활용하기

- Mock object (가짜 객체)? 이상적으로, 각 테스트 케이스는 서로 분리되어야 한다. 이를 위해 가짜 객체(Mock object)를 생성하는 것도 좋은 방법이다.
- 예를들어 Service를 유닛테스트 하고자 할 경우, 서비스에 DI 되어있는 녀석들을 Mock Object로 만들어줌으로써 온전히 Service만 테스트할 수 있는것이다.
- 단, MockRepository와 같이 테스트할 클래스 외 나머지 클래스는 스프링을 기동 안한다는 가정하에 직접 구현해줘야 한다.
4) Mockito mock을 사용한 단위 테스트

- 위에서 언급한 대로 Mock Object는 실제로 구현해줘야 한다.
- 💥Mockito는 이러한 비용을 줄이고자 Mock 객체를 쉽게 만들 수 있는 방법을 제공하는 것이다.
- 사용 방법은 아래와 같다.
- 테스트할 클래스의 테스트 클래스를 생성한다.
- 클래스 선언부 위에 @ExtendWith(MockitoExtension.class)를 붙여준다.
- 테스트 클래스 내부에서 해당 클래스 외 나머지 클래스에 @Mock을 붙여준다.
- @Mock을 붙인 곳에서 나올 수 있는 결과를 명시해줘야 한다. 이를 사용 케이스를 추가한다고 한다.
ProductService productService = new ProductService(productRepository);
when(productRepository.findById(productId))
.thenReturn(Optional.of(product));
5) 테스트 계층도

- 단위 테스트란?
- 하나의 모듈이나 클래스에 대해 세밀한 부분까지 테스트 가능
- 모듈 간에 상호 작용 검증 못함
- 통합 테스트란?
- 두 개 이상의 모듈이 연결된 상태를 테스트
- 모듈 간의 연결에서 발생하는 에러 검증 가능
- E2E 테스트 (End to End Test)
- 실제 사용자의 실행 환경과 거의 동일한 환경에서 테스트 진행 (=블랙박스 테스팅)
6) 스프링 부트를 이용한 통합 테스트
- 원래 테스트 클래스에서는 Spring이 기동을 하지 않아 Bean을 찾을 수 없어 null이 들어간다.
- 하지만
@SpringBootTest
를 붙여주면 테스트 수행 시 스프링이 동작한다!!
- 또한 @Order(1), @Order(2), ... 를 통해 테스트의 순서를 정할 수 있다.
- @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 랜덤포트로 지정해준다
- Bean에 등록된 Service, Repository를 사용할 수 있다.
7) 스프링 MVC 테스트 (Controller 테스트)
- build.gradle에 testImplementation 'org.springframework.security:spring-security-test'를 추가하면,
- SecurityMockMvcConfigurers를 임포트할 수 있다.
- @WebMvcTest는 Controller를 테스트하는 것
- 테스트 코드는 아래와 같은 방식으로 작성된다.
@Test
@DisplayName("로그인 view")
void test1() throws Exception {
mvc.perform(get("/user/login"))
.andExpect(status().isOk())
.andExpect(view().name("login"))
.andDo(print());
}
@Test
@DisplayName("회원 가입 요청 처리")
void test2() throws Exception {
MultiValueMap<String, String> signupRequestForm = new LinkedMultiValueMap<>();
signupRequestForm.add("username", "제이홉");
signupRequestForm.add("password", "hope!@#");
signupRequestForm.add("email", "hope@sparta.com");
signupRequestForm.add("admin", "false");
mvc.perform(post("/user/signup")
.params(signupRequestForm)
)
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/user/login"))
.andDo(print());
}
- 시큐리티 상황을 전제하기에 권한이 필요한 요청에서는 MockUser도 따로 셋업해줘야 한다.
private void mockUserSetup() {
String username = "제이홉";
String password = "hope!@#";
String email = "hope@sparta.com";
UserRoleEnum role = UserRoleEnum.USER;
Users testUser = new Users(username, password, email, role);
UserDetailsImpl testUserDetails = new UserDetailsImpl(testUser);
mockPrincipal = new UsernamePasswordAuthenticationToken(testUserDetails, "", testUserDetails.getAuthorities());
}
2. 코멘트
- 지금 까지 스프링으로 개발해보며, 의미있는 테스트 코드를 돌려본적은 없는 것 같다.
- 오늘 배운 개념으로 나중에 복잡한 기능을 구현할 때, TDD에 의거하여 선 테스트 후 개발 과정으로 시도해봐야겠다.
- 이로써 스프링 심화강의 3주차까지 완료!