ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [appling] 나머지 Controller 작업 및 Test Code 추가 작성
    appling 프로젝트 2024. 9. 8. 23:44
    728x90
    반응형

    🔴 나머지 Controller 작업

    🟠 put, get

    🟢 put

    @ApiController
    @RequiredArgsConstructor
    @Tag(name = "Product", description = "Product API Documentation")
    public class ProductController {
        private final ProductService productService;
    
        ...
    
        @PutMapping("/product")
        @Operation(summary = "상품 수정", description = "상품 수정 api")
        @ApiResponses(value = {
                @ApiResponse(responseCode = "200", description = "정상", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)),
                @ApiResponse(responseCode = "500", description = "서버 에러", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)),
        })
        public ResponseEntity<ResponseData<PostProductResponse>> putProduct(@RequestBody @Validated PutProductRequest putProductRequest) {
            return ResponseEntity.status(HttpStatus.OK)
                    .body(ResponseData.from(ResponseDataCode.SUCCESS, productService.putProduct(putProductRequest)));
        }
    
    }
    @Getter
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    public class PutProductRequest {
        @JsonProperty("product_id")
        @NotNull(message = "상품 번호를 입력해 주세요.")
        @Schema(description = "상품 번호", example = "1")
        private Long productId;
        @NotNull(message = "상품명을 입력해 주세요.")
        @JsonProperty("product_name")
        @Schema(description = "상품명", example = "시나노 골드")
        private String productName;
        @NotNull(message = "상품 무게를 입력해 주세요.")
        @JsonProperty("product_weight")
        @Schema(description = "상품 무게", example = "10")
        private int productWeight;
        @NotNull(message = "상품 타입을 입력해 주세요. ex) 사과는 11과")
        @JsonProperty("product_type")
        @Schema(description = "상품 타입", example = "13과")
        private String productType;
        @NotNull(message = "상품 가격을 입력해 주세요.")
        @JsonProperty("product_price")
        @Schema(description = "상품 가격", example = "150000")
        private int productPrice;
        @NotNull(message = "상품 수량을 입력해 주세요.")
        @JsonProperty("product_stock")
        @Schema(description = "상품 수량", example = "0")
        private int productStock;
        @NotNull(message = "상품 상태를 입력해 주세요.")
        @JsonProperty("product_status")
        @Schema(description = "상품 상태", example = "SOLD_OUT")
        private ProductStatus productStatus;
    
    }

    🟢 get

    @ApiController
    @RequiredArgsConstructor
    @Tag(name = "Product", description = "Product API Documentation")
    public class ProductController {
        private final ProductService productService;
    
        ...
    
        @GetMapping("/product")
        @Operation(summary = "상품리스트", description = "상품리스트 api")
        @ApiResponses(value = {
                @ApiResponse(responseCode = "200", description = "정상", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)),
                @ApiResponse(responseCode = "500", description = "서버 에러", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))
        })
        public ResponseEntity<ResponseData<ProductListResponse>> getProductList(
                @Schema(description = "페이지 크기", defaultValue = "10", nullable = true) @RequestParam(required = false, defaultValue = "10" ) int size,
                @Schema(description = "페이지 번호", defaultValue = "0", nullable = true) @RequestParam(required = false, defaultValue = "0") int page,
                @Schema(description = "페이지 정렬(proudct id 기준)", defaultValue = "DESC", nullable = true) @RequestParam(required = false, defaultValue = "DESC" ) Sort sort,
                @Schema(description = "검색어(아직 기능 개발 안함)", defaultValue = "", nullable = true) @RequestParam(required = false, defaultValue = "") String search) {
    
            return ResponseEntity.status(HttpStatus.OK)
                    .body(ResponseData.from(ResponseDataCode.SUCCESS, productService.getProductList(GetProductListRequest.from(size, page, sort, search))));
        }
    }

    🟠 Test Code 작성

    build를 해보니 jacoco에서 coverage를 충족시키지 못해 터져버린다. controller에 test를 까먹었다. test code를 작성하자!

    🟢 post

    @SpringBootTest
    @AutoConfigureMockMvc
    class ProductControllerTest {
    
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private MockMvc mockMvc;
    
        @Test
        @DisplayName("/post/product")
        void postProduct() throws Exception {
            //given
            PostProductRequest productRequest = PostProductRequest.builder()
                    .productName("등록 상품")
                    .productWeight(5)
                    .productPrice(10000)
                    .productStock(10)
                    .productType("11과")
                    .build();
    
    
            //when
            ResultActions perform = mockMvc.perform(post("/api/v1/product")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(productRequest)));
    
            //then
            perform.andExpect(status().isCreated());
        }
    }

    🟢 put

    @SpringBootTest
    @AutoConfigureMockMvc
    @Transactional(readOnly = true)
    class ProductControllerTest {
    
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private MockMvc mockMvc;
    
        @Autowired
        private ProductRepository productRepository;
    
        @BeforeEach
        void setUp() {
            productRepository.deleteAll();
        }
    
        ...
    
        @Test
        @DisplayName("[PUT] /api/v1/product")
        void putProduct() throws Exception {
            //given
            PostProductRequest productRequest = PostProductRequest.builder()
                    .productName("등록 상품")
                    .productWeight(5)
                    .productPrice(10000)
                    .productStock(10)
                    .productType("11과")
                    .build();
            ProductEntity saveProduct = productRepository.save(productRequest.toProductEntity());
    
            PutProductRequest putProductRequest = PutProductRequest.builder()
                    .productId(saveProduct.getProductId())
                    .productType("12과")
                    .productPrice(100000)
                    .productStock(10)
                    .productWeight(10)
                    .productName("수정 상품")
                    .productStatus(ProductStatus.ON_SALE)
                    .build();
    
            //when
            ResultActions perform = mockMvc.perform(put("/api/v1/product")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(putProductRequest)));
    
            //then
            perform.andExpect(status().isOk());
        }
    
    }

    🟢 get

    @SpringBootTest
    @AutoConfigureMockMvc
    @Transactional(readOnly = true)
    class ProductControllerTest {
    
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private MockMvc mockMvc;
    
        @Autowired
        private ProductRepository productRepository;
    
        @BeforeEach
        void setUp() {
            productRepository.deleteAll();
        }
    
        ...
    
        @Test
        @DisplayName("[GET] /api/v1/product")
        void getProduct() throws Exception {
            //given
            PostProductRequest productRequest = PostProductRequest.builder()
                    .productName("등록 상품")
                    .productWeight(5)
                    .productPrice(10000)
                    .productStock(10)
                    .productType("11과")
                    .build();
            ProductEntity saveProduct1 = productRepository.save(productRequest.toProductEntity());
            ProductEntity saveProduct2 = productRepository.save(productRequest.toProductEntity());
    
            //when
            ResultActions perform = mockMvc.perform(get("/api/v1/product").param("size", "10").param("page", "0").param("sort", "DESC"));
    
            //then
            perform.andExpect(status().isOk());
        }
    
    }

    🟠 branch coverage

    controller를 모두 작성했는데 build에서 또 터지더라 이유를 보니 ProductCustomRepository에서 커버리지가 50프로로 나왔다. 로직을 보니 중간에 조건에 따라 분기되는 곳이 있는데 한쪽 로직만 테스트를 했던 것이다.

    해당 부분도 테스트 코드를 추가해주자

    🟢 로직

    @Repository
    @RequiredArgsConstructor
    public class ProductCustomRepositoryImpl implements ProductCustomRepository{
        private final JPAQueryFactory querydsl;
    
        @Override
        public Page<ProductEntity> findAll(GetProductListRequest getProductListRequest) {
            Pageable pageable = Pageable.ofSize(getProductListRequest.getSize()).withPage(getProductListRequest.getPage());
    
            Sort sort = getProductListRequest.getSort();
    
            QProductEntity product = QProductEntity.productEntity;
    
            List<ProductEntity> fetch = querydsl.selectFrom(product)
                    .orderBy(sort == Sort.ASC ? product.productId.asc() : product.productId.desc())
                    .offset(pageable.getOffset())
                    .limit(pageable.getPageSize())
                    .fetch();
    
            Long total = querydsl.selectFrom(product).fetch().stream().count();
            return new PageImpl<>(fetch, pageable, total);
        }
    }

    여기서 orderBy()를 결정할때 분기처리 되어 있는데 해당 부분의 DESC만 테스트를 하고 있었다.

    🟢 Test

    @SpringBootTest
    @Transactional(readOnly = true)
    class ProductCustomRepositoryImplTest {
        @Autowired
        private ProductCustomRepository productCustomRepository;
    
        @Autowired
        private ProductRepository productRepository;
    
        @BeforeEach
        void setUp() {
            productRepository.deleteAll();
        }
    
        @Test
        @DisplayName("findAll asc 성공")
        void findAllASC() {
            //given
            PostProductRequest productRequest = PostProductRequest.builder()
                    .productName("등록 상품")
                    .productWeight(5)
                    .productPrice(10000)
                    .productStock(10)
                    .productType("11과")
                    .build();
    
            ProductEntity saveProduct1 = productRepository.save(productRequest.toProductEntity());
            ProductEntity saveProduct2 = productRepository.save(productRequest.toProductEntity());
    
            //when
            Page<ProductEntity> productPage = productCustomRepository.findAll(GetProductListRequest.from(10, 0, Sort.ASC, ""));
    
            //then
            Assertions.assertThat(productPage.getContent().get(0).getProductId()).isEqualTo(saveProduct1.getProductId());
        }
    
        @Test
        @DisplayName("findAll desc 성공")
        void findAllDESC() {
            //given
            PostProductRequest productRequest = PostProductRequest.builder()
                    .productName("등록 상품")
                    .productWeight(5)
                    .productPrice(10000)
                    .productStock(10)
                    .productType("11과")
                    .build();
    
            ProductEntity saveProduct1 = productRepository.save(productRequest.toProductEntity());
            ProductEntity saveProduct2 = productRepository.save(productRequest.toProductEntity());
    
            //when
            Page<ProductEntity> productPage = productCustomRepository.findAll(GetProductListRequest.from(10, 0, Sort.DESC, ""));
    
            //then
            Assertions.assertThat(productPage.getContent().get(0).getProductId()).isEqualTo(saveProduct2.getProductId());
        }
    
    }

    다른 곳에서 DESC를 테스트하고 있긴 하지만 해당 테스트 코드가 수정되면 결국 여기서 또 터지기 때문에 2개의 케이스로 테스트를 진행했다.

    이제 정상적으로 Build가 되었다!

    728x90
    반응형
Designed by Juno.