[TIL] Day36 - TDD / lombok

JIONY·2022년 10월 9일
0

TIL - Web BE - Spring Boot

목록 보기
12/20
post-thumbnail

오 롬복 대박 신기함 자동생성을 자동생성해주는ㅋㅋㅋ TDD는 맨 뒷 단계인 QA-배포만 경험해보다가 기능 단위로 테스트해보면서 개발하는 과정을 작게나마 적용해보니 필요성이 더 와닿았달까.. 내가 만들면 내가 만든대로 테스트를 하게 되는 경향이 있는데, 테스트 코드를 통한 사전 테스트가 확신을 높여주는 도구로 사용되기 좋을 것 같음


TDD

  • Test Driven Development(테스트 주도 개발)
  • 설계 이후 코드 개발 및 테스트케이스를 작성하는 기존의 개발 프로세스와 다르게 테스트케이스를 작성한 후에 실제 코드를 개발하여 리펙토링하는 절차

소프트웨어 생애주기

  • life cycle, v cycle이라고도 함
  • 요청을 받고 세부 기능 설계 > 세부단위로 기능 구현 후 통합 > 원하는 환경에서 돌아가는지, 몇 명까지 수용 가능한지 등 성능 테스트(JUnit으로 안됨, 가상환경이 필요한 운영체제별 테스트는 안되고 부하 테스트는 가능) > 고객에게 인수(인수 전에 알파, 클로즈베타, 오픈베타 / 배포(최종 인수))


테스팅 도구

  • 스프링 부트는 도구를 묶음으로 불러옴
    • 핵심 테스팅 도구: JUnit, spring-test
  • 테스팅에 필요한 의존성: spring-boot-starter-test
    • scope가 test : test에서만 사용 가능한 의존성이라는 의미

JUnit

  • 단위 테스트 도구
    • 스프링 뿐만 아니라 자바 전체에서 사용 가능

사용법

src/test/java > 패키지 > test 클래스 생성되어 있음

  • 연관 있는 테스트끼리 같은 클래스에 테스팅 코드 작성(필요 시 클래스 추가)

  • @SpringBootTest

    • 프로젝트 내의 등록된 Spring 도구를 사용할 수 있음
  • @Test

    • 테스트용 메소드임을 표시
    • JUnit에서 만든 것(import 확인)
package com.jw.spring12;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

//이 표시를 하면 프로젝트 내의 등록된 Spring 도구를 사용할 수 있음
@SpringBootTest
class Spring12testApplicationTests {

	@Test //이 표시가 붙은 메소드는 테스트용 메소드
	void contextLoads() {
		System.out.println("Test code");
	}

}

실행

  • Run as JUnit Test로 실행 시, 결과가 console에 출력됨
  • 실행 시간, 성공 수 등 확인 가능한 JUnit 윈도우 생김
  • 기록을 남기는 테스트가 가능해짐

테스트 - 기본

주의사항

  • 클래스명을 Test라고 지으면 Test 어노테이션 때문에 에러 발생함

  • 메소드 생성 시 주의사항

    • 반환형 void, 매개변수 없음
    • 이렇게 안하면 main을 만들어야 하는데 그럼 시간 성능 측정 불가
  • 가설이 있어야 함

    • 1.1 + 2.2 = 3.3?

테스트 코드

@SpringBootTest
public class Test01 {

	@Test
	public void basic01() {
		//목표: 1.1 + 2.2는 3.3인가?
		double a = 1.1;
		double b = 2.2;
		double c = a + b;
		double d = 3.3;
		System.out.println(c==d); //false 		
	}
  • 가설에 대한 결과는 false인데 테스트 결과는 성공으로 나옴
    • 검증 구문(단정문)이 추가로 필요함

단정문(Assertion)

  • 상황에 따라 assertEquals(), assertNotEquals()사용
  • 테스트에서만 가능
  • 가설 설정: 1.1 + 2.2는 3.3일 것이다
  • 실행결과도 false로 나옴
    //단정문(Assertion)
    //-> 목표: 1.1 + 2.2는 3.3일 것이다
    assertEquals(c, d);
    
    //목표가 부정문이라면 assertNotEquals()사용

테스트 - 스프링 도구 사용

TDD를 적용해 포켓몬스터 등록 기능을 구현하는 과정을 정리하고자 함. 컨트롤러와 DTO, DAO 등을 먼저 구현하고 브라우저에서 직접 기능 동작 여부를 확인했던 것과 달리, 단위 테스트와 모듈 테스트 등을 거치면서 필요한 내용들을 구현할 예정임

[진행 순서]
1. 포켓몬스터 데이터 등록을 위한 insert 쿼리 작성
2. 해당 쿼리를 통해 테이블에 데이터가 등록되는지 단위 테스트 진행
3. DAO 생성 후 주입 테스트
4. DAO, DaoImpl에 insert 기능 모듈화
5. Dao.insert()를 통해 테이블에 데이터가 등록되는지 모듈 테스트 진행
6. 컨트롤러 생성
7. 매핑 테스트


스프링 도구 주입

기본적으로는 클래스와 DAO 만들어서 주입해야 불러올 수 있는 도구를 바로 사용할 수 있음. 스프링에서 제공하는 도구는 바로 주입할 수 있음

  • ex. JdbcTemplate
  • 테스트는 서버가 켜져 있지 않아도 됨

JUnit annotation

테스트를 위한 사전/사후 작업 등이 필요할 때, 하나의 메소드에서 여러 작업을 하지 않고 분리

용도별 어노테이션

  • @BeforeEach
    • 테스트 실행 전 단계에서 실행할 준비 메소드(테스트 할 때마다 실행)
    • BeforeAll: static 메소드로 만들어야 함
  • @Test: 테스트 코드
  • @AfterEach: 정리 작업
  • 실제 실행은 @Test만 되고 before/after는 테스트 앞뒤에 알아서 붙음(실행 결과 1개로 나옴)
@SpringBootTest
public class Test04 {
	
	@BeforeEach //준비 작업
	public void before() {
		System.out.println("데이터 추가");
	}
	
	@Test //테스트
	public void test() {
		System.out.println("조회 테스트");
	}
	
	@AfterEach //테스트 후 정리 작업
	public void after() {
		System.out.println("데이터 삭제");
	}
}

단위 테스트

  • INSERT 쿼리를 먼저 테스트하고 성공 시, DAO를 주입해 insert 기능 테스트

  • src/main에 있는 DAO를 src/test에 불러올 수 있음

    • testing 코드 제외한 나머지는 main에 작성
  • jdbctemplate 주입 테스트 → 성공(pocketmonster 테이블에 (56,팬텀,환영) 데이터가 등록된 것 확인) → DaoImpl에 대한 확신을 가질 수 있음

@SpringBootTest
public class Test02 {

	@Autowired
	private JdbcTemplate jdbcTemplate;
	
	@Test
	public void basic02() {
		int no = 56;
		String name = "팬텀";
		String type = "환영";
		
		String sql = "insert into pocket_monster"
							+ "(no, name, type) values(?, ?, ?)";
		Object[] param = {no, name, type};
		jdbcTemplate.update(sql, param);
	}
}

모듈 테스트

  • 새 클래스에 DAO 주입 테스트(test01())
    • 성공 시, 데이터 등록 기능에 대한 DAO, DaoImpl 코드 작성
  • Dao.insert() 테스트(test02())
@SpringBootTest
public class Test03 {
	
	@Autowired
	private PocketMonsterDao pocketMonsterDao;
	
	@Test
	public void test01() {
		//System.out.println(pocketMonsterDao);
		assertNotNull(pocketMonsterDao);
	}
	
	@Test
	public void test02() {
		pocketMonsterDao.insert(60, "발챙이", "물");
	}
	
}

컨트롤러 테스트

테스트는 서버가 꺼져 있어도 가능하다고 위에서 언급함. 컨트롤러는 요청을 받아야 하는데 서버가 꺼져 있다면, 임시로 켜서 테스트하고 다시 꺼지게 하면 됨. 가짜 요청 생성 도구를 만들어서 테스트하는 방법을 사용함

MockMvc

  • 가짜 요청 생성 도구
  • 실제로 네트워크에 연결하지 않고도 API 테스트가 가능하도록 모킹된 객체
  • MockMvcBuiler를 사용해서 파라미터를 반드시 셋팅해주어야 하는데, webAppContextSetup 혹은 standaloneSetup 중 하나만 설정하면 됨
  • WebApplicationContext
    • 가짜 테스트를 수행할 수 있도록 환경을 구성하는 도구
    • 여기서 환경을 만들어서 도구에 넣어줘야 그 도구로 계속 컨트롤러를 테스트할 수 있음
    • 실제 Spring MVC 구성을 로드하여 보다 완벽한 통합 테스트를 수행
  • standaloneSetup
    • 단위 테스트에 약간 더 가까움
    • 한 번에 하나의 컨트롤러를 테스트하며, 스프링 구성을 로드하는 것도 포함되지 않음
      • ex. 컨트롤러에서 Dao 불러왔다면 그 Dao는 쓸 수 없음
      • ex. ViewResolver 등 연관된 기능들을 포함한 테스트를 구현하기 어려움
    • 스프링 MVC 구성을 검증하기 위해 추가 webAppContextSetup 테스트가 필요함
private MockMvc mockMvc;
	
@Autowired
private WebApplicationContext ctx;
	
@BeforeEach
public void before() {
	mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();

}

명령

자바는 객체를 통해서 메소드를 불러야 하는데, 테스트 한정 스테틱 메소드를 통해서 불러올 수 있음

  • .perform() : 요청을 수행하고 추가 조치를 수행할 수 있는 ResultActions 데이터 반환
  • .andDo() : 수행할 일반적인 행동을 설정
  • .andExpect() : 성공 상황을 설정(단정)
  • .andReturn() : 결과에 대한 정보들을 MvcResult 형태로 반환
@Test
	public void test() throws Exception {
	//assertNotNull(ctx);
		
	mockMvc.perform(get("/pocketmon/insert"))
			.andExpect(status().is(200)) //네트워크 상태 200: 정상
			.andDo(print())
			.andReturn();
}

자동 설정

  • @AutoConfigureMockMvc : MockMvc에 대한 자동 설정
    - ctx Autowired 불필요
    - MockMvc 변수를 autowired
    @SpringBootTest
    @AutoConfigureMockMvc //MockMvc에 대한 자동 설정
    public class Test05 {
    
    @Autowired
    private MockMvc mockMvc;
    }

<br>

### POST 매핑 테스트

```java
@Test
public void pocketMonsterInsertTest() throws Exception {
	mockMvc.perform(post("/pocketmon/insert"))
			.andExpect(status().is3xxRedirection())
			.andDo(print())
			.andReturn();
}

이대로 실행하면 전달하는 데이터가 없기 때문에 실패함. 요청 명령은 perform 뿐이므로 여기에 데이터를 첨부해야 함(post 주소 + param)


파라미터 자료형

포켓몬스터 번호(no)의 자료형은 int인데, String으로 첨부해야 함.

http는 원래 파라미터에 String만 허용

  • int형 데이터를 첨부할 수 없는 이유는 패킷 크기와 관련이 있음. 패킷 크기는 1byte인데, 이를 넘으면 반드시 분해와 조립 과정이 생김. (int: 4byte) 자바는 유일하게 문자열이 패킷 단위 크기에 맞음
  • 한글은 쪼개서 보내지게 되는데, 인코딩 방식을 UTF-8로 사용 중이므로 조립과 분해에 대해 따로 신경쓰지 않아도 됨(* UTF-8: 한글을 초/중/종성으로 나눔)
@Test
public void pocketMonsterInsertTest() throws Exception {
	mockMvc.perform(
			post("/pocketmon/insert")
				.param("no", "77")
				.param("name", "디그다")
				.param("type", "풀")
			)
			.andExpect(status().is3xxRedirection())
			.andDo(print())
			.andReturn();
}



Lombok

  • 클래스 자동화 라이브러리
    • 반복적으로 사용하는 코드들을 최소화시켜 가독성과 효율성을 올려줌
    • ex. setter, getter,..등이 컴파일 과정에서 생성되도록 해줌
  • 종속성이 강한 기술
    • 한 번 설치하면 모든 프로젝트에서 다 쓸 수 있음

의존성 추가

방법1. Spring Boot Add Starters에서 추가

  • pom.xml을 확인해보면 방법2와 달리 버전이 없음. 스프링에서 관리하는 도구이므로 버전이 불필요

방법2. Maven Central Repository에서 추가


STS에 설치

프로젝트에만 추가하면 import나 annotation 등은 사용할 수 있지만 실제로 에디터에서 인지하지 못하기 때문에 오류가 발생함

  • maven으로 다운로드한 lombok jar파일의 위치를 찾고 terminal(또는 cmd)에서 실행 후 install

어노테이션

Lombok은 멤버 필드만 존재한다면 나머지 구성 요소들을 Annotation으로 만들도록 지원해줌

@Getter

  • 해당하는 영역의 필드에 Getter 메소드 생성
  • 필드, 클래스에 사용

@Setter

  • 해당하는 영역의 필드에 Setter 메소드 생성
  • 필드, 클래스에 사용

@NoArgsConstructor

  • 기본 생성자 생성
  • 클래스에 사용

@AllArgsConstructor

  • 모든 필드를 매개변수로 가지는 생성자 생성
  • 클래스에 사용
  • @AllArgsConstructor를 추가하면 기본 생성자가 사라지므로 기본 생성자가 필요하다면 @NoArgsConstructor도 같이 추가해야 함

@ToString

  • 객체를 출력했을 때 나오는 toString() 메소드를 필드 값 출력 형태로 재정의
  • exclude 옵션으로 원하지 않는 항목은 출력되지 않도록 설정할 수 있음
    @ToString(exclude = “score”)

@Data

  • @Getter, @Setter, @ToString 등은 거의 모든 클래스에 존재하므로 이를 압축해 @Data라고 부를 수 있음
@Data
@NoArgsConstructor
public class StudentDto {
    private String name;
    private int score;
}

@Builder

  • 객체 생성 시, 빌더 패턴을 사용할 수 있도록 빌더 클래스를 지원
    StudentDto studentDto = StudentDto.builder().build();
    • 스프링은 빈 등록을 해야 관리해줌 → new 키워드를 잘 안씀
      • 이런 특징에 맞게 new 없이 객체 생성한 것처럼 해줌
    • 특히 RowMapper 생성을 효율적으로 할 수 있음
  • 데이터를 설정하면서 객체를 생성하고 싶은 경우 builder가 지원하는, 변수명과 동일한 메소드를 활용
    Student a = Student.builder().name("피카츄").score(50).build();
    • 한 줄 형태로 작성될 수 있어 코드가 더 간결해지는 효과가 발생함



[참고]



0개의 댓글