ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [SpringBoot] @TestInstance / @WithUser & @WithAccount / 중복된 코드에 대한 메서드 분리
    Back-end/TIL 2022. 3. 21. 09:00

    특정 API의 POST와 UPDATE 시 예외처리 코드를 추가하는 업무를 진행했다. 

    업무를 진행하며 배운 내용에 대해 잊지 않기 위해 작성하였다.

     

    📌 SpringBoot의 테스트 코드 작성시 @TestInstance(TestInstance.Lifecycle.PER_CLASS)

    • 해당 어노테이션은 테스트 인스턴스의 라이프 사이클을 설정할 때 사용한다.
    • PER_METHOD: 테스트 함수 당 1개의 인스턴스가 생성된다.
    • PER_CLASS: 테스트 클래스 당 1개의 인스턴스가 생성된다.
    • 라이프 사이클을 클래스 단위로 설정하면, @BeforeAll, @AfterAll 어노테이션을 static method가 아닌 곳에서도 사용할 수 있다.
    • 장점
      • 매 테스트 메서드마다 클래스의 인스턴스를 생성하는데 필요한 자원이 많을 때 유용하다. (ex. 데이터베이스 연결, 큰 용량의 파일 로딩 등)
      • 상태 공유가 가능하다. 단위 테스트에서는 상태 공유 패턴이 지양해야하는 형태이지만, 통합 테스트에서는 유용하다. (이번에 회사에서 작성한 테스트 코드는 통합 테스트였으므로 사용했다)
      • @BeforeEach나 @AfterEach를 사용해 공유되는 변수의 상태를 초기화시키면 된다. 

    참고) https://www.baeldung.com/junit-testinstance-annotation

     

     

    여기서부터는 작성한 코드(POST와 UPDATE시 예외처리, 해당 API의 통합테스트 작성)에 대한 팀장님의 피드백을 받고 정리한 내용이다. 

     

    📌 @WithUser, @WithAccount

    • 위에서 작성했듯이, @TestInstance(TestInstance.Lifecycle.PER_CLASS) 어노테이션을 사용한 이유는 위와 같은 장점도 있었지만, 로그인을 위해 기존에 인스턴스 변수로 선언되어 있던(회사 코드에서 이렇게 설정이 되어 있었음) mockMvc를 사용하기 위함도 있었다. 이를 위해 해당 어노테이션을 사용하고, @BeforeAll 어노테이션이 사용된 메서드는 static이 아닌 인스턴스로 메서드로 정의해 사용하였다.
    • 하지만, static 메서드의 빠른 속도를 사용하기 위해 @BeforAll의 static 메서드를 유지해서 사용하려면, @WithUser, @WithAccount 사용해 사용자를 생성할 수도 있다.
    • 다만, 꼭 위와같은 방법이 정답은 아니고, 상황에 따라 적절하게 사용하면 된다.

     

    통합 테스트 작성 후 궁금했던 부분은 '값 변경 후 or 값 생성 후 데이터베이스에서 불러와 확인을 해봐야 하는지' 였다. 

    팀장님께 여쭤보니 이것도 정답이 있는 것은 아니지만, 팀장님의 경우 API 호출 결과 200 OK가 나오면 데이터베이스에 저장이 되었다고 생각하신다고 하셨다.

     

    📌 중복된 코드에 대한 메서드 분리 ❗

    상황

    Post와 Put에 대한 Service 클래스에서 취하된 약품인지 확인하는 코드가 중복되어 작성되었다.

     

    개선

    해당 코드 내용이 2 곳 이상에서 사용되면 따로 메서드로 분리하는 것이 좋다. (다른 곳에서 추가로 사용될 수도 있으니까)

     

    추가 고민 부분: 메서드의 형태

    메서드로 분리하고자 하였는데, 생각해보니 수정 가능한 메서드의 형태도 2가지가 나왔다.

    //기존 코드
    if(medicine.getIsDrop()){
    	throw new CommonBadRequestException("userMedicineIsDropCreate");
    }
    
    if(medicine.getIsDrop()){
    	throw new CommonBadRequestException("userMedicineIsDropUpdate");
    }
    
    //수정1
    private boolean isDropMedicine(Medicine medicine) {	//분리된 메서드
    	return medicine.getIsDrop();
    }
    
    if(isDropMedicine(medicine)) {
    	throw new CommonBadRequestException("userMedicineIsDropCreate");
    }
    
    if(isDropMedicine(medicine)) {
    	throw new CommonBadRequestException("userMedicineIsDropUpdate");
    }
    
    //수정2
    private void isDropMedicine(Medicine medicine, String exceptionMsg) {	//분리된 메서드
    	if(medicine.getIsDrop()) {
    		throw new CommonBadRequestException(exceptionMsg);
    	}
    }
    
    isDropMedicine(medicine, "userMedicineIsDropCreate");
    
    isDropMedicine(medicine, "userMedicineIsDropUpdate");
    • 수정1의 경우에는 boolean 값만 반환하는 메소드이므로, 기존의 코드와 크게 달라지는 부분이 없어 이렇게 메소드를 구성하는 것은 맞지 않다.
    • 수정2의 경우처럼 따로 분리한 메소드 내부에서 예외처리를 하는 것도 괜찮다. 어차피 예외 발생되면 intelliJ가 자세히 알려주기 때문이다.
    • 지금은 2개의 param으로 공통 로직을 뽑아낼 수 있지만, 만약 여러 개의 param이 전달되어야 하는 상황에서는 DTO로 만들어 전달하는 방법도 있다. (팀장님께서는 3개 이상이면 DTO로 생성해 전달한다고 한다)

     

    ▶️ 고민해야할 부분1 - public vs. private

    A) Service 클래스 내부에 public 메서드로 정의해서 Controller 부분에서도 호출 가능하도록 함 

    public postController() {
    	//1.취하된 약품인지 확인
    	//2.약품 등록
    }
    • 장점
      • 만약 데이터베이스에 취하된 약품이 더 많을 경우 1번에서 처리한 후 2번은 수행이 안되므로 불필요한 자원 낭비 및 DB 처리가 필요하지 않게 된다.
    • 단점
      • 만약 데이터베이스에 취하된 약품이 많지 않을 경우 1번과 2번에서 모두 DB 조회를 하게 된다.

     

    B) Service 클래스 내부에 private 메서드로 정의해서 Service 클래스 내부에서만 호출 가능하도록 함

    • 단점은 딱히 없다.

     

    ▶️ 고민해야할 부분2 - 접근 지정자(public vs. private vs. protected)

    • 만약 해당 메서드가 Service 외에 다른 모듈에서도 사용된다면, 접근 지정자를 private 대신 protected까지는 설정해도 좋다. (public으로 하면 오남용될 수 있으므로 지양한다)

     

    ▶️ 결정사항

    현재 우리 데이터베이스에는 취하된 약품이 많지 않고, 아직 특정 Service 클래스에서만 사용하므로, 해당 Service 클래스 내에 private 메소드로 분리하였다.

     

     

    📌 느낀점

    단순한 코드 작성이어도 생각해야 될 부분이 많다는 것을 느꼈다. 앞으로 2번 생각해보는 개발자가 되야겠다.

    댓글

Designed by Tistory.