제이유닛

  1. 테스트란?
  • 효율적인 테스트를 위함.(SpringBoot 스타터에 이미 포함되어 있는 JUnit !)
  • 프로그램이 정상적으로 동작하는지 검증하는 과정
  • 버그 방지
  • 의도한대로 동작하는가?
  1. TDD(Test Driven Development)
  • 개발 전에 테스트 코드를 먼저 작성하는 방식
  • 테스트 코드 -> 기능 동작 확인 -> 실제 코드에 적용
  1. JUnit
  • JUnit을 가장 많이 사용한다.
  • 단위(메소드) 테스트를 수행하는 테스트 프레임워크

테스트 케이스 생성

  • src/test/java -> JUnit이 관리하는 영역이다.
  1. 발생 가능 오류
  • @RequiredArgsConstructor
  • private final BoardService service;
  • 이게 가능했던 이유는 Spring이 알아서 bean으로 주입하기 떄문.
  • test 쪽에서 위처럼 쓰면 의존성 주입이 안 된다.
    -> @Autowired필요.(final 위쪽 줄에 추가 하고 final과 매개변수 생성자 지우자)
  1. 테스트 케이스 방법
  • 원래 우리 세 가지 방법으로 했었다.
    1)필드 주입
    2)setter
    3)생성자 주입
  • JUnit을 사용할때는 @Autowired 추가 필요!(이게 있어야 의존성 주입이 가능하다.)

예시 코드

package com.gn.mvc.service;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.assertEquals;

import com.gn.mvc.entity.Board;

import lombok.RequiredArgsConstructor;

@SpringBootTest
class BoardServiceTest {
	
	@Autowired
	private BoardService service;
	
	@Test
	void selectBoardOne_success() {
		// 1. 예상 데이터
		Long id = 4L;
		Board expected = Board.builder().boardTitle("제목1").build();
		// 2. 실제 데이터
		Board real = service.selectBoardOne(id);
		// 3. 비교 및 검증
		assertEquals(expected.getBoardTitle(), real.getBoardTitle());
	}
	
	// 실패 테스트
	// 존재하지 않는 PK기준으로 조회 요청
	@Test
	void selectBoardOne_fail() {
		// 1. 실패 예상 PK 데이터
		Long id = 100000000000000L;
		// 2. 실패 예상 PK를 사용한 데이터 조회
		Board expected = service.selectBoardOne(id);
		// 3. 비교 및 검증
		assertEquals(expected, null);
	}
}

assertEquals

  1. 사용법
  • assertEquals(기대값,실제값);
  • 자료형이 숫자든 문자들 날짜든 가리지 않고 검사한다.
  • 만약 객체 형태를 비교한다면~ toString()을 서로 비교하면 된다.
  • null 여부만 비교하는 경우에는 toString()없이 사용
  1. assertEquals 가져오는 2가지 방법
  • Assertions.assertEquals(기대값, 실제값) (일단 import는 필요할듯?)
  • import static org.junit.jupiter.api.Assertions.assertEquals; (static method 가져오는 거라서 그냥 쓰면 된다.)

File 업로드 하기

  1. 방법
  • 파일은 두 가지 정보가 있다.
    1) 파일 데이터(File Data) -> 실제 파일 자체
    - 사용자가 올리는 파일 데이터는 특정 서버의 폴더에 저장한다.
    2) 파일 메타데이터(File Metadata) -> DB에 저장
    - 파일명, 확장자, 저장된 경로 등의 정보를 DB에 저장한다.

파일 정보 저장

1) application.properties에 게시글 첨부 파일이 저장될 위치 추가
- application.properties 를 사용하면 유동성이 높다.(=유동성이 높은 파일이다. 유지 보수 관리 편하다.)

ffupload.location=C:/upload/

2) MvcApplication.java에 implements WebMvcConfigurer 한다.
- WebMvcConfigurer -> 바깥쪽에 있는 파일을 가져올 때 사용하는 스프링이 가지고 있는 Servlet이다.

3) 파일 정보를 담을 수 있는 AttachDto 생성.
4) service 아래에 AttachService 생성
5) AttachService에 MultipartFile 데이터를 받아서 컴퓨터에 파일을 저장하고 메타 데이터가 담긴 Dto를 리턴하는 메소드 생성

@Service
@RequiredArgsConstructor
public class AttachService {
	
	private final BoardRepository boardRepository;
	private final AttachRepository attachRepository;
	
	@Value("${ffupload.location}")
	private String fileDir;
	
	public AttachDto uploadFile(MultipartFile file) {
		AttachDto dto = new AttachDto();
		try {
			// 1. 정상 파일 여부 확인(없는 파일인지 아닌지)
			if(file == null || file.isEmpty()) {
				throw new Exception("존재하지 않는 파일입니다.");
			}
			// 2. 파일 최대 용량 체크
			// Spring 허용 파일 최대 용량 1MB(1048576byte)
			// byte -> KB -> MB
			long fileSize = file.getSize();
			if(fileSize >= 1048576) {
				throw new Exception("허용 용량을 초과하는 파일입니다.");
			}
			// 3. 파일 최초 이름 읽어오기
			String oriName = file.getOriginalFilename();
			dto.setOri_name(oriName);
			
			// 4. 파일 확장자 자르기
			String fileExt = oriName.substring(oriName.lastIndexOf("."));
//			String fileExt = oriName.substring(oriName.lastIndexOf("."), oriName.length());
			// 5. 파일 명칭 바꾸기
			UUID uuid = UUID.randomUUID();
			// 6. uuid의 8자리마다 반복되는 하이픈 제거
			String uniqueName = uuid.toString().replaceAll("-", "");
			// 7. 새로운 파일명 생성
			String newName = uniqueName + fileExt;
			dto.setNew_name(newName);
			
			// 8. 파일 저장 경로 설정. C:/upload/board/newName
			String downDir = fileDir + "board/" + newName;
			dto.setAttach_path(downDir);
			
			// 9. 파일 껍데기 생성
			File saveFile = new File(downDir);
			// 10. 경로 존재 유무 확인
			if(saveFile.exists() == false) {
				saveFile.mkdirs();
			}
			// 11. 껍데기에 파일 정보 복제(file이 알맹이?)
			file.transferTo(saveFile);
			
		} catch (Exception e) {
			// throw를 만나면 dto가 null로 초기화 된다.
			dto = null;
			e.printStackTrace();
		}
		
		return dto;
	}
}
  1. BoardController에 코드 수정

메타 데이터 저장

  1. 파일 정보를 처리할 수 있는 엔티티 Attach 생성
  2. Board 엔티티에 Attach관련 코드 추가
  3. BoardController에서 전달받은 AttachDto를 List에 담음
  4. dto들이 담긴 list의 크기와 전달 받은 파일의 목록이 담긴 list의 크기가 같다면 다음 작업 수행
  5. AttachRepository 생성
  6. BoardService에 AttachRepository 의존성 주입
  7. AttachDto에 toEntity 메소드 추가
  8. BoardService의 createBoard 메소드 수정

트랜잭션

  1. 트랜잭션
  • insert update delete / 자료 데이터를 변화 시키는 친구가 두개 이상 있을 경우~ 트랜잭션 사용.
  • Spring에서 사용 가능한 어노테이션 : @Transactional
  1. 주의사항!
  • RuntimeException이 발생할 경우에만 롤백됨.
  • 이것을 보완하기 위해 별도의 설정 필요
  • @Transactional(rollbackFor = Exception.class)

파일 조회하기

  1. AttachSpecification 클래스 생성
  2. AttachRepository에 게시글 번호 기준으로 파일 목록 조회할 수 있는 메소드 생성
  3. AttachService에 게시글 번호 전달하여 파일 목록 조회하는 메소드 생성
  4. BoardController에 파일 상세 정보 조회 메소드에 코드 추가
  5. /board/detail.html에 파일 정보 추가

흐름

service selectAttachList
-> board의 repository를 통해 board객체 반환받음
-> specification을 만들어서 criteriaBuilder.equal 사용해서 board 반환하는 메소드 만듦.
-> service에서 위의 specification 메소드를 불러와서 사용
-> attachRepository를 통해 findAll(spec을 매개변수로)


파일 다운로드

  1. ResponseEntity
  • ResponseEntity를 사용하는 이유! 무조건 써야한다!
  • 파일 다운로드는 브라우저가 파일로 인식할 수 있도록 응답 구성해야함
  • 파일은 일반 데이터를 응답해주는 것과 다른 작업이다.
  • 브라우저가 파일이라고 인식할 수 있도록 응답을 구성해야한다.
  • 파일 데이터, 인코딩, 위치, 이름 등을 전부!

작업

  1. detail.html에서 파일명을 클릭했을때 컨트롤러로 다운로드 요청 전달
  2. controller 패키지에 AttachController 생성
  3. 파일 다운로드 로직 가지고 있는 메소드 생성
profile
함께 공부해요!

0개의 댓글