게시글
블로그
전체 122025. 06. 22.
0
인프런 워밍업 클럽 4기 BE 스터디 Day 18 미션
Practical Testing: 실용적인 테스트 가이드 🎯 Day 18 미션 ①@Mock, @MockBean, @Spy, @SpyBean, @InjectMocks의 차이를 정리하기@Mock가짜 객체 생성@MockBean가짜 객체 등록@Spy실제 객체를 한 번만 감싸서 가짜 객체로 만듦@SpyBean실제 빈을 감싸서 등록@InjectMocks가짜 객체를 주입 받는 객체 생성 🎯 Day 18 미션 ②내용을 살펴보고, 각 항목을 @BeforeEach, given절, when절에 배치한다면 어떻게 배치하고 싶으신가요?@BeforeEach에 올라간 내용은 공통 항목으로 합칠 수 있음ex) 1-1과 2-1을 하나로 합쳐서 @BeforeEach에 배치@BeforeEach void setUp() { 사용자 생성에 필요한 내용 준비 사용자1 생성 사용자1의 게시물 생성에 필요한 내용 준비 사용자1의 게시물 생성 } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given 1-5. 댓글 생성에 필요한 내용 준비 // when 1-6. 댓글 생성 // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given 2-5. 댓글 생성에 필요한 내용 준비 2-6. 댓글 생성 // when 2-7. 댓글 수정 // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given 3-3. 사용자2 생성에 필요한 내용 준비 3-4. 사용자2 생성 3-7. 사용자1의 댓글 생성에 필요한 내용 준비 3-8. 사용자1의 댓글 생성 // when 3-9. 사용자2가 사용자1의 댓글 수정 시도 // then 검증 }
백엔드
2025. 06. 22.
1
인프런 워밍업 클럽 4기 BE 스터디 4주차
💻 강의Practical Testing: 실용적인 테스트 가이드 📚 학습한 문단에 한 주제로만 테스트 하기복잡하고, 여러 조건을 나열한 논리 구조는 지양하기목적에 따라 하나의 케이스로만 구성하기private 메서드 테스트private 메서드는 직접 테스트 할 필요가 없음클라이언트 입장에서는 외부로 노출되지 않은 내부 기능까지 알아야 할 필요가 없음private 기능들은 외부로 공개하고 싶지 않다는 의미로도 해석 됨public 메서드를 검증하다 보면 그 내부에서 호출되는 private 메서드도 간접적으로 검증 됨 🎯 미션Day 16 미션Day 18 미션회고테스트 코드를 몇 번 작성해 보았지만, 매번 작성하는 것이 어려워 좀 더 고생을 하더라도 항상 Excel이나 Notion에 시나리오를 정리해가며 체크리스트 형태로 테스트를 진행하였다 (테스트 시나리오)하지만 이번 주 미션을 수행하며 계층별 테스트 방식을 정리한 덕분에 이전보다 계층에 따라 테스트 코드를 어떻게 작성해야 하는지 더 잘 이해할 수 있게 되었다이전 미션들도 의미있었지만, Day 16 미션과 Day 18 미션은 특히 재미있었고, 많은 도움이 됐던 미션이었다
백엔드
2025. 06. 17.
0
인프런 워밍업 클럽 4기 BE 스터디 Day 16 미션
Practical Testing: 실용적인 테스트 가이드 🎯 Day 16 미션레이어별로 어떤 특징이 있고, 어떻게 테스트하면 좋을지 자기만의 언어로 정리하기1⃣ Persistence Layer✅ 특징Repository데이터의 CRUD를 담당 🧪 테스트@DataJpaTest 보다는 @SpringBootTest 권장@ActiveProfiles를 사용해 애플리케이션 환경과 테스트 환경 분리 @Autowired 를 사용해 Repository 주입하기given : 생성자를 통해 객체 생성 후 저장하기when : 조회하기then : Size 체크, 필드 검증 2⃣ Business Layer✅ 특징ServicePersistence Layer와 상호작용하여 비즈니스 로직을 구현트랜잭션 관리 🧪 테스트@Autowired 를 사용해 Repository 주입하기@Autowired 를 사용해 Service 주입하기@Transactional 사용given에서 필요한 정보와 필요하지 않은 정보를 구분하기 위해 도우미 메서드 만들기예를 들어, 객체 생성 분리when : 테스트할 Service 메서드 호출then : ResponseDTO 검증 3⃣ Presentation Layer✅ 특징Controller요청 수신 및 응답 반환요청에 대한 최소한의 검증을 수행🧪 테스트@WebMvcTest 에 테스트할 컨트롤러 명시하기@Autowired 를 사용해 MockMvc 주입하기@MockBean 을 사용해 Service를 가짜로 주입하기@EnableJpaAuditing 분리하기given : RequestDTO 만들기when 및 then : 테스트할 엔드포인트, Method, Content-type을 지정해 응답 검증@EnableJpaAuditing @Configuration public class JpaAuditingConfig { }
백엔드
2025. 06. 15.
1
인프런 워밍업 클럽 4기 BE 스터디 3주차
💻 강의Practical Testing: 실용적인 테스트 가이드 📚 학습통합 테스트여러 모듈이 협력하는 기능을 통합적으로 검증단위 테스트만으로는 기능 전체의 신뢰성을 보장할 수 없음ORMObject-Relational MappingJPAJava 진영의 ORM 기술 표준인터페이스여러 구현체가 있지만, 보통 Hibernate를 많이 사용자동으로 CRUD SQL을 생성하고 실행, 여러 부가 기능들을 제공ORM이 생성해주는 쿼리를 사용하고 있기 때문에 어떤 식으로 쿼리가 생성되고 실행되는지 명확한 이해 필요Spring 진영에서는 JPA를 한번 더 추상화한 Spring Data JPA 제공보통 QueryDSL과 조합하여 많이 사용타입 체크동적 쿼리회고
백엔드
2025. 06. 08.
1
인프런 워밍업 클럽 4기 BE 스터디 2주차
💻 강의Readable Code: 읽기 좋은 코드를 작성하는 사고법 📚 학습주석주석이 많다는 것은 비즈니스의 요구사항을 코드에 잘 못 녹였다는 것코드를 설명하는 주석은 코드가 아닌 주석에 의존한 것주석에 의존하여 코드를 작성하면 적절하지 않은 추상화 레벨을 갖게 돼 낮은 품질의 코드가 만들어질 수 있다언제 주석을 사용해야 할까?후대에 전해야 할 의사 결정의 히스토리를 코드로 표현할 수 없을 때 상세한 설명이 필요하기 때문에 이때 주석이 필요하다 주석 작성주석을 작성할 때, 자주 변하는 정보는 지양한다코드가 변경되면 주석도 같이 업데이트 해야 하기 때문에 주석이 없는 코드보다 부정확한 주석이 달린 코드가 더 치명적이다좋은 주석최대한 코드에 녹여내고, 코드로 표현 못하는 정보를 전달해야 할 때 주석을 사용한다예외 검증 테스트 코드assertThatThrownByisInstanceOfhasMessageassertThatThrownBy(() -> cafeKiosk.add(americano, 0)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("음료는 1잔 이상 주문하실 수 있습니다.");회고지금은 컨트롤러에만 주석을 남기거나 학습이 중점인 개인 프로젝트에만 주석을 남기는 편이다하지만 예전에 팀 프로젝트를 할 때는 코드마다 주석을 남겼고, 클린 코드에 관심을 가지게 되면서 코드를 최대한 간결하게 작성하려고 하는데, 주석을 남기는 게 맞는지 의문이 생겨 부트캠프 튜터님들과 알고 지내는 주니어 개발자 분들에게 질문을 드린 적이 있다튜터님들이 생각하시는 클린 코드는 학습 내용처럼 주석 없이 최대한 코드에 녹여내는 것이 클린 코드라고 하셨고, 주니어 개발자 사이에서는 의견이 각각 달랐다아무래도 주위에 미들이나 시니어 개발자 보다는 같은 주니어 개발자가 대부분이기 때문에 의문점이 생기면 학습을 통해 배우거나 나보다 경험이 많은 개발자 선배들에게 물어보는 것이 최고인 것 같다튜터님들께 질문을 드려 피드백을 받았다고 해도 주변 주니어 개발자 분들과 마주칠 일이 많다 보니 주석에 대한 혼란이 조금은 남아있었는데 학습을 통해 주석에 대한 의문점이 완전히 풀렸다 상황에 따라 다르겠지만, 내가 배운 내용에 대해 자신감을 가져야겠다아는 것이 힘이다! 🎯 미션리팩토링 한 단계마다, 그 이유를 설명할 수 있어야 한다회고리팩토링 전 코드를 처음 봤을 때, 되게 복잡해 보이고 리팩토링이 꼭 필요한 코드라고 생각했다내가 처음부터 작성한 코드가 아닌, 다른 사람의 코드를 내가 이해하고 리팩토링을 해야 된다는 것에서 어떻게 시작해야 할지 망설여지고 어려움을 느꼈다그리고 복잡한 코드를 보며 협업에서 클린 코드는 꼭 필요한 부분이라고 생각하였다일단은 어떻게 리팩토링을 해야 할지 도전하는 마음으로 시작하였지만, 어떤 흐름대로 구성해야 할지 감이 잘 잡히지 않았다기능 구현을 하는 것보다 클린 코드로 리팩토링 하는 것이 더 어려운 것 같다클린 코드를 잘하기 위해서는 많이 해보는 수밖에 없는 것 같다
백엔드
2025. 06. 01.
1
인프런 워밍업 클럽 4기 BE 스터디 1주차
💻 강의Readable Code: 읽기 좋은 코드를 작성하는 사고법 📚 학습Early returnEarly return으로 else 사용 지양하기표현하고자 하는 것이 명확하다면 else 없이 바로 반환하는 방향으로 리팩토링 진행하기부정어부정 연산자는 가독성을 낮춤부정어구를 쓰지 않아도 되는 상황인지 확인하기부정어구를 사용할 수 밖에 없는 상황이라면 부정어구로 메서드명 구성하기doesNotisNot 회고항상 코딩을 하면서 클린 코드를 추구하기 때문에 if 문을 사용할 때, 가독성을 높이기 위해 else 사용을 지양해왔다1주차 학습을 하면서 'Early return' 용어를 처음 접했고, 용어를 모른 채 코드의 가독성을 높이기 위해 else 사용을 지양해 온 것에 대해 좋은 방향으로 나아가고 있다는 것을 깨달았다부정 연산자가 코드의 가독성을 낮출 수 있다는 것은 인지하지 못했다지금이라도 부정어를 대하는 자세에 대해 배우게 돼 다행이다앞으로는 부정어구를 쓰지 않아도 되는 상황인지 확인하고, 최대한 부정 연산자의 사용을 자제해야겠다 🎯 미션Day 2 미션Day 4 미션회고미션을 진행하면서 추상과 구체의 개념에 대해 다시 배우게 됐고, 1주차에 배운 내용을 기반으로 Day 4 미션을 통해 막상 리팩토링을 해보니 어려웠다처음에는 어떤 부분부터 어떻게 리팩토링을 해야 할지 막막했지만, 처음에 배운 추상과 구체가 떠올라 추상화부터 하였다추상적으로 하는 것만으로도 코드가 깔끔해 보였고, 추상화의 중요성에 대해 깨닫게 되었다
백엔드
2025. 05. 29.
0
인프런 워밍업 클럽 4기 BE 스터디 Day 4 미션
Readable Code: 읽기 좋은 코드를 작성하는 사고법 🎯 Day 4 미션 ①[섹션 3. 논리, 사고의 흐름]에서 이야기하는 내용을 중심으로 읽기 좋은 코드로 리팩토링 하기추상화 하기Early return 적용부정어구를 사용해야 하는지 고민하고, 사용해야 한다면 부정어구로 메서드명 지정public boolean validateOrder(Order order) { if (hasNoOrderItem(order)) { log.info("주문 항목이 없습니다."); return false; } if (hasTotalPrice(order)) { if (order.hasCustomerInfo()) { return true; } return false; } if (hasNoTotalPrice(order)) { log.info("올바르지 않은 총 가격입니다."); return false; } return true; } private boolean hasNoTotalPrice(Order order) { return order.getTotalPrice() 0; } private boolean hasNoOrderItem(Order order) { return order.getItems().size() == 0; } 🎯 Day 4 미션 ②SOLID에 대하여 자신만의 언어로 정리하기SRP책임을 분리하자 OCP추상화와 다형성을 활용해 기존 코드 변경 없이, 기능을 확장할 수 있어야 한다 LSP자식 클래스는 부모 클래스의 행위를 변경하지 않는다부모 클래스의 인스턴스를 자식 클래스의 인스턴스로 치환할 수 있어야 한다 ISP기능 단위로 인터페이스를 나누자 DIP추상화(인터페이스)에 의존하자
백엔드
2025. 05. 28.
0
인프런 워밍업 클럽 4기 BE 스터디 Day 2 미션
Readable Code: 읽기 좋은 코드를 작성하는 사고법 🎯 Day 2 미션 : 추상과 구체의 예시추상밥을 먹었다 구체젓가락으로 반찬을 집어 밥과 함께 입에 넣는다입 안으로 들어간 음식물은 저작 운동을 통해 잘게 부서진다식도를 통해 위로 내려가고, 위는 음식물을 분해한다
백엔드
2025. 03. 24.
0
인프런 워밍업 클럽 3기 BE 스터디 4주차
💻 강의입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기 📚 학습@Transactional(readOnly = true)읽기 전용 트랜잭션으로 설정하면 데이터 변경이 일어나지 않기 때문에 스냅샷을 저장하는 동작을 생략해 좀 더 성능을 개선할 수 있다readOnly 참고Service 테스트@ExtendWith : 테스트 확장을 지원하며 JUniit5와 Mockito를 연동해 테스트를 진행할 경우에는 MockitoExtension.class를 사용@InjectMocks : Mockito에서 테스트 대상이 되는 클래스에 인스턴스를 주입하기 위해 사용@Mock : Mockito에서 Mock 객체를 생성할 때 사용하며 실제로 메서드는 갖고 있지만 내부 구현이 없는 상태참고 Repository 테스트를 했을 때와는 다르게 Service 테스트를 할 때는 Mockito를 사용한다@ExtendWith(MockitoExtension::class) class PresentationServiceTest { @InjectMocks lateinit var presentationService: PresentationService @Mock lateinit var presentationRepository: PresentationRepository }Service 테스트 코드 해석given일단 홀수이면 isActive = false, 짝수이면 true로 설정한다필터링을 해 isActive = true인 것만 남긴다presentationRepository.getActiveIntroductions()를 실행하면 필터링 한 데이터를 반환하도록 한다["2", "4", "6"]when테스트 대상 메서드를 실행한다thenDATA_SIZE = 7이므로 DATA_SIZE / 2 = 3.5이지만 정수 연산에서는 3이 반환 된다필터링 한 데이터 수가 일치하면 첫 번째 검증은 통과이다content 값을 정수로 변환하고, isEven()을 사용해 짝수인지 검증한다짝수이면 두 번째 검증도 통과이다@Repository class PresentationRepository( ...) { ... fun getActiveIntroductions(): List { return introductionRepository.findAllByIsActive(true) }... @Test fun testGetIntroductions() { // given val introductions = mutableListOf() for (i in 1..DATA_SIZE) { // 1, 3, 5, 7 -> false / 2, 4, 6 -> true val introduction = Introduction(content = "${i}", isActive = i % 2 == 0) introductions.add(introduction) } val activeIntroductions = introductions.filter { introduction -> introduction.isActive } Mockito.`when`(presentationRepository.getActiveIntroductions()) .thenReturn(activeIntroductions) // when val introductionDTOs = presentationService.getIntroductions() // then assertThat(introductionDTOs).hasSize(DATA_SIZE / 2) for (introductionDTO in introductionDTOs) { assertThat(introductionDTO.content.toInt()).isEven() } } Controller 테스트@SpringBootTest : 실제 애플리케이션과 유사한 환경을 구성하여 테스트 가능@AutoConfigureMockMVC : Spring MVC를 모의로 테스트하는 데 사용@SpringBootTest @AutoConfigureMockMvc @DisplayName("[API 컨트롤러 테스트]") class PresentationApiControllerTest(@Autowired private val mockMvc: MockMvc) { }Thymeleaf 문법th:fragment : 템플릿의 일부를 재사용 가능한 fragment로 정의th:replace : 해당 요소를 다른 요소로 대체할 때 사용 fragment 이름을 navigation으로 지정하고, 해당 경로에 있는 파일의 navigation fragment를 찾아서 대체한다// /templates/presentation/fragments/fragment-navigation.html // /templates/presentation/index.html 🎯 미션 6과 미션 7가상 프로필을 나의 프로필로 바꾸기강의 실습 프로젝트의 데이터 초기화 클래스 내용을 나의 프로필로 바꾼 뒤 커밋커밋 메시지 : [미션6] 가상 프로필을 나의 프로필로 바꾸기미션6 제출 스레드에 깃허브 커밋 링크를 공유프로젝트를 배포한 뒤 브라우저에서 접속미션7 제출 스레드에 도메인 주소를 공유문제이미지 태그를 지정하고 Dockerfile을 Build 하는 과정에서 test 실패 오류가 발생하였다테스트 코드가 문제인 건지 확인하기 위해 TestApplication을 실행해 전체 테스트 코드를 실행시키니 오류는 없었다원인을 찾을 수 없어서 계속 구글링을 해보니 아래 코드가 원인이 될 수도 있다고 한다tasks.withType { useJUnitPlatform() }해결아래 코드를 주석 처리하니 정상적으로 Build가 됐다tasks.withType { useJUnitPlatform() }참고
백엔드
2025. 03. 17.
0
인프런 워밍업 클럽 3기 BE 스터디 3주차
💻 강의입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기 📚 학습Data ClassData Class가 무엇이고, 어떤 용도로 사용되는지 찾아봤다Data Class 참고CustomExceptionJava Spring Project를 할 때 ErrorCode를 enum으로 관리해 CustomException을 사용한 적이 있다미니 프로젝트에 CustomException을 적용하려고 Kotlin으로 변환해보니 막히는 부분이 있었지만, 참고한 자료 덕분에 해결할 수 있었다CustomException 참고 { "timestamp": "2025-03-18T01:46:23.5967528", "status": 404, "message": "사용자를 찾을 수 없습니다." }아쉬운 점이번 주는 몸이 안 좋아서 평일 동안 회복하는 데 집중했고, 주말에는 평일에 못한 미션 4와 미션 5를 제출하였다작년 9월부터 지금까지 부트캠프와 스터디를 하면서 실력 향상에 집중하다 보니 피로가 쌓여 면역력이 떨어졌다😥건강 관리를 못해 결국 이번 주에 강의를 많이 듣지 못한 부분이 아쉽다회고토요일 오전에 수액을 맞고 많이 괜찮아져서 다음 주는 이번 주에 못한 만큼 열심히 해야겠다다음 주는 강의도 듣고, 미니 프로젝트 기능 보완, 인증/인가 구현, 예외 처리를 해야겠다 🎯 미션 4와 미션 5조회 REST API 만들기조회 API를 개발한 뒤 테스트 코드를 작성테스트 케이스는 3개 이상, 모든 케이스가 어떤 환경에서도 성공해야 함커밋 메시지 : [미션4] 조회 REST API 만들기미션4 제출 스레드에 깃허브 커밋 링크를 공유삽입, 수정, 삭제 REST API 만들기삽입, 수정, 삭제 API를 개발한 뒤 테스트 코드를 작성테스트 케이스는 API별로 3개 이상, 모든 케이스가 어떤 환경에서도 성공해야 함커밋 메시지 : [미션5] 삽입, 수정, 삭제 REST API 만들기미션5 제출 스레드에 깃허브 커밋 링크를 공유문제테스트 케이스를 3개 이상 작성해야 하는데 어떤 경우로 나뉘어서 작성해야 하는지 잘 모르겠다아직 테스트 케이스를 작성하는 것은 어려워서 일단 Repository에서 사용하는 메서드가 제대로 동작하는지 테스트 코드를 작성해 확인하였다findAll()findById()findDepartmentByCode()findCourseByIdAndStudent()회고findCourseByIdAndStudent() 메서드가 잘 동작하는지 테스트 코드를 작성했을 때 의도한 대로 결과가 나와서 뿌듯했다아직은 간단하게 테스트 코드를 작성할 수 있을 정도이지만, 계속 하다 보면 익숙해질 것 같다... @Test fun testFindCourseByIdAndStudent() { logger.info { "findCourseByIdAndStudent 테스트 시작" } val student = userRepository.findById(1L).get() val course = courseRepository.findCourseByIdAndStudent(1L, student).get() logger.info { "학생 이름: ${student.name}" } logger.info { "수강 과목의 학생 이름: ${course.student.name}" } assertThat(course.student).isEqualTo(student) logger.info { "findCourseByIdAndStudent 테스트 종료" } } }2025-03-23T21:26:12.090+09:00 INFO 5532 --- [ main] c.k.a.d.c.r.CourseRepositoryTest : findCourseByIdAndStudent 테스트 시작 Hibernate: select u1_0.id, u1_0.academic_year, u1_0.code, u1_0.created_at, u1_0.department_id, u1_0.login_id, u1_0.name, u1_0.password, u1_0.role, u1_0.updated_at from users u1_0 where u1_0.id=? Hibernate: select c1_0.id, c1_0.created_at, c1_0.student_id, c1_0.subject_id from course_enrollment c1_0 where c1_0.id=? and c1_0.student_id=? 2025-03-23T21:26:12.163+09:00 INFO 5532 --- [ main] c.k.a.d.c.r.CourseRepositoryTest : 학생 이름: 학생 2025-03-23T21:26:12.164+09:00 INFO 5532 --- [ main] c.k.a.d.c.r.CourseRepositoryTest : 수강 과목의 학생 이름: 학생 2025-03-23T21:26:12.165+09:00 INFO 5532 --- [ main] c.k.a.d.c.r.CourseRepositoryTest : findCourseByIdAndStudent 테스트 종료
백엔드