CI/CD - CI(Continuous Integration)

박선규's avatar
May 19, 2024
CI/CD - CI(Continuous Integration)

CI/CD

📌
CI/CD(Continuous Integration/Continuous Delivery 또는 Continuous Deployment)는 소프트웨어 개발 프로세스를 자동화하여 코드 변경 사항을 효율적으로 빌드, 테스트, 배포하는 방법이다.
 

CI(Continuous Integration)

  • 코드 통합: 작업을 마무리하고 여러 사람의 코드들을 공용 코드 베이스로 최신 상태로 유지한다.
  • 자동화된 빌드: 통합된 코드를 자동으로 빌드한다. 이 과정에서 컴파일을 통해 정상적으로 실행 되는지 확인 한다.
  • 자동화된 테스트: 빌드가 완료 된 후, 자동화된 테스트를 실행하여 빌드된 코드가 예상대로 동작하는지 새로운 코드 변경이 기존 기능에 문제를 일으키지 않는지 검증한다.
📌
원래는 이순서이지만 나는 테스트랑 빌드의 순서를 바꿔서 진행한다. 왜냐하면 테스트 후에 빌드시 jar 파일이 정상 동작하는 코드로 이루어 졌다는것을 보장 할 수 있다 즉 문제가 해결되지 않은 코드를 빌드하고 배포하는것을 막을 수 있다.
 

1. 통합 테스트 및 API문서

설정

맨처음 빌드 그레이들로 가서 레스트 덕 추가하기
notion image
 

API문서 만들 때의 레스트 덕이랑 스웨거 차이

레스트덕:프로덕션 코드에 영향이 없다 테스트 코드만 돼있으면 된다.
통합 테스트 케이스를 기반으로 문서를 생성하기 때문이다.
 
 
스웨거:프로덕션 코드가 영향이있는데 프로덕션 코드란 실제 코드가 완성이 돼야된다? 대신 테스트 코드가 필요 없음 스웨거가 API문서 자체는 이쁘게 나오는데 밑 코드가 컨트롤러에 붙어야 한다.
개발자 입장에서는 코드 더러움
notion image
notion image
 

 
notion image

build gradle에 라이브러리 추가하기

notion image
plugins { id 'java' id 'org.springframework.boot' version '3.2.2' id 'io.spring.dependency-management' version '1.0.15.RELEASE' id "org.asciidoctor.jvm.convert" version "3.3.2" } group = 'shop.mtcoding' version = '1.0' java { sourceCompatibility = '21' } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { // 서드파티 implementation group: 'com.auth0', name: 'java-jwt', version: '4.3.0' implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.0' implementation 'org.springframework.boot:spring-boot-starter-aop' implementation group: 'org.qlrm', name: 'qlrm', version: '4.0.1' implementation group: 'org.mindrot', name: 'jbcrypt', version: '0.4' // 기본 implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-mustache' implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.h2database:h2' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' } tasks.named('test') { useJUnitPlatform() } jar { enabled = false } ext { set('snippetsDir', file("build/generated-snippets")) } // src/docs 이하면 *.adoc 파일을 테스트시에 찾아내서 html 파일을 생성해줌 tasks.named('asciidoctor') { inputs.dir snippetsDir dependsOn test } // jar에 api.html에 옮기는 코드 bootJar { dependsOn asciidoctor copy { from "${asciidoctor.outputDir}" into 'src/main/resources/static/docs' // /static/docs로 복사! } }
 

레스트 덕 추가하기

notion image
package shop.mtcoding.blog.controller; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.restdocs.RestDocumentationContextProvider; import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; import org.springframework.restdocs.operation.preprocess.Preprocessors; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.filter.CharacterEncodingFilter; import java.nio.charset.StandardCharsets; @ExtendWith({ SpringExtension.class, RestDocumentationExtension.class }) public class MyRestDoc { protected MockMvc mvc; protected RestDocumentationResultHandler document; @BeforeEach public void setup(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) { this.document = MockMvcRestDocumentation.document("{class-name}/{method-name}", Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), Preprocessors.preprocessResponse(Preprocessors.prettyPrint())); mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) .addFilter(new CharacterEncodingFilter(StandardCharsets.UTF_8.name(), true)) .apply(MockMvcRestDocumentation.documentationConfiguration(restDocumentation)) // .apply(SecurityMockMvcConfigurers.springSecurity()) .alwaysDo(document) .build(); } }
 

각 테스트 코드 밑에 붙여주기

이때 모든 컨트롤러 myRestduck 상속받기
notion image
 

통합 테스트 실습

package com.example.finalproject.domain.user; import com.example.finalproject._core.utils.AppJwtUtil; import com.example.finalproject.domain.photo.Photo; import com.example.finalproject.domain.user.User; import com.example.finalproject.domain.user.UserRequest; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.hamcrest.Matchers.containsString; @AutoConfigureMockMvc @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) public class UserControllerTest { private ObjectMapper om = new ObjectMapper(); @Autowired private MockMvc mvc; private static String jwt; @BeforeAll public static void setUp() { jwt = AppJwtUtil.create( User.builder() .id(3) .myName("변우석") .email("bunwuseok@example.com") .blueChecked(true) .build()); } //회원가입 @Test public void join_test() throws Exception { // given UserRequest.JoinDTO reqDTO = new UserRequest.JoinDTO(); reqDTO.setEmail("p4rk@naver.com"); reqDTO.setMyName("p4rk"); reqDTO.setNickName("cat"); reqDTO.setPassword("1234"); String reqBody = om.writeValueAsString(reqDTO); // when ResultActions actions = mvc.perform( post("/user/join") .content(reqBody) .contentType(MediaType.APPLICATION_JSON) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); // System.out.println("respBody: " + respBody); // then actions.andExpect(jsonPath("$.status").value(200)); actions.andExpect(jsonPath("$.success").value(true)); actions.andExpect(jsonPath("$.response.email").value("p4rk@naver.com")); actions.andExpect(jsonPath("$.response.nickName").value("cat")); actions.andExpect(jsonPath("$.errorMessage").doesNotExist()); } //아이디 중복체크 @Test public void join_username_same_fail_test() throws Exception { // given UserRequest.JoinDTO reqDTO = new UserRequest.JoinDTO(); reqDTO.setEmail("junghein@example.com"); reqDTO.setMyName("dfd"); reqDTO.setNickName("dfdfd"); reqDTO.setPassword("1234"); String reqBody = om.writeValueAsString(reqDTO); // when ResultActions actions = mvc.perform( post("/user/join") .content(reqBody) .contentType(MediaType.APPLICATION_JSON) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); System.out.println("respBody: " + respBody); // then actions.andExpect(jsonPath("$.status").value(400)); actions.andExpect(jsonPath("$.success").value(false)); actions.andExpect(jsonPath("$.errorMessage").value("중복된 이메일이 있습니다.")); } //회원가입 유효성검사 @Test public void join_username_valid_fail_test() throws Exception { // given UserRequest.JoinDTO reqDTO = new UserRequest.JoinDTO(); reqDTO.setEmail("a"); reqDTO.setMyName("dfd"); reqDTO.setNickName("cat"); reqDTO.setPassword("1234"); String reqBody = om.writeValueAsString(reqDTO); // when ResultActions actions = mvc.perform( post("/user/join") .content(reqBody) .contentType(MediaType.APPLICATION_JSON) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); System.out.println("respBody: " + respBody); // then actions.andExpect(jsonPath("$.status").value(400)); actions.andExpect(jsonPath("$.success").value(false)); actions.andExpect(jsonPath("$.errorMessage").value("이메일은 최소 3자 이상 최대 20자 이하여야 합니다. : email")); } //로그인 @Test public void login_success_test() throws Exception { // given UserRequest.LoginDTO reqDTO = new UserRequest.LoginDTO(); reqDTO.setEmail("junghein@example.com"); reqDTO.setPassword("1234"); String reqBody = om.writeValueAsString(reqDTO); // when ResultActions actions = mvc.perform( post("/user/login") .content(reqBody) .contentType(MediaType.APPLICATION_JSON) ); //eye String respBody = actions.andReturn().getResponse().getContentAsString(); String jwt = actions.andReturn().getResponse().getHeader("Authorization"); // then actions.andExpect(status().isOk()); // header 검증 actions.andExpect(result -> result.getResponse().getHeader("Authorization").contains("Bearer " + jwt)); actions.andExpect(jsonPath("$.status").value(200)); actions.andExpect(jsonPath("$.success").value(true)); actions.andExpect(jsonPath("$.response.email").value("junghein@example.com")); actions.andExpect(jsonPath("$.errorMessage").doesNotExist()); } //로그인 실패 테스트 @Test public void login_fail_test() throws Exception { // given UserRequest.LoginDTO reqDTO = new UserRequest.LoginDTO(); reqDTO.setEmail("junghein@example.com"); reqDTO.setPassword("12345"); String reqBody = om.writeValueAsString(reqDTO); // when ResultActions actions = mvc.perform( post("/user/login") .content(reqBody) .contentType(MediaType.APPLICATION_JSON) ); // then actions.andExpect(status().isUnauthorized()); // header 검증 actions.andExpect(jsonPath("$.status").value(401)); actions.andExpect(jsonPath("$.errorMessage").value("사용자 정보를 찾을 수 없습니다.")); actions.andExpect(jsonPath("$.response").isEmpty()); } //로그인 유효성검사 @Test public void login_username_valid_fail_test() throws Exception { // given UserRequest.LoginDTO reqDTO = new UserRequest.LoginDTO(); reqDTO.setEmail(""); reqDTO.setPassword("1234"); String reqBody = om.writeValueAsString(reqDTO); // when ResultActions actions = mvc.perform( post("/user/login") .content(reqBody) .contentType(MediaType.APPLICATION_JSON) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); // then actions.andExpect(jsonPath("$.status").value(400)); actions.andExpect(jsonPath("$.errorMessage").value("이메일이 공백일 수 없습니다 : email")); } //앱 세팅 테스트 (사용자 변경) @Test public void setting_success_test() throws Exception { // given String jwt1 = AppJwtUtil.create(User.builder() .id(2) .blueChecked(false) .build()); // when ResultActions actions = mvc.perform( get("/app/setting") .header("Authorization", "Bearer " + jwt1) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); // System.out.println("respBody:"+respBody); // then actions.andExpect(jsonPath("$.status").value(200)); actions.andExpect(jsonPath("$.success").value(true)); actions.andExpect(jsonPath("$.response.id").value(2)); actions.andExpect(jsonPath("$.response.myName").value("임시완")); actions.andExpect(jsonPath("$.response.email").value("limsiwan@example.com")); actions.andExpect(jsonPath("$.response.nickName").value("limsiwan")); actions.andExpect(jsonPath("$.response.mobile").value("010-9876-5432")); actions.andExpect(jsonPath("$.errorMessage").doesNotExist()); } @Test public void setting_fail_test() throws Exception { // given String jwt1 = AppJwtUtil.create(User.builder() .id(999) .blueChecked(false) .build()); // when ResultActions actions = mvc.perform( get("/app/setting") .header("Authorization", "Bearer " + jwt1) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); // System.out.println("respBody: " + respBody); // then actions.andExpect(jsonPath("$.status").value(401)); actions.andExpect(jsonPath("$.success").value(false)); actions.andExpect(jsonPath("$.errorMessage").value("인증되지 않았습니다.")); } //앱 프로필 테스트 @Test public void profile_success_test() throws Exception { // given String jwt = AppJwtUtil.create(User.builder() .id(2) .blueChecked(false) .build()); // when ResultActions actions = mvc.perform( get("/app/profile") .header("Authorization", "Bearer " + jwt) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); // then actions.andExpect(jsonPath("$.status").value(200)); actions.andExpect(jsonPath("$.success").value(true)); actions.andExpect(jsonPath("$.response.userId").value(2)); actions.andExpect(jsonPath("$.response.myName").value("임시완")); actions.andExpect(jsonPath("$.response.email").value("limsiwan@example.com")); actions.andExpect(jsonPath("$.response.nickName").value("limsiwan")); actions.andExpect(jsonPath("$.response.mobile").value("010-9876-5432")); actions.andExpect(jsonPath("$.response.photoDTO.id").value(5)); actions.andExpect(jsonPath("$.response.photoDTO.name").exists()); actions.andExpect(jsonPath("$.response.photoDTO.photoPath").value("/upload/user/user2.webp")); actions.andExpect(jsonPath("$.errorMessage").doesNotExist()); } @Test public void profile_fail_test() throws Exception { // given String jwt1 = AppJwtUtil.create(User.builder() .id(999) .build()); // when ResultActions actions = mvc.perform( get("/app/profile") .header("Authorization", "Bearer " + jwt1) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); // then actions.andExpect(jsonPath("$.status").value(401)); actions.andExpect(jsonPath("$.success").value(false)); actions.andExpect(jsonPath("$.errorMessage").value("인증되지 않았습니다.")); } // 크리에이터 지원 페이지 @Test public void creator_apply_form_success_test() throws Exception { // given String jwt1 = AppJwtUtil.create(User.builder() .id(1) .blueChecked(false) .photo(Photo.builder() .uuidName("uuid_사용자사진1") .path("/upload/user/user1.webp") .build()) .nickName("junghein") .introMsg("연예인 체형") .build()); // when ResultActions actions = mvc.perform( get("/app/creator-apply-form") .header("Authorization", "Bearer " + jwt1) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(respBody); // then actions.andExpect(jsonPath("$.status").value(200)); actions.andExpect(jsonPath("$.success").value(true)); actions.andExpect(jsonPath("$.response.id").value(1)); actions.andExpect(jsonPath("$.response.name").value("정해인")); actions.andExpect(jsonPath("$.response.instagram").value("holyhaein")); actions.andExpect(jsonPath("$.response.blueChecked").value("false")); actions.andExpect(jsonPath("$.response.status").value("신청 전")); actions.andExpect(jsonPath("$.errorMessage").doesNotExist()); } @Test public void creator_apply_form_fail_test() throws Exception { // given String jwt1 = AppJwtUtil.create(User.builder() .id(999) .blueChecked(false) .build()); // when ResultActions actions = mvc.perform( get("/app/creator-apply-form") .header("Authorization", "Bearer " + jwt1) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); // then actions.andExpect(jsonPath("$.status").value(401)); actions.andExpect(jsonPath("$.success").value(false)); actions.andExpect(jsonPath("$.errorMessage").value("인증되지 않았습니다.")); } //사용자 크리에이터 지원하기 @Test public void creator_apply_success_test() throws Exception { // given String jwt1 = AppJwtUtil.create(User.builder() .id(1) .blueChecked(false) .photo(Photo.builder() .uuidName("uuid_사용자사진1") .path("/upload/user/user1.webp") .build()) .nickName("junghein") .introMsg("연예인 체형") .build()); UserRequest.CreatorApplyDTO reqDTO = new UserRequest.CreatorApplyDTO(); reqDTO.setHeight("182"); reqDTO.setComment("키 182 연예인 체형입니다."); reqDTO.setInstagram("abc@naver.com"); reqDTO.setJob("학생"); reqDTO.setWeight("72"); String reqBody = om.writeValueAsString(reqDTO); // when ResultActions actions = mvc.perform( put("/app/creator-apply") .header("Authorization", "Bearer " + jwt1) .content(reqBody) .contentType(MediaType.APPLICATION_JSON) ); //eye String respBody = actions.andReturn().getResponse().getContentAsString(); // System.out.println(respBody); String respJwt = actions.andReturn().getResponse().getHeader("Authorization"); // then actions.andExpect(status().isOk()); // 상태 코드 검증 if (respJwt != null) { assertTrue(respJwt.contains("Bearer ")); } actions.andExpect(jsonPath("$.status").value(200)); actions.andExpect(jsonPath("$.success").value(true)); actions.andExpect(jsonPath("$.response.id").value(1)); actions.andExpect(jsonPath("$.response.name").value("정해인")); actions.andExpect(jsonPath("$.response.instagram").value("abc@naver.com")); actions.andExpect(jsonPath("$.response.blueChecked").value(false)); actions.andExpect(jsonPath("$.response.status").value("승인 대기")); actions.andExpect(jsonPath("$.errorMessage").doesNotExist()); } @Test public void creator_apply_fail_test() throws Exception { // given UserRequest.CreatorApplyDTO reqDTO = new UserRequest.CreatorApplyDTO(); reqDTO.setHeight(""); reqDTO.setComment("키 182 연예인 체형입니다."); reqDTO.setInstagram("abc@naver.com"); reqDTO.setJob("학생"); reqDTO.setWeight("72"); String reqBody = om.writeValueAsString(reqDTO); // when ResultActions actions = mvc.perform( put("/app/creator-apply") .header("Authorization", "Bearer " + jwt) .content(reqBody) .contentType(MediaType.APPLICATION_JSON) ); //eye String respBody = actions.andReturn().getResponse().getContentAsString(); // System.out.println(respBody); String respJwt = actions.andReturn().getResponse().getHeader("Authorization"); // then if (respJwt != null) { assertTrue(respJwt.contains("Bearer ")); } actions.andExpect(jsonPath("$.status").value(400)); actions.andExpect(jsonPath("$.success").value(false)); actions.andExpect(jsonPath("$.errorMessage").value("키는 공백일 수 없습니다 : height")); } //크리에이터 뷰 페이지 @Test public void creator_view_success_test() throws Exception { // given Integer userId = 3; // when ResultActions actions = mvc.perform( get("/app/creator-view/" + userId) .header("Authorization", "Bearer " + jwt) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(respBody); // then actions.andExpect(jsonPath("$.status").value(200)); actions.andExpect(jsonPath("$.success").value(true)); actions.andExpect(jsonPath("$.response.userDTO.creatorId").value(3)); actions.andExpect(jsonPath("$.response.userDTO.blueChecked").value(true)); actions.andExpect(jsonPath("$.response.userDTO.photoName").exists()); actions.andExpect(jsonPath("$.response.userDTO.photoPath").exists()); actions.andExpect(jsonPath("$.response.userDTO.nickName").value("bun")); actions.andExpect(jsonPath("$.response.userDTO.height").value("180cm")); actions.andExpect(jsonPath("$.response.userDTO.weight").value("75kg")); actions.andExpect(jsonPath("$.response.userDTO.job").value("직장인")); actions.andExpect(jsonPath("$.response.userDTO.introMsg").value("연예인 체형")); actions.andExpect(jsonPath("$.response.codiList[0].codiId").value(1));// Json데이터가 배열이면 몇번지의 데이터인지 기입해줘야 함. actions.andExpect(jsonPath("$.response.codiList[0].codiPhotoId").value(14)); actions.andExpect(jsonPath("$.response.codiList[0].photoName").exists()); actions.andExpect(jsonPath("$.response.codiList[0].photoPath").exists()); actions.andExpect(jsonPath("$.response.codiList[0].codiPhoto").value("CODI")); actions.andExpect(jsonPath("$.errorMessage").doesNotExist()); } @Test public void creator_view_fail_test() throws Exception { // given Integer userId = 999; // when ResultActions actions = mvc.perform( get("/app/creator-view/" + userId) .header("Authorization", "Bearer " + jwt) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); System.out.println("respBody: " + respBody); // then actions.andExpect(jsonPath("$.status").value(401)); actions.andExpect(jsonPath("$.success").value(false)); actions.andExpect(jsonPath("$.errorMessage").value("크리에이터가 아닙니다.")); } //유저 마이페이지 @Test public void user_my_page_success_test() throws Exception { // given // when ResultActions actions = mvc.perform( get("/app/user-my-page") .header("Authorization", "Bearer " + jwt) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(respBody); // then actions.andExpect(jsonPath("$.status").value(200)); actions.andExpect(jsonPath("$.success").value(true)); actions.andExpect(jsonPath("$.response.userId").value(3)); actions.andExpect(jsonPath("$.response.photoName").exists()); actions.andExpect(jsonPath("$.response.photoPath").exists()); actions.andExpect(jsonPath("$.response.nickName").value("bun")); actions.andExpect(jsonPath("$.response.orderCount").value(4)); actions.andExpect(jsonPath("$.errorMessage").doesNotExist()); } @Test public void user_my_page_fail_test() throws Exception { // given String jwt1 = AppJwtUtil.create(User.builder() .id(999) .blueChecked(false) .build()); // when ResultActions actions = mvc.perform( get("/app/user-my-page") .header("Authorization", "Bearer " + jwt1) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); // then actions.andExpect(jsonPath("$.status").value(401)); actions.andExpect(jsonPath("$.success").value(false)); actions.andExpect(jsonPath("$.errorMessage").value("인증되지 않았습니다.")); } @Test public void search_all_multiple_results_success_test() throws Exception { // given // when ResultActions actions = mvc.perform( get("/app/search-all") .header("Authorization", "Bearer " + jwt) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); // then actions.andExpect(status().isOk()); actions.andExpect(jsonPath("$.status").value(200)); actions.andExpect(jsonPath("$.success").value(true)); //첫번째 코디 actions.andExpect(jsonPath("$.response.codiListDTOS").isArray()); actions.andExpect(jsonPath("$.response.codiListDTOS[0].codiId").value(1)); actions.andExpect(jsonPath("$.response.codiListDTOS[0].codiPhotoId").value(14)); actions.andExpect(jsonPath("$.response.codiListDTOS[0].photoName").value("uuid_코디사진1")); actions.andExpect(jsonPath("$.response.codiListDTOS[0].photoPath").value("/upload/codi/user-3-codi1.webp")); // 첫번째 아이템 actions.andExpect(jsonPath("$.response.codiListDTOS").isArray()); actions.andExpect(jsonPath("$.response.itemListDTOS[0].itemId").value(1)); actions.andExpect(jsonPath("$.response.itemListDTOS[0].name").value("SCRAPPED 티셔츠(WHITE)")); actions.andExpect(jsonPath("$.response.itemListDTOS[0].description").value("힙하고 유니크한 반팔로 어느 코디에도 잘 어울립니다.")); actions.andExpect(jsonPath("$.response.itemListDTOS[0].price").value(45000)); actions.andExpect(jsonPath("$.response.itemListDTOS[0].itemPhotoId").value(30)); actions.andExpect(jsonPath("$.response.itemListDTOS[0].itemPhotoName").value("uuid_아이템사진1")); actions.andExpect(jsonPath("$.response.itemListDTOS[0].photoPath").value("/upload/items/item01/mainItemPhoto.jpg")); actions.andExpect(jsonPath("$.errorMessage").doesNotExist()); } @Test public void creator_my_page_success_test() throws Exception { // given // when ResultActions actions = mvc.perform( get("/app/creator-my-page") .header("Authorization", "Bearer " + jwt) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); System.out.println("respBody = " + respBody); // then actions.andExpect(status().isOk()) .andExpect(content().json(respBody)) .andExpect(jsonPath("$.status").value(200)) .andExpect(jsonPath("$.success").value(true)) .andExpect(jsonPath("$.response.userDTO.creatorId").value(3)) .andExpect(jsonPath("$.response.userDTO.blueChecked").value(true)) .andExpect(jsonPath("$.response.userDTO.photoName").exists()) .andExpect(jsonPath("$.response.userDTO.photoPath").exists()) .andExpect(jsonPath("$.response.userDTO.nickName").value("bun")) .andExpect(jsonPath("$.response.userDTO.height").value("180cm")) .andExpect(jsonPath("$.response.userDTO.weight").value("75kg")) .andExpect(jsonPath("$.response.userDTO.job").value("직장인")) .andExpect(jsonPath("$.response.userDTO.introMsg").value("연예인 체형")) .andExpect(jsonPath("$.response.userDTO.orderCount").value(4)) .andExpect(jsonPath("$.response.userDTO.mileage").value(3000)) .andExpect(jsonPath("$.response.codiList[0].codiId").value(1)) .andExpect(jsonPath("$.response.codiList[0].codiPhotoId").value(14)) .andExpect(jsonPath("$.response.codiList[0].photoName").value("uuid_코디사진1")) .andExpect(jsonPath("$.response.codiList[0].photoPath").value("/upload/codi/user-3-codi1.webp")) .andExpect(jsonPath("$.response.codiList[0].codiPhoto").value("CODI")) .andExpect(jsonPath("$.response.itemList[0].itemId").value(5)) .andExpect(jsonPath("$.response.itemList[0].name").value("crop cable sweater")) .andExpect(jsonPath("$.response.itemList[0].description").value("방모 원사임에도 모달이 섞여 기분좋은 찰랑거림이 있는게 매력적입니다.")) .andExpect(jsonPath("$.response.itemList[0].price").value(75000)) .andExpect(jsonPath("$.response.itemList[0].itemPhotoId").value(38)) .andExpect(jsonPath("$.response.itemList[0].itemPhotoName").value("uuid_아이템사진5")) .andExpect(jsonPath("$.response.itemList[0].photoPath").value("/upload/items/item05/mainItemPhoto.jpg")) .andExpect(jsonPath("$.response.itemList[0].itemPhoto").value("ITEM")) .andExpect(jsonPath("$.errorMessage").doesNotExist()); } @Test public void creator_my_page_fail_test() throws Exception { // given String jwt1 = AppJwtUtil.create(User.builder() .id(1) .blueChecked(false) .photo(Photo.builder() .uuidName("uuid_사용자사진1") .path("/upload/user/user1.webp") .build()) .nickName("junghein") .height("175cm") .weight("70kg") .job("직장인") .introMsg("연예인 체형") .mileage(0) .build()); // when ResultActions actions = mvc.perform( get("/app/creator-my-page") .header("Authorization", "Bearer " + jwt1) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); // then actions.andExpect(jsonPath("$.status").value(401)); actions.andExpect(jsonPath("$.success").value(false)); actions.andExpect(jsonPath("$.errorMessage").value("인증 되지 않았습니다.")); } @Test public void user_profile_update_success_test() throws Exception { // given Integer userId = 3; UserRequest.ProfileUpdateDTO reqDTO = new UserRequest.ProfileUpdateDTO(); reqDTO.setMyName("변우식"); reqDTO.setNickName("bun"); reqDTO.setPassword("12345"); UserRequest.ProfileUpdateDTO.PhotoDTO photoDTO = new UserRequest.ProfileUpdateDTO.PhotoDTO(); photoDTO.setName("uuid_사용자사진3"); photoDTO.setBase64("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9"); reqDTO.setPhoto(photoDTO); String reqBody = om.writeValueAsString(reqDTO); // when ResultActions actions = mvc.perform( put("/user/profile/" + userId) .header("Authorization", "Bearer " + jwt) .content(reqBody) .contentType(MediaType.APPLICATION_JSON) ); //eye String respBody = actions.andReturn().getResponse().getContentAsString(); // System.out.println(respBody); String respJwt = actions.andReturn().getResponse().getHeader("Authorization"); // then actions.andExpect(status().isOk()); // 상태 코드 검증 if (respJwt != null) { assertTrue(respJwt.contains("Bearer ")); } actions.andExpect(jsonPath("$.status").value(200)); actions.andExpect(jsonPath("$.success").value(true)); actions.andExpect(jsonPath("$.response.userId").value(3)); actions.andExpect(jsonPath("$.response.email").value("bunwuseok@example.com")); actions.andExpect(jsonPath("$.response.myName").value("변우식")); actions.andExpect(jsonPath("$.response.nickName").value("bun")); actions.andExpect(jsonPath("$.response.photo.photoId").value(6)); actions.andExpect(jsonPath("$.response.photo.photoPath").exists());//uuid같은 경우 값이 계속 바뀌기 때문에 존재하는지만 체크 actions.andExpect(jsonPath("$.errorMessage").doesNotExist()); } @Test public void user_profile_update_fail_test() throws Exception { // given Integer userId = 3; UserRequest.ProfileUpdateDTO reqDTO = new UserRequest.ProfileUpdateDTO(); reqDTO.setMyName("a"); // Invalid myName reqDTO.setNickName("bunㅇㄹㅇㄹㅇㄹㄹㅇㅇㅇ"); reqDTO.setPassword("12345"); UserRequest.ProfileUpdateDTO.PhotoDTO photoDTO = new UserRequest.ProfileUpdateDTO.PhotoDTO(); photoDTO.setName("uuid_사용자사진3"); photoDTO.setBase64("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9"); reqDTO.setPhoto(photoDTO); String reqBody = om.writeValueAsString(reqDTO); System.out.println(reqBody); // when ResultActions actions = mvc.perform( put("/user/profile/" + userId) .header("Authorization", "Bearer " + jwt) .content(reqBody) .contentType(MediaType.APPLICATION_JSON) ); // then actions.andExpect(status().isBadRequest()); // Status code validation actions.andExpect(jsonPath("$.status").value(400)); actions.andExpect(jsonPath("$.success").value(false)); actions.andExpect(jsonPath("$.response").doesNotExist()); actions.andExpect(jsonPath("$.errorMessage").value("실명은 최소 2자 이상 최대 20자 이하여야 합니다. : myName")); } @Test public void user_profile_update_valid_fail_test() throws Exception { // given Integer userId = 3; UserRequest.ProfileUpdateDTO reqDTO = new UserRequest.ProfileUpdateDTO(); reqDTO.setMyName("됐다됐다"); // Invalid myName reqDTO.setNickName("bun"); reqDTO.setPassword("12"); UserRequest.ProfileUpdateDTO.PhotoDTO photoDTO = new UserRequest.ProfileUpdateDTO.PhotoDTO(); photoDTO.setName("uuid_사용자사진3"); photoDTO.setBase64("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9"); reqDTO.setPhoto(photoDTO); String reqBody = om.writeValueAsString(reqDTO); // when ResultActions actions = mvc.perform( put("/user/profile/" + userId) .header("Authorization", "Bearer " + jwt) .content(reqBody) .contentType(MediaType.APPLICATION_JSON) ); // then actions.andExpect(status().isBadRequest()); actions.andExpect(jsonPath("$.status").value(400)); actions.andExpect(jsonPath("$.success").value(false)); actions.andExpect(jsonPath("$.errorMessage").value("비밀번호는 최소 4자 이상 최대 20자 이하여야 합니다. : password")); } //자동 로그인 @Test public void app_auto_login_success_test() throws Exception { // given // when ResultActions actions = mvc.perform( post("/app/auto/login") .header("Authorization", "Bearer " + jwt) .contentType(MediaType.APPLICATION_JSON) ); //eye String respBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(respBody); String respJwt = actions.andReturn().getResponse().getHeader("Authorization"); // then actions.andExpect(status().isOk()); // 상태 코드 검증 if (respJwt != null) { assertTrue(respJwt.contains("Bearer ")); } actions.andExpect(jsonPath("$.status").value(200)); actions.andExpect(jsonPath("$.success").value(true)); actions.andExpect(jsonPath("$.response.id").value(3)); actions.andExpect(jsonPath("$.response.email").value("bunwuseok@example.com")); actions.andExpect(jsonPath("$.response.photo").exists()); actions.andExpect(jsonPath("$.errorMessage").doesNotExist()); } @Test public void app_auto_login_fail_test() throws Exception { // given String invalidJwt = ""; // when ResultActions actions = mvc.perform( post("/app/auto/login") .header("Authorization", "Bearer " + invalidJwt) .contentType(MediaType.APPLICATION_JSON) ); //eye String respBody = actions.andReturn().getResponse().getContentAsString(); System.out.println(respBody); String respJwt = actions.andReturn().getResponse().getHeader("Authorization"); // then // actions.andExpect(status().isOk()); // 상태 코드 검증 // if (respJwt != null) { // assertTrue(respJwt.contains("Bearer ")); // } actions.andExpect(status().isUnauthorized()) // 상태 코드가 401(Unauthorized)인지 검증 .andExpect(content().string(containsString("토큰이 유효하지 않습니다."))); // 응답 본문에 특정 메시지가 } }
 
 
package com.example.finalproject.domain.inquiry; import com.example.finalproject._core.utils.AppJwtUtil; import com.example.finalproject.domain.user.User; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @AutoConfigureMockMvc @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) class InquiryRestControllerTest { private ObjectMapper om = new ObjectMapper(); @Autowired private MockMvc mvc; private static String jwt; @BeforeAll public static void setUp() { jwt = AppJwtUtil.create( User.builder() .id(1) .email("junghein@example.com") .password("1234") .myName("junghein") .build()); } @Test public void inquiryDetail_success_test() throws Exception { // given int inquiryId = 1; // when ResultActions actions = mvc.perform( get("/app/inquiries/" + inquiryId) .header("Authorization", "Bearer " + jwt) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); System.out.println("respBody : " + respBody); // then actions.andExpect(jsonPath("$.status").value(200)); actions.andExpect(jsonPath("$.success").value(true)); actions.andExpect(jsonPath("$.response.inquiryId").value(1)); actions.andExpect(jsonPath("$.response.userId").value(1)); actions.andExpect(jsonPath("$.response.title").value("상품 문의")); actions.andExpect(jsonPath("$.response.content").value("상품이 반팔도 셔츠도 입고 되면 좋겠는데 혹시 안 되나요?.")); actions.andExpect(jsonPath("$.response.commentDTO.brandId").value(1)); actions.andExpect(jsonPath("$.response.commentDTO.brandName").value("SALOMON")); actions.andExpect(jsonPath("$.response.commentDTO.comment").value("출시 예정 제품 있습니다! 1월 27일 11:00부로 상품 구매 가능합니다!")); } @Test public void inquiryDetail_fail_test() throws Exception { // given int inquiryId = 999; // when ResultActions actions = mvc.perform( get("/app/inquiries/" + inquiryId) .header("Authorization", "Bearer " + jwt) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); System.out.println("respBody : " + respBody); // then actions.andExpect(jsonPath("$.status").value(404)); actions.andExpect(jsonPath("$.success").value(false)); actions.andExpect(jsonPath("$.errorMessage").value("페이지를 찾을 수 업습니다.")); } @Test public void inquirySave_success_test() throws Exception { // given InquiryRequest.SaveDTO reqDTO = new InquiryRequest.SaveDTO(); reqDTO.setBrandId(3); reqDTO.setTitle("상품 문의"); reqDTO.setContent("상품 문의 좀 할께요."); String reqBody = om.writeValueAsString(reqDTO); System.out.println("reqBody : " + reqBody); // when ResultActions actions = mvc.perform( post("/app/inquiries") .header("Authorization", "Bearer " + jwt) .contentType(MediaType.APPLICATION_JSON) .content(reqBody) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); System.out.println("respBody : " + respBody); // then actions.andExpect(status().isOk()); actions.andExpect(jsonPath("$.status").value(200)); actions.andExpect(jsonPath("$.success").value(true)); actions.andExpect(jsonPath("$.response.brandId").value(3)); actions.andExpect(jsonPath("$.response.title").value("상품 문의")); actions.andExpect(jsonPath("$.response.content").value("상품 문의 좀 할께요.")); } @Test public void inquirySave_fail_test() throws Exception { // given InquiryRequest.SaveDTO reqDTO = new InquiryRequest.SaveDTO(); reqDTO.setBrandId(999); reqDTO.setTitle("상품 문의"); reqDTO.setContent("상품 문의 좀 할께요."); String reqBody = om.writeValueAsString(reqDTO); System.out.println("reqBody : " + reqBody); // when ResultActions actions = mvc.perform( post("/app/inquiries") .header("Authorization", "Bearer " + jwt) .contentType(MediaType.APPLICATION_JSON) .content(reqBody) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); System.out.println("respBody : " + respBody); // then actions.andExpect(status().isNotFound()); actions.andExpect(jsonPath("$.status").value(404)); actions.andExpect(jsonPath("$.success").value(false)); actions.andExpect(jsonPath("$.response").isEmpty()); } @Test public void inquiryPage_success_test() throws Exception { // when ResultActions actions = mvc.perform( get("/app/inquiries-lists") .header("Authorization", "Bearer " + jwt) ); // eye String respBody = actions.andReturn().getResponse().getContentAsString(); System.out.println("respBody : " + respBody); // then actions.andExpect(jsonPath("$.status").value(200)); actions.andExpect(jsonPath("$.success").value(true)); actions.andExpect(jsonPath("$.response[0].inquiryId").value(1)); actions.andExpect(jsonPath("$.response[0].isReplied").value(true)); actions.andExpect(jsonPath("$.response[0].title").value("상품 문의")); actions.andExpect(jsonPath("$.response[0].content").value("상품이 반팔도 셔츠도 입고 되면 좋겠는데 혹시 안 되나요?.")); actions.andExpect(jsonPath("$.response[0].createdAt").value("2024-01-25 11:30")); } }
 

테스트 성공후

generated-snippets 폴더가 생긴다
generated-snippets 폴더가 생긴다
notion image
아까 추가한 file의 설정 돼있는 곳에 전체 테스트 코드가 생성하면 저렇게 생긴다고 설정하는 곳
아까 추가한 file의 설정 돼있는 곳에 전체 테스트 코드가 생성하면 저렇게 생긴다고 설정하는 곳
 
 
컨트롤러는 하이픈, 메서드는 언더바
컨트롤러는 하이픈, 메서드는 언더바
 
notion image
대문자는 하이픈으로 만들어진다.
대문자는 하이픈으로 만들어진다.
 
curl:os 명령어로 os에서 http통신 요청 할 수 있는 명령어
 
 
인터넷이 되는 모든 컴퓨터들이 localhost가 될 수 있다.
 
httpie:os명령어 $ echo(모니터 화면)
 
 
이 파일을 토대로
이 파일을 토대로
http와 httpie 파일들로 html 파일을 만들어 낼거다.
notion image
이 경로의 html파일을 만든다.
 
만들기
만들기
 
ifndef =c언어 조건문
 
toc = 정렬
 
include = c언어 문법으로 스니펫츠에 저장
변수에 저장할때 { } 사용 원래 user-controllers-test 이렇게 풀네임 인데 줄일 수 있다.
변수에 저장할때 { } 사용 원래 user-controllers-test 이렇게 풀네임 인데 줄일 수 있다.
뒤에 오는 메서드명은 스니펫 안에에 있는 메서드 명을 근거로 적어야됨
notion image
= 블로그 RestAPI metacoding <getinthere@naver.com> 1.0, 05.20, 2024: AsciiDoc article template ifndef::snippets[] :snippets: ./build/generated-snippets endif::[] :user: user-controller-test :board: board-controller-test :reply: reply-controller-test :toc: left :toclevels: 2 :source-highlighter: highlightjs == 유저 === 회원가입 ==== 요청 예시 include::{snippets}/{user}/join_test/http-request.adoc[] ==== 응답 예시 include::{snippets}/{user}/join_test/http-response.adoc[] === 회원가입 유저네임 중복체크 ==== 요청 예시 include::{snippets}/{user}/join_username_same_fail_test/http-request.adoc[] ==== 응답 예시 include::{snippets}/{user}/join_username_same_fail_test/http-response.adoc[] == 게시글 === 게시글 상세보기 ==== 요청 예시 include::{snippets}/{board}/detail_fait_test/http-request.adoc[] ==== 응답 예시 include::{snippets}/{board}/detail_fait_test/http-response.adoc[] === 게시글 한건보기 ==== 요청 예시 include::{snippets}/{board}/find-one_fail_test/http-request.adoc[] ==== 응답 예시 include::{snippets}/{board}/find-one_fail_test/http-response.adoc[] == 댓글 === 댓글 쓰기 ==== 요청 예시 include::{snippets}/{reply}/save_suc_test/http-request.adoc[] ==== 응답 예시 include::{snippets}/{reply}/save_suc_test/http-response.adoc[]
여기 까지 하고 다시 굽기
 
 
구웠는데 파일이이 없음 그 이유는 내가 안만들었기 때문에
구웠는데 파일이이 없음 그 이유는 내가 안만들었기 때문에
내가 직접 static폴더를 만들고 했는데도 안됨 그 이유는
내가 직접 static폴더를 만들고 했는데도 안됨 그 이유는
 
notion image
plugins { id 'java' id 'org.springframework.boot' version '3.2.2' id 'io.spring.dependency-management' version '1.0.15.RELEASE' id "org.asciidoctor.jvm.convert" version "3.3.2" } group = 'shop.mtcoding' version = '1.0' java { sourceCompatibility = '21' } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { // 서드파티 implementation group: 'com.auth0', name: 'java-jwt', version: '4.3.0' implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.0' implementation 'org.springframework.boot:spring-boot-starter-aop' implementation group: 'org.qlrm', name: 'qlrm', version: '4.0.1' implementation group: 'org.mindrot', name: 'jbcrypt', version: '0.4' // 기본 implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-mustache' implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.h2database:h2' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' } tasks.named('test') { useJUnitPlatform() } jar { enabled = false } ext { set('snippetsDir', file("build/generated-snippets")) } // src/docs 이하면 *.adoc 파일을 테스트시에 찾아내서 html 파일을 생성해줌 tasks.named('asciidoctor') { inputs.dir snippetsDir dependsOn test } // jar에 api.html에 옮기는 코드 bootJar { dependsOn asciidoctor copy { from "${asciidoctor.outputDir}" into 'src/main/resources/static/docs' // /static/docs로 복사! } }
위 코드에 맞게 셋팅을 잘해주고 돌리는데
notion image
 

오류

제데로 설정하고 구웠는데도 안되면 일부로
일부로 경로 설정 이상하게 해서 터뜨리고
일부로 경로 설정 이상하게 해서 터뜨리고
이렇게 생김
이렇게 생김
 
 

2. 로컬 환경에서의 단일 jar 파일 빌드

 
여기서 추가로 -x  test를 붙이면 테스트 없이 javr굽는거 가능
여기서 추가로 -x test를 붙이면 테스트 없이 javr굽는거 가능
 

설정(jar파일 하나만 굽는 설정)

notion image
 

jar 파일이 하나만 만들어야 하는 이유

통과하면 libs의 jar파일이 하나만 만들어져야되는데 그 이유는
배포를 자동화 할때 스크립트가 통일화 되니까
 
  1. 로컬 테스트
  1. 로컬 빌드
  1. 로컬 실행 이렇게 3가지가 검증 돼야된다.
  1. version 설정
 

로컬에서 jar 실행하는 법

실행 되는지 확인 이렇게 jar파일이 하나만 있으면 자동화 할 수 있음
실행 되는지 확인 이렇게 jar파일이 하나만 있으면 자동화 할 수 있음
 

버전 설정

notion image
 
 
이렇게 까지 하고 다시 굽고 다시 실행해본다.

3. 익셉션

notion image
 

4. yaml 설정

resources/application-dev.yml

server: servlet: encoding: charset: utf-8 force: true # url rewrite 문제 해결 session: tracking-modes: cookie port: 8080 spring: datasource: driver-class-name: org.h2.Driver url: jdbc:h2:mem:test;MODE=MySQL username: sa password: h2: console: enabled: true sql: init: data-locations: - classpath:db/data.sql jpa: hibernate: ddl-auto: create show-sql: true properties: hibernate: format_sql: true default_batch_fetch_size: 10 defer-datasource-initialization: true open-in-view: false logging: level: org.hibernate.orm.jdbc.bind: TRACE shop.mtcoding.blog: DEBUG
 

resources/application-prod.yml

server: servlet: encoding: charset: utf-8 force: true # url rewrite 문제 해결 session: tracking-modes: cookie port: 5000 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://${RDS_HOST}:3306/{database 이름} username: ${RDS_USERNAME} password: ${RDS_PASSWORD} jpa: hibernate: ddl-auto: none properties: hibernate: default_batch_fetch_size: 10 dialect: org.hibernate.dialect.MySQL8Dialect open-in-view: false logging: level: org.hibernate.orm.jdbc.bind: INFO shop.mtcoding.blog: INFO
📌
github action 테스트는 dev모드로 실제 배포시에는 prod 모드로 진행한다.
 

resources/application.yml

spring: profiles: active: - dev
 

5. Git hub Action 설정

notion image
 
깃 헙에서 action - publish java package with gradle로 설정
 
notion image
 

.github/workflows/deply.yml

name:{워크 플로우 이름} on: push: branches: - master # https://github.com/actions/setup-java # actions/setup-java@v2는 사용자 정의 배포를 지원하고 Zulu OpenJDK, Eclipse Temurin 및 Adopt OpenJDK를 기본적으로 지원합니다. v1은 Zulu OpenJDK만 지원합니다. jobs: build: // 우분투 환경에서 빌드 runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 //Java Development Kit(JDK) 21을 설정합니다. temurin 배포판을 사용합니다. - name: Set up JDK 21 uses: actions/setup-java@v4 with: java-version: 21 distribution: 'temurin' // 권한 설정 - name: Permission run: chmod +x ./gradlew // 빌드 - name: Build with Gradle run: ./gradlew clean build
📌
워크플로우:일련의 작업들을 자동으로 실행하기 위한 절차 또는 과정
 
notion image
 
이 상태로 git hub 에 푸쉬하면 action 에서 확인할 수 있다. 초록 버튼이 뜬다면 CI 가 완료된거다.
Share article

p4rksk