• Home
  • About
    • Zzu-h photo

      Zzu-h

      주니어 Android 개발자입니다.

    • Learn More
    • Email
    • Instagram
    • Tistory
    • Github
  • Posts
    • All Posts
    • All Tags
    • All Categories
  • Projects

Exception and Test

23 Jul 2021

Reading time ~3 minutes

Validation 활용

  • JSON 객체는 파라미터가 되는 필드들을 JSON 객체로 감싼다.
    • 즉, 어떤 필드가 null인지 알 수 없다.

validation 적용

  1. 필드에 NotNull 조건 추가
    • @NotNull 어노테이션 추가
      public class UserVO implements Serializable{
       ...
       @NotNull(message = "userName필드가 null입니다.")
       private String userName;
       ...
      }
      
  2. controller 클레스에 validation 적용
    • @Validated 어노테이션 추가
      @RequestMapping(value = "/regist")
      public ResponseEntity<?> registUser(@Validated @RequestBody UserVO userVO){
       System.out.println("controller vo check::" + userVO.toString() );
       userService.createUser(userVO);
       return new ResponseEntity(null, HttpStatus.OK);
      }
      

예외 처리를 위한 ControllerAdvice

Controller에 대한 예외 처리

  • @ControllerAdvice
    • Controller 클래스들의 모든 예외에 대해서 공통으로 초리할 수 있다.
    • 이를 사용하려면 @EnableWebMvc 선언 또는 <mvc:annotation-driven/> 추가

예외 클래스 작성

  • 에러 정보 전달 클래스
    public class UserNotFoundException extends RuntimeException{
      private static final long serialVersionUID = 1L;
    
      public UserNotFoundException() {
      }
    
      public UserNotFoundException(String message) {
          super(message);
      }
    }
    
  • 서비스 클래스
    @Service
    public class UserService {
      @Autowired
      UserRepository userRepository;
    
      public Iterable<UserVO> findAllUserInfo(){
          Iterable<UserVO> allUsers = userRepository.getUserInfoAll();
          return allUsers;
      }
    
      public void dummyInfo(){
          ServletUriComponentsBuilder.fromCurrentRequest()
                  .toUriString();
      }
    
      public void createUser(UserVO userVO){
          System.out.println("userVO::" + userVO.toString());
          userRepository.adduserInfo(userVO);
      }
    
      public Iterable<? extends UserVO> findByLikeUserName(String userName){
          Iterable<UserVO> resultList = userRepository.findByUserNameLike(userName);
          return resultList;
      }
    
      public UserVO findByOneUserName(String userName){
          UserVO userVO = userRepository.findByUserName(userName);
          return userVO;
      }
    }
    

ExceptionHadnler

  • @ControllerAdvice 선언 후 @ExceptionHandler로 예외를 명시할 수 있음
    • @ResponseStatus로 예외 응답 코드를 정의할 수도 있음
@ControllerAdvice
public class ApiExceptionHandler {
    @ExceptionHandler(UserNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ResponseEntity<ApiErrorDetail> handleUserNotFoundException(UserNotFoundException unfe){
        ApiErrorDetail errorDetail = new ApiErrorDetail();
        errorDetail.setTimeStamp(new Date());
        errorDetail.setCode(1002);// 별도의 예외코드 추가
        errorDetail.setMessage(unfe.getMessage());

        return new ResponseEntity(errorDetail, HttpStatus.NOT_FOUND);
    }
}

DB 예외 처리

Transaction

Database 참고

PlatformTransactionManager

  • PlatformTransactionManager를 추상화해서 트랜잭션에 관련된 특정 기술에 대한 종속성을 낮춤 img

Transaction 설정

  • TransactionManagerConfigure를 추가
    @Configuration
    @EnableTransactionManagement
    @PropertySource("application.properties")
    public class MariaDBConnectionConfig implements TransactionManagementConfigurer{
      @Value("${spring.datasource.url}")
      private String dbUrl;
      @Value("${spring.datasource.username}")
      private String dbUsername;
      @Value("${spring.datasource.password}")
      private String dbPassword;
      @Value("${spring.datasource.classname}")
      private String dbClassName;
    
      @Lazy
      @Bean(destroyMethod = "close")
      public DataSource dataSource(){
          final HikariConfig hikariConfig = new HikariConfig();
          hikariConfig.setUsername(dbUsername);
          hikariConfig.setPassword(dbPassword);
    
          hikariConfig.addDataSourceProperty("url", dbUrl);
          hikariConfig.setDataSourceClassName(dbClassName);
          hikariConfig.setLeakDetectionThreshold(2000);
          hikariConfig.setPoolName("jpubDBpool");
    
          final HikariDataSource dataSource = new HikariDataSource(hikariConfig);
          return dataSource;
      }
    
      @Bean
      public DataSourceTransactionManager transactionManager(){
          return new DataSourceTransactionManager(dataSource());
      }
    
      @Override
      public PlatformTransactionManager annotationDrivenTransactionManager() {
          return transactionManager();
      }
    }
    

spring transaction과 Service 레이어

  • Repository Layer에서는 하나의 DB table을 대상으로 데이터 입력 조회 갱신 등의 기능을 가진 메서드들이 필요
  • Service Layer에서는 위의 메서드들을 모아서 한 번의 요청 안에서 여러 테이블을 조회하거나 갱신할 필요가 있음
  • 대부분 트랜잭션에 대한 롤백 설정은 Service Layer에서 처리

@Transacional 어노테이션

  • @Transacional
    • 트랜잭션 제어가 필요한 메서드에 사용
    • 인터페이스나 추상 클래스에는 사용 X
      • 실제 구현 클래스 내에서 사용
    • 롤벡에 관련된 속성값을 정의할 수 있음
      • isolation
      • propagation
      • readOnly
      • rollbackFor
      • no-rollback-for
      • timeout

Spring Boot Test

  • 단위 테스트 작성 시
    • junit, mockito, spring-test를 추가해 사용함
  • spring-boot-test-starter는 테스트에 필요한 의존성들을 spring-starter로 만들어 모아 놓은 모듈
    • 이를 사용하면 테스트 관련 라이브러리들을 별도로 추가하지 않아도 됨

DB 연동 테스트

  • 의존성 추가
    • testCompile("org.springframework.boot:spring-boot-starter-test")
    • testCompile은 테스트 시에만 의존성 추가됨

      DB 연동 테스트 코드

      @RunWith(SpringRunner.class)
      @ContextConfiguration(classes = MyBatisConfig.class, loader = AnnotationConfigContextLoader.class)
      @Rollback
      public class MybatisTestConfig {
      }
      
  • UserRepository 클래스 테스트 클래스
    public class UserDaoTest extends MybatisTestConfig {
      @Autowired
      private UserRepository userRepository;
    
    
      @Test
      public void testList(){
          System.out.println(userRepository.getUserInfoAll());
      }
    
      @Test
      public void createUser(){
          UserVO userVO = new UserVO();
          userVO.setId("jpub115");
          userVO.setUserName("홍길동");
          userVO.setEmail("test4@jpub.com");
          userRepository.adduserInfo(userVO);
          System.out.println(userRepository.getUserInfoAll());
      }
    }
    

    Service 클래스의 단위 테스트

  • 위와 동일하게 test에 Service 클래스를 스캐닝하는 설정 클래스를 로드
    @RunWith(SpringRunner.class)
    @ContextConfiguration(classes = ServiceConfig.class, loader = AnnotationConfigContextLoader.class)
    @Rollback
    public class ServiceTestConfig {
    }
    
  • 테스트 클래스
    public class UserServiceTest extends ServiceTestConfig {
      @Autowired
      UserService userService;
    
      @Test
      public void findUserList(){
          userService.findAllUserInfo();
      }
    
      @Test
      public void findUserNameTest(){
          String uname = "kim1";
          System.out.println(userService.findByOneUserName(uname));
      }
    }
    

통합 테스트

  • 최초의 사용자 액션에 접점이 되는 컨트롤러부터 지금까지 테스트해 온 모든 테스트를 합친 test
  • 매우 복잡함
    • 톰캣 또는 임베디드 톰캣을 실행
    • 다른 모든 계층 클래스들의 메서드들의 정상 실행
    • 통합 테스트와 단위 테스트는 분리해서 관리

      통합 테스트를 위한 gradle 설정

  • 다음은 integrationTest 폴더 하위에 작성한 코드들이 클래스 패스에 등록
    integrationTest {
          java.srcDir "src/integrationTest/java"
          resources.srcDir "src/integrationTest/resources"
    
          compileClasspath += main.output + test.output
          runtimeClasspath += main.output + test.output
      }
    
  • 통합 테스트용 task 추가 및 단위 테스트와의 관계 설정
    task integrationTest(type: Test) {
      testClassesDir = sourceSets.integrationTest.output.classesDir
      classpath = sourceSets.integrationTest.runtimeClasspath
      outputs.upToDateWhen { false }
    }
    check.dependsOn integrationTest
    integrationTest.mustRunAfter test
    


SpringSprng-bootExceptionValidationSpring-test Share Tweet +1