블로그

Day 16 Mission [Layered Architecture 에 대하여 정리]

1. 레이어 별로 어떤 특징이 있는가?보통 3계층 혹은 4계층으로 구조를 분리하는데 강의에서는 3계층으로 표현하고 있다.프레젠테이션 계층 (Presentation Layer)사용자와 직접 상호작용하는 부분이다. (외부 세계의 요청을 가장 먼저 받는 계층이다.)파라미터에 대한 최소한의 검증을 진행한다.비즈니스 로직 계층 (Business Logic Layer)애플리케이션의 핵심 기능과 규칙을 처리합니다.사용자 요청에 따라 데이터 처리, 검증, 트랜잭션 관리 등을 수행한다.영속성 계층 (Persistence Layer)데이터베이스나 외부 저장소와의 통신을 담당합니다.가공 로직이 포함되어서는 안된다.SQL 쿼리 실행, ORM 매핑 등이 이 계층에서 이뤄집니다.2. 어떻게 테스트 하면 좋을까?각 계층이 다른 계층에 침투하지 않도록 해야한다. 또한 각 레이어의 목표에 맞게 진행해야한다.프레젠테이션 계층 (Presentation Layer)사실 테스트하기 가장 난감하다. 단순 restfull api 서버라면 가능하지만 화면을 제공한다면 테스트가 복잡해진다.화면의 구조나 UI/UX 측인 부분도 검증이 필요하다.restfull 서버라는 가정하에는 MockMvc와 @MockBean을 사용하여 Mocking 처리가 가능하다.비즈니스 로직 계층 (Business Logic Layer)사실상 중간단계의 통합테스트가 많다. @MockBean을 사용하여 Repository를 Mocking하고 비즈니스 로직 검증이 가능하지만 fake객체를 만들어 단독으로 검증할 수 있도록 하는것이 더 좋다.(속도가 월등하다)데이터 접근 계층 (Data Access Layer)@Transactional를 이용한 CRUD 테스트가 가능하다.인메모리 디비를 사용해 테스트가 가능하지만 역시다 fake객체를 이용하여 빠른 속도로 검증이 가능하다.영속성 객체의 속성을 잘못 인지하면 테스트는 통과하지만 실제로는 오류가 발생한다거나 부하테스트 등을 정상적으로 처리다 안되고 영속성 1차 캐시에서만 처리되 무의미한 테스트가 발생할수 있으니 주의가 필요하다.

워밍업 클럽 3기 Code 과정 Day 16 미션

미션 내용Layered Architecture의 레이어별로 1) 어떤 특징이 있고2) 어떻게 테스트를 하면 좋을지자기만의 언어로 정리해보기 Persistence Layer특징DB와 상호작용하는 LayerDB에 값을 넣고 가져오는 로직을 담당비즈니스 가공 로직이 있으면 X (역할과 책임 분리)데이터 CRUD에 집중단위 테스트 느낌 어떻게 테스트 하면 좋을지?사용하는 기술 (ex : jpa, querydsl, jpql)을 내가 잘 사용해서 DB와 상호 작용하는지 테스트기술을 잘못 사용하면 원하는 데이터를 받아올 수 없음jpa의 경우에도 쿼리 메서드를 잘 짜주겠지만 혹시 내가 메서드명을 잘못 지었을 수도 있기에 쿼리 메서드에 대해서도 테스트주로 List 형태로 메서드의 반환 값이 많이 반환될 것이기에 hasSize, extracting, containsExactlyInAnyOrder 메서드 활용하면 좋음데이터를 직접 넣기에 데이터 만드는 메서드가 정의되어 있으면 조금 더 보기 좋음 Business Layer특징비즈니스 로직이 구현된 곳도메인 객체의 로직도 존재트랜잭션 관련된 것에 주의해야 함persistence layer와 상호 작용persistence layer와 business layer 통합 테스트 느낌 어떻게 테스트 하면 좋을지?비즈니스 로직, 도메인 객체의 로직을 수행하는 메서드들에 대해 테스트 진행 (단위 테스트)각 로직들 메서드 단위로 분리를 잘하여 테스트하기 용이하게 해야 함LocalDateTime과 같이 테스트하기 어려운 것들이 존재한다면 분리하고 파라미터로 받게 하여 테스트 용이하게 해야 함도메인과 관련된 validation은 이 layer에서 테스트를 진행하는 것이 더 역할과 책임이 분리된 것역시 데이터 많이 만들기에 데이터 만드는 메서드 있으면 좋음 Presentation Layer특징외부와 상호작용하는 곳api로 파라미터를 받거나 값을 내려주는 곳응답 처리, 예외 처리, validation이 중요한 곳하위 layer들과 분리해서 테스트하는 곳단위 테스트 느낌 어떻게 테스트 하면 좋을지?하위 layer들과 분리해서 테스트하는 것이 좋다. 이를 위해 MockMvc 및 mock 객체 사용andExpect 메서드를 잘 활용해야 함공통 응답이 잘 내려가는지 테스트 필요예외 발생 시 원하는 포맷, 메시지 등이 잘 응답되는지 테스트 필요쿼리 파라미터에 대한 validation 잘 이뤄지는지 테스트 필요여기에서의 validation은 기본적인 @NotNull, @NotBlank 같은 것을 테스트business layer의 validation과 분리하는 것이 필요

아아

인프런 워밍업 클럽 스터디 3기 - 백엔드 클린 코드, 테스트 코드 미션 - Day 16

Layered Architecture는 왜 사용하는 걸까?핵심 개념: 관심사의 분리 (Separation of Concerns)Layered Architecture의 핵심 목적은 관심사의 분리입니다.그렇다면 왜 관심사를 분리해야 할까요?가장 큰 이유는 유지보수성과 확장성 때문입니다.사용자의 요청이 들어왔을 때, 각 Layer가 명확한 책임을 갖고 동작한다면 코드 수정이나 기능 추가가 훨씬 쉬워집니다.Spring Boot에서의 관심사 분리Spring Boot로 개발할 때 우리는 자연스럽게 관심사를 다음과 같이 나누게 됩니다:Controller (Presentation Layer): 사용자의 요청을 받고 응답을 반환Service (Business Layer): 비즈니스 로직 처리Repository (Persistence Layer): 데이터베이스 접근 및 처리아래 예시를 보면서 각 Layer의 역할을 살펴보겠습니다.Presentation Layer사용자의 요청을 받고 요청값을 검증 후 반환합니다. @RestController @RequiredArgsConstructor public class UserController { private final UserService userService; @GetMapping("/login") public LoginResponse login(@RequestBody @Valid LoginRequest loginRequest) { return service.login(loginRequest); } } @Vali를 통해 LoginRequest을 검증합니다.Business Layer비즈니스 로직을 수행하는 핵심 구간입니다. @Service @RequiredArgsConstructor public class UserService { private final UserRespository userRepository; public LoginResponse login(LoginRequest loginRequest) { Users user = userRepository.findByEmail(loginRequest.email()) .orElseThrow( LoginErrorCode.EMAIL_NOT_FOUND::exception ); if(user.matchPassword(bCryptPasswordEncoder, loginRequest.password())) { throw LoginErrorCode.PASSWORD_NOT_FOUND.exception(); } return LoginResponse.builder() .username(user.getUsername) .build(); } Persistence Layer 데이터베이스 접근을 담당합니다. public interface UserRepository extends JpaRepository<Users,Long> { }이렇게 각 레이어가 자신의 역할에 집중하면:- 수정과 확장이 쉬워지고- 테스트 코드 작성도 쉬워지며- 유지보수성이 높아집니다.테스트 코드 작성저는 주로 서비스단을 테스트 코드로 작성해서 검증합니다.서비스쪽이 비즈니스 로직을 담당하고 있는 부분이라 생각하여 제일 중요하다 생각합니다. 그래서 이번에 채팅방 생성하는 테스트 코드를 저의 방식대로 해봤습니다.@ActiveProfiles("test") @SpringBootTest class ChatRoomTest { @Autowired private ChatRoomRepository chatRoomRepository; @Autowired private UserRepository userRepository; @Autowired private BoardRepository boardRepository; @Autowired private ChatService chatService; @DisplayName("방 생성 테스트") @Test void createRoom() { // given Users user = userRepository.findById(1L).orElseThrow(); Product product = boardRepository.findById(1L).orElseThrow(); Users user2 = userRepository.findById(2L).orElseThrow(); Product product2 = boardRepository.findById(2L).orElseThrow(); ChatRoomRequest request = ChatRoomRequest.builder() .userId(user) .productId(product) .name("테스트 채팅방") .build(); ChatRoomRequest request2 = ChatRoomRequest.builder() .userId(user2) .productId(product2) .name("테스트 채팅방2") .build(); List<ChatRoomEntity> chatRoom = ChatRoom.create(List.of(request, request2)); List<ChatRoomEntity> save = chatRoomRepository.saveAll(chatRoom); // when var chatRooms = chatRoomRepository.findAll(); // then assertThat(chatRooms).hasSize(2); assertThat(chatRooms.get(0).getProductId().getId()).isEqualTo(1L); assertThat(chatRooms.get(0).getProductId().getId()).isEqualTo(2L); } } application-test.yml을 만들어 테스트 환경을 구성하였으며 그 구성 기반으로 더미 데이터(data.sql)를 만들어서 테스트를 진행했습니다.

정기석

워밍업 클럽 3기 BE 클린코드&테스트 DAY-18 미션

1. @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이MockSpring Context 없이 순수하게 Mock 객체를 만들어서 사용InjectMocksMock은 Spring Context에서 관리해주지 않기 때문에 해당 Mock객체가 필요한 객체에 선언하여 Mock객체를 주입MockBeanSpring Context 에서 관리하는 빈 대신 사용하기 위해 사용MockBean을 사용하면 Srping Context에 있는 빈을 대신해서 사용하기 때문에 Spring에서 의존성 주입을 해준다. Spymock은 가짜객체라서 지정한 행동말고는 아무것도 하지 않지만진짜객체를 기반으로 생성되어 일부 행동에 대해 지정한 값을 반환Mock이 when과 thenReturn을 지정하지만Spy는 진짜 객체를 기반으로 행동하기에 doSometing(return)만 지정실제객체를 기반으로 만들기에 stub과 실제 메소드 혼합해서 테스트 가능SpyBeanmock과 mockbean같은 관계2. 아래 3개의 테스트가 있습니다. 내용을 살펴보고, 각 항목을 @BeforeEach, given절, when절에 배치한다면 어떻게 배치하고 싶으신가요?(@BeforeEach에 올라간 내용은 공통 항목으로 합칠 수 있습니다. ex. 1-1과 2-1을 하나로 합쳐서 @BeforeEach에 배치)@BeforeEach void setUp() { 사용자1 생성에 필요한 내용 준비 사용자1 생성 사용자2 생성에 필요한 내용 준비 사용자2 생성 사용자1의 게시물 생성에 필요한 내용 준비 사용자1의 게시물 생성 } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given 댓글 생성에 필요한 내용 준비 // when 댓글 생성 // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given 댓글 생성에 필요한 내용 준비 댓글 생성 // when 댓글 수정 // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given 사용자1의 댓글 생성에 필요한 내용 준비 사용자1의 댓글 생성 // when // then 사용자2가 사용자1의 댓글 수정 시도 검증 }

바커스

아이폰 배터리 교체 효율 성능 80이상 만들기 설정 꿀팁

출근길에 배터리가 빠르게 소모되어 고민했던 경험을 바탕으로, 배터리를 오래 사용할 수 있는 설정 및 충전 습관을 정리했습니다.📌 배터리 수명 연장 설정법 (iOS & Android)✅ 1. 화면 밝기 조절자동 밝기보다는 수동 조절이 배터리 절약에 효과적입니다.iPhone: 설정 → 디스플레이 및 밝기 → 자동 밝기 해제Android: 설정 → 디스플레이 → 밝기 수동 조절💡 OLED 디스플레이 기기는 "다크 모드"를 활용하면 더욱 효과적! ✅ 2. 백그라운드 앱 정리불필요한 앱이 계속 실행되면 배터리가 소모됩니다.iPhone: 설정 → 일반 → 백그라운드 앱 새로 고침 → "Wi-Fi 또는 꺼짐"Android: 설정 → 배터리 → 배터리 최적화 → 필요 없는 앱 비활성화💡 SNS, 뉴스 앱 등 필요한 앱만 백그라운드 실행 허용 추천! ✅ 3. 위치 서비스 최적화GPS 사용은 배터리 소모가 크므로 제한하는 것이 좋습니다.iPhone: 설정 → 개인정보 보호 → 위치 서비스 → "앱 사용 중"으로 변경Android: 설정 → 위치 → 사용하지 않는 앱의 위치 액세스 차단 ✅ 4. 배터리 절약 모드 활성화배터리가 부족할 때 절약 모드를 활용하면 사용 시간을 늘릴 수 있습니다.iPhone: 설정 → 배터리 → 저전력 모드Android: 설정 → 배터리 → 절전 모드⚡ 충전 습관 개선 방법 (배터리 수명 연장 핵심!)✅ 1. 20~80% 구간에서 충전하기0%까지 방전 후 충전하는 것은 배터리에 부담을 줍니다.💡 최적의 충전 구간: 20~80%✅ 2. 취침 중 충전 피하기배터리를 장시간 100% 상태로 유지하면 열화가 가속됩니다.💡 "iPhone 최적화된 배터리 충전" 기능 활성화 추천!✅ 3. 고속 충전 남용 금지고속 충전은 발열을 유발하여 배터리 수명을 단축시킬 수 있습니다.💡 일상에서는 일반 충전(5W~15W) 활용!📊 배터리 상태 확인 & 교체 시점 체크iPhone: 설정 → 배터리 → 배터리 성능 상태Android: 설정 → 배터리 → 배터리 사용량💡 배터리 성능이 80% 이하로 떨어지면 교체 고려!🔎 배터리 오래 쓰는 법, 오늘부터 실천하세요!✔ 스마트폰 설정 최적화 📱✔ 올바른 충전 습관 ⚡✔ 배터리 건강 체크 🔋이 방법을 적용하면 배터리 수명이 확실히 길어지는 걸 체감할 수 있습니다. 😊📌 좀 더 상세한 정보가 필요하면 아래 링크를 참조하시기 바랍니다.상세한 정보 보러가기   

교양스마트폰배터리절약법아이온폰배터리수명안드로이드배터리수명아이온폰배터리절약안드로이드배터리설정

[워밍업 클럽 3기 BE 클린코드&테스트] - 레이어드 아키텍처 주요 특징

Spring에서 애플리케이션을 설계할 때 흔히 사용하는 Layered Architecture(레이어드 아키텍처)는 다음과 같은 3가지 주요 계층으로 구성[Presentation Layer] → [Business Layer] → [Persistence Layer] (Controller) (Service) (Repository) Layered Architecture의 핵심 특징계층 분리 : 각 계층이 독립된 책임을 가지며, 역할이 명확히 나뉨독립성 : 상위 계층은 하위 계층을 의존하지만, 하위 계층은 상위 계층을 모름유연성 : 특정 계층을 변경하더라도 다른 계층에 영향이 적음 테스트 용이성 : 계층별 단위 테스트가 가능해 테스트 작성이 명확하고 쉬움1. Presentation Layer (UI 계층 - Controller)✅ 역할사용자 요청을 받아서 적절한 응답을 반환 (주로 JSON 형태)HTTP 관련 처리 (@RestController, @GetMapping 등)✅ 테스트 전략@WebMvcTest를 사용하여 컨트롤러만 테스트MockMvc + ObjectMapper로 HTTP 요청/응답 흐름을 검증Service는 @MockBean으로 주입📌 예시 코드WebMvcTest(controllers = OrderController.class) class OrderControllerTest { @Autowired MockMvc mockMvc; @Autowired ObjectMapper objectMapper; @MockBean OrderService orderService; @Test void 신규_주문_등록_성공() throws Exception { OrderCreateRequest request = OrderCreateRequest.builder() .productNumbers(List.of("001")) .build(); mockMvc.perform(post("/api/v1/orders/new") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("200")) .andExpect(jsonPath("$.message").value("OK")); } @Test void 상품번호_없을때_예외() throws Exception { OrderCreateRequest request = OrderCreateRequest.builder().build(); mockMvc.perform(post("/api/v1/orders/new") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.message").value("상품 번호 리스트는 필수입니다.")); } } 2. Business Layer (서비스 계층 - Service)✅ 역할비즈니스 로직 처리의 중심트랜잭션 처리, 여러 레포지토리 호출, 외부 API 조합 등✅ 테스트 전략@SpringBootTest 또는 순수 JUnit 테스트로 비즈니스 로직 검증Repository는 필요 시 가짜(Mock) 또는 실제 DB 사용Happy Path + 예외 케이스 + 경계값 등 다양한 시나리오 테스트📌 예시 코드@SpringBootTest class ProductServiceTest { @Autowired private ProductService productService; @Autowired private ProductRepository productRepository; @AfterEach void tearDown() { productRepository.deleteAllInBatch(); ; } @DisplayName("신규 상품을 등록한다. 상품번호는 가장 최근 상품의 상품번호에서 1증가한 것이다.") @Test void createProduct() { //given Product product = createProduct("001", HANDMADE, SELLING, "아메리카노", 4000); productRepository.save(product); ProductCreateServiceRequest request = ProductCreateServiceRequest.builder() .type((HANDMADE)) .sellingStatus((SELLING)) .name("카푸치노") .price(5000) .build(); //when ProductResponse productResponse = productService.createProduct(request); //then assertThat(productResponse) .extracting("productNumber", "type", "sellingStatus", "name", "price") .contains("002", HANDMADE, SELLING, "카푸치노", 5000); List<Product> products = productRepository.findAll(); assertThat(products).hasSize(2) .extracting("productNumber", "type", "sellingStatus", "name", "price") .containsExactlyInAnyOrder( tuple("001", HANDMADE, SELLING, "아메리카노", 4000), tuple("002", HANDMADE, SELLING, "카푸치노", 5000) ); }주의: 테스트에서 @Transactional을 사용할 경우, 실제 코드와 작동 방식이 달라질 수 있음 → 필요에 따라 deleteAllInBatch()로 정리3. Persistence Layer (저장소 계층 - Repository)✅ 역할데이터베이스와 직접 상호작용 (CRUD, 조건 검색 등)JPA, MyBatis 등 ORM/쿼리 도구 사용✅ 테스트 전략@DataJpaTest를 사용하여 가벼운 JPA 테스트테스트 종료 시 자동으로 Rollback 처리됨실제 DB 조회/저장 동작 검증📌 예시 코드@ActiveProfiles("test") //@SpringBootTest @DataJpaTest class ProductRepositoryTest { @Autowired private ProductRepository productRepository; @DisplayName("원하는 판매상태를 가진 상품들을 조회한다.") @Test void findAllBySellingStatusIn() { //given Product product1 = createProduct("001", HANDMADE, SELLING, "아메리카노", 4000); Product product2 = createProduct("002", HANDMADE, HOLD, "카페라떼", 4500); Product product3 = createProduct("003", HANDMADE, STOP_SELLING, "팥빙수", 7000); productRepository.saveAll(List.of(product1, product2, product3)); //when List<Product> products = productRepository.findAllBySellingStatusIn(List.of(SELLING, HOLD)); //then assertThat(products).hasSize(2) .extracting("productNumber", "name", "sellingStatus") .containsExactlyInAnyOrder( tuple("001", "아메리카노", SELLING), tuple("002", "카페라떼", HOLD) ); }요약Controller : HTTP 요청/응답 처리, @WebMvcTest + MockMvc로 요청 파라미터, 상태 코드, JSON 응답 값 등을 테스트Service : 비즈니스 로직 처리, @SpringBootTest or JUnit + Mockito로 로직 분기, 예외 처리, 흐름 제어 등 테스트Repository : 데이터 CRUD 및 조회, @DataJpaTest로 쿼리 정확성, 연관 관계 매핑, 조건 검색 등을 테스트

레이어드 아키텍처 특징 및 테스트 작성법 알아보기

이 글은 박우빈님의 Practical-Testing 강의 를 참조하여 작성한 글입니다. 레이어드 아키텍처(Layered Architecture)란?소프트웨어 시스템을 계층(layer)으로 나누어 구성하는 설계 방식으로, 각 계층이 특정한 역할과 책임을 갖도록 설계하는 것을 의미한다.이렇게 각 계층들을 관심사를 기준으로 분리함으로써 계층의 응집도를 높이고 결합도를 낮출 수 있다.-> 유지보수성이 올라감! 레이어드 아키텍처의 주요 특징 계층 분리 Layered Architecture에서는 보통 3개의 Layer로 구성되어 있다.Presentation Layer (UI 계층): 사용자의 요청 및 응답을 처리하며 상호작용 한다.Business Layer (서비스 계층): 비즈니스 로직을 수행하는 책임을 지닌다.Persistence Layer (비즈니스 계층): DB에 접근하여 상호작용(데이터 CRUD) 한다.   독립성상위 계층은 하위 계층에 의존하지만,하위 계층은 상위 계층에 대한 지식이나 정보를 가지지 않아야 한다.e.g, Presentation Layer에서는 하위 계층인 Business Layer를 의존한다.이때 Business Layer는 상위 계층인 Presentation Layer에서 넘어온 데이터로 비즈니스 로직을 처리할 뿐이며,Presentation Layer 에 대해 알고 있지 않다!유연성 특정 계층의 구현을 변경하더라도 다른 계층에는 영향을 미치지 않도록 설계할 수 있다. 테스트 용이성계층별로 테스트가 가능하므로, 각 계층의 단위 테스트를 독립적으로 수행할 수 있다. 각 계층 테스트 코드 작성법 Presentation LayerAPI의 요청-응답 흐름과 응답 형식(HTTP 상태 코드와 JSON 응답 데이터)을 검증하는 테스트를 작성한다.@WebMvcTest 를 통해 테스트 하고자 하는 컨트롤러를 등록해준다.해당 컨트롤러를 테스트 하기위해 필요한 Business 계층(Service 클래스)를 @MockBean을 통해 주입하며, MockMvc 또한 의존성 주입해준다.이 때 json과의 소통이 필요하므로 객체를 json, json을 객채로 변환할 수 있게끔 ObjectMapper 또한 주입해주자.@WebMvcTest(controllers = OrderController.class) class OrderControllerTest { @Autowired MockMvc mockMvc; @Autowired ObjectMapper objectMapper; @MockBean OrderService orderService; @DisplayName("신규 주문을 등록한다.") @Test void createOrder() throws Exception { // given OrderCreateRequest request = OrderCreateRequest.builder() .productNumbers(List.of("001")) .build(); // when // then mockMvc.perform( post("/api/v1/orders/new") .content(objectMapper.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON) ) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("200")) .andExpect(jsonPath("$.status").value("OK")) .andExpect(jsonPath("$.message").value("OK")); } @DisplayName("신규 주문을 등록할 때 상품번호는 1개 이상이어야 한다.") @Test void createOrderWithEmptyProductNumbers() throws Exception { // given OrderCreateRequest request = OrderCreateRequest.builder() .build(); // when // then mockMvc.perform( post("/api/v1/orders/new") .content(objectMapper.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON) ) .andDo(print()) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.code").value("400")) .andExpect(jsonPath("$.status").value("BAD_REQUEST")) .andExpect(jsonPath("$.message").value("상품 번호 리스트는 필수입니다.")) .andExpect(jsonPath("$.data").isEmpty()); } }  Business Layer작성한 비지니스 로직이 의도한 대로 작동하는지 검증하는 테스트를 작성한다.@SpringBootTest 을 통해 모든 Bean 의존성 주입함으로써 통합 테스트가 가능하다.해피케이스 뿐만 아니라 예외 테스트, 경계값 테스트 등 여러 케이스에 대해 테스트를 작성하자.@Transactional vs sql을 이용한 데이터 삭제실제 코드에서는 @Transactional을 사용하지 않는데, 단순히 롤백만을 위해 테스트코드에서 @Transactional을 사용하면, 실제 작동 방식과 다르게 작동할 수 있다.따라서 테스트 코드 작성시 Transactional의 부작용에 대해 인지하고 사용할 것!!@SpringBootTest class ProductServiceTest { @Autowired private ProductService productService; @Autowired private ProductRepository productRepository; @AfterEach void tearDown() { productRepository.deleteAllInBatch(); } @DisplayName("신규 상품을 등록한다. 상품번호는 가장 최근 상품의 상품번호에서 1 증가한 값이다.") @Test void createProduct() { //given Product product1 = createProduct("001", HANDMADE, SELLING, "아메리카노", 4000); productRepository.save(product1); ProductCreateRequest request = ProductCreateRequest.builder() .type(HANDMADE) .sellingStatus(SELLING) .name("카푸치노") .price(5000) .build(); //when ProductResponse productResponse = productService.createProduct(request); //then assertThat(productResponse) .extracting("productNumber", "type", "sellingStatus", "price", "name") .contains("002", HANDMADE, SELLING, 5000, "카푸치노"); List<Product> products = productRepository.findAll(); assertThat(products).hasSize(2) .extracting("productNumber", "type", "sellingStatus", "price", "name") .containsExactlyInAnyOrder( tuple("001", HANDMADE, SELLING, 4000, "아메리카노"), tuple("002", HANDMADE, SELLING, 5000, "카푸치노") ); } } Persistence Layer 데이터에 접근하는 역할로 데이터 CRUD와 연관된 메서드들을 테스트 한다. @DataJpaTest JPA와 관련된 의존성들만 주입해준다 -> @SpringBootTest보다 가볍다.어노테이션 내부에 @Transactional이 포함되어 있어 테스트 후 데이터가 롤백된다.@DataJpaTest // @DataJpaTest안에 @Transactional이 걸려있어서 자동으로 rollback이 됨 class ProductRepositoryTest { @Autowired private ProductRepository productRepository; @DisplayName("원하는 판매상태를 가진 상품들을 조회한다.") @Test void findAllBySellingStatusIn() { //given Product product1 = createProduct("001", HANDMADE, SELLING, "아메리카노", 4000); Product product2 = createProduct("002", HANDMADE, HOLD, "카페라떼", 4500); Product product3 = createProduct("003", HANDMADE, STOP_SELLING, "팥빙수", 7000); productRepository.saveAll(List.of(product1, product2, product3)); //when List<Product> products = productRepository.findAllBySellingStatusIn(List.of(SELLING, HOLD)); //then assertThat(products).hasSize(2) .extracting("productNumber", "name", "sellingStatus") .containsExactlyInAnyOrder( tuple("001", "아메리카노", SELLING), tuple("002", "카페라떼", HOLD) ); } }Referencehttps://www.inflearn.com/course/practical-testing-%EC%8B%A4%EC%9A%A9%EC%A0%81%EC%9D%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%80%EC%9D%B4%EB%93%9C/dashboard 

백엔드레이어드아키텍쳐워밍업클럽테스트코드

별안간뿌우

[인프런 워밍업 클럽_3기 백엔드 프로젝트]2번째 발자국

지금부터는 실습 위주라 발자국을 자세하게 남기긴 힘들 것 같다.데이터 베이스 초기화를 하여 데이터를 삽입을 하는 작업을 한다.코틀린을 써본 적이 없어서 아직 너무 미숙하다. 그리고 뭐가 뭔진 잘 모르겠지만지금 데이터를 넣을 때mutableListof 라는 메서드를 이용하여 삽입하고 있다.그리고 데이터를 삽입할 때컬럼이 여러개일 경우그 컬럼의 값이 안 들어가게 되면 오류가 난다.값이 여러개 일 경우 mutableListof 를 사용하여 리스트화 해서saveall 이라는 키워드를 사용해 값을 집어 넣는다.saveall은 영속성으로 값을 집어 넣는 것이다.실습을 하던 도중 Error creating bean with name '*': Invocation of init method failed이 에러가 떠 인터넷에 찾아보다가 강의를 다시 들으며 오타를 낸 부분이 있나 찾아봤더니skill부분에 project로 의존주입을 해서 에러가 난 거였다.테스트 코드는 매우 중요하다.인텔리제이는 테스트코드를 작성하기 매우 용이하다테스트 패키지안에코틀린 파일을 만든다그리고 어노테이션을 @DataJpaTest 이것을 부착해준다.@DataJpaTest은 필요한 기능들만 초기화해준다롤백을 안해주면 다음 테스트에 영향을 미칠 수 있다트랜잭션 어노테이션을 달아준다@TestInstance(TestInstance.Lifecycle.PER_CLASS)한번 만들어주고 클래스 단위로 돌아가게 해준다.Assertions.assertThat(beforeInitialize).hasSize(0)테스트 통과 실패여부를 알려주는 코드테스트 코드 실행 후 실패 에러defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract long확인해보니 httpinterfaceRepostory의 메서드의 이름에 오타가 나있었던 것. 

워밍업클럽3기_미션3_지표 설계

미션프로덕트에서 어떤 지표를 측정할지 설정하기. 지표의 이름을 정하고 지표의 계산식을 정의해보기. 해당 지표를 측정해야 하는 이유는 무엇인지, 지표의 변동에 따라 어떤 후속 조치를 해야 하는지 작성하기 지표 설계(1) 지표 설정 목적과 방향새로 추가한 기능을 사용자들이 원활하게 사용하고 있는지 추적이 필요한 상황이라, 신규 사용자의 기능 활성화 지표를 측정 지표로 설정하였다.새로 추가한 기능의 사용 플로우는 아래와 같다.*플로우: 메인 화면 -> 코치마크 -> 미리 만들어 둔 프로필 선택/프로필 생성 -> 시간 입력 -> 휴대폰 화면 제한 시작 -> 종료/ 이탈서비스의 핵심 가치는 휴대폰 사용을 제한하는 신규 기능을 사용자의 라이프 스타일에 맞게 커스텀하여 활용하는 것이다. 따라서 활성화 지표를 Set up, Aha, Habit 단계로 구분하여 보았다.Set up: 미리 만들어 둔 프로필을 선택하여 휴대폰 화면 제한을 시작한 후에 종료 또는 이탈하는 것을 목표로 둠. 제한 종료 또는 이탈을 이벤트를 발생시킨 신규 사용자 수 측정*수집 가능한 이벤트: 메인 화면 -> 프로필 선택 -> 시간 입력 -> 제한 시작 버튼 클릭 -> 제한 화면 -> 제한 종료 또는 이탈Aha: 사용자가 새로 프로필을 생성하여 저장하는 것을 목표로 둠. 저장 버튼 클릭 이벤트를 발생 시킨 신규 사용자 수 측정*수집 가능한 이벤트: 메인 화면 -> 프로필 추가 버튼 클릭 -> 프로필 정보 입력 화면 -> 프로필 정보 입력 -> 저장 버튼 클릭Habit: 사용자가 일주일에 3회 이상 화면 제한 기능을 사용하는 것을 목표로 둠. 제한 또는 이탈 이벤트를 가입 후 일주일 이내에 3회 이상 발생시킨 신규 사용자 수 측정*수집 가능한 이벤트: 메인 화면 -> 프로필 선택 -> 시간 입력 -> 제한 시작 버튼 클릭 -> 제한 화면 -> 제한 종료 또는 이탈(2) 지표 이름과 계산식신규 사용자: first_open 이벤트와 로그인 이벤트, 그리고 데이터 새로 가져오기 이벤트가 모두 일어난 사용자. 데이터 새로 가져오기 이벤트를 기준으로 7일 간 신규 사용자로 처리Set up = 신규 사용자이면서 제한 종료 또는 이탈 이벤트를 발생 시킨 사용자 수/ 메인 화면을 방문한 신규 사용자 수*100Aha=신규 사용자이면서 프로필 저장 버튼을 클릭한 사용자 수/ 메인 화면을 방문한 신규 사용자 수*100Habit=신규 사용자이면서 제한 종료 또는 이탈 이벤트를 3회 이상 발생 시킨 사용자 수/ 메인 화면을 방문한 신규 사용자 수*100. 7일간 기간을 설정한 후 계산할 것(3) 후속 조치각 단계의 %를 살펴보며 기능의 사용자 플로우에서 개선이 필요한 부분을 발굴하여 개선 방향을 고민한다.

기획 · PM· PO

gptjddl777

[인프런 워밍업 클럽 3기] PM/PO - 3주차 미션

미션 3. 프로덕트 지표 설정하기여러분이 맡은 프로덕트에서 어떤 지표들을 측정할지 설정해보세요.단, 프로덕트 지표 프레임워크 강의에서 소개한 지표들은 제외합니다. 지표의 이름을 정하고, 지표의 계산식을 정의해보세요.그 지표를 측정해야 하는 이유는 무엇인지, 지표의 변동에 따라 어떤 의사결정 또는 후속조치를 해야 하는지 작성해보세요.프로덕트 : 올리브영지표명 : 배송유형별 선택율계산식 : 각 배송유형(일반배송/오늘드림/픽업) 장바구니 수 / 전체 장바구니 수 X 100측정이유 : 사용자들이 선호하는 배송유형을 파악하고, 강화 혹은 개선이 필요한 배송유형을 확인하기 위함 의사결정/후속조치 : 선택율이 가장 높은 배송유형의 배송방법을 더 강화하고, 선택율이 가장 낮은 배송유형을 개선하기 위한 프로모션 및 마케팅 강화 2. 지표명 : 배송유형별 구매전환율계산식 : 각 배송유형별 주문완료 건수/ 각 배송유형을 선택한 건수 X 100측정이유 : 각 배송유형에 따른 구매전환율을 확인하여, 강화 혹은 개선이 필요한 배송유형을 파악하기 위함의사결정/후속조치 : 구매전환율이 낮은 배송유형에 대한 구매혜택 추가제공 및 배송유형 정보 추가안내를 통해 장바구니 내 구매전환율 개선3. 지표명 : 배송유형별 장바구니 이탈률계산식 : 각 배송유형별 → 결제하지 않고 이탈한 장바구니 수 / 장바구니 수 X 100측정이유 : 각 배송유형에 따른 이탈률을 확인하여, 이탈 요인을 파악하기 위함의사결정/후속조치 : 특정 배송유형에 대한 이탈률이 높은경우 해당 배송유형의 불편한점, 이탈하게 되는 요인을 찾아 이탈을 최소화 하기 위한 배송방법 개선점 도출 

기획 · PM· POPMPO

Jaeeun Jeong

워밍업 클럽 3기 PM/PO_미션3

미션 3. 프로덕트 지표 설정하기 여러분이 맡은 프로덕트에서 어떤 지표들을 측정할지 설정해보세요.단, 프로덕트 지표 프레임워크 강의에서 소개한 지표들은 제외합니다.맡고 있는 프로덕트가 없는 경우, 프로덕트를 하나 정해서 해 보세요.지표의 이름을 정하고, 지표의 계산식을 정의해보세요.그 지표를 측정해야 하는 이유는 무엇인지, 지표의 변동에 따라 어떤 의사결정 또는 후속조치를 해야 하는지 작성해보세요.지표 대상 서비스화해의 마케팅 서비스 지표1: 사용자 마케팅 채널지표: 자사 이벤트 시 컨텐츠의 채널별 참여자 수 확인계산식: (각 채널별 이벤트 참여자 수) / (해당 콘텐츠 게시 후 일정 기간 내 참여자 수)지표 이유: 마케팅에 사용시 효과적인 광고 채널을 파악할 수 있고, 채널별 효과성도 파악 가능하다.의사결정: 참여자가 많은 채널을 집중적으로 활용하여 마케팅 비용을 절감할 수 있음. 참여자가 적은 채널은 광고 전략 및 콘텐츠 기획 방향을 수정하거나 우선 순위를 낮출 수 있다.후속 조치참여자 증가 : 해당 채널의 콘텐츠 강화 및 예산 추가하여 지속적인 활용참여자 감소 : 콘텐츠 수정 및 다른 채널로의 이동 지표2. 외부 업체와 협업시 효과적인 채널 확인지표 : 외부 업체(인플루언서, 블로그, SNS 광고, 이벤트 부스)를 이용한 캠페인을 통해 유입된 가입 및 참여자 수 확인계산식: (각 캠페인 별 유입자 수 / 캠페인 활동 기간) / 소요된 비용측정 이유: 단가 대비 캠페인 참여자 수 파악을 통해 효과적인 캠페인 채널을 파악하여 외부 업체와 협업시 방향성을 정할 수 있다.의사결정:효율이 높은 캠페인 : 적극적으로 활용 및 협업 강화효율이 낮은 캠페인 : 캠페인을 중단하거나 재검토를 통해 전략 방향 수정지표 변동에 따른 의사 결정 및 후속 조치효율적인 채널: 더 많은 예산 투입 및 추가 캠페인 기획비효율적인 채널: 해당 채널의 캠페인 종료 및 캠페인 방향/기획 재검토 지표3. 외부 업체와 협업을 통해 유입된 사용자의 리텐션율 확인지표: 외부 업체(인플루언서, 블로그, SNS 광고, 이벤트 부스)를 통해 유입된 사용자의 리텐션율 (재방문율, 지속 사용 비율 등)계산식: (외부 업체를 통해 유입된 사용자가 일정 기간 내 다시 방문하거나 활동한 비율) / (전체 외부 업체 유입 사용자 수)측정이유: 유입된 사용자 중 실제로 지속적으로 활동하는 사용자의 비율을 확인하여, 외부 협업의 효과를 장기적으로 평가할 수 있습니다. 리텐션율을 통해 유입된 사용자가 얼마나 브랜드에 충성도 높은지, 협업의 장기적인 효과를 평가할 수 있습니다.지표 변동에 따른 의사 결정 및 후속 조치: 높은 리텐션율: 해당 업체와의 추가 협업을 고려하거나, 동일한 전략을 다른 외부 업체와 적용.낮은 리텐션율: 타겟층 고려 및 외부 업체의 협업 퀄리티 등의 파악을 통해 원인을 분석하여 업체를 변경하거나 전략을 수정  회고다 작성하고 보니 제품을 성장시키고 발전시키는 부분보다 제품을 광고하고 홍보하는 지표로서 작성된거 같아서 조금 아쉽네요.캠페인에 사용된 기능들의 사용자의 사용 빈도나 사용 시간등의 파악을 통해 제품의 문제점 파악 및 개선의 지표로 사용할 수도 있을 거 같습니다.

Day 16 미션 : 레이어 아키텍쳐의 테스트

이번에 설명하는 레이어는 영속성 레이어, 비즈니스 레이어, 프레젠테이션 레이어를 기준으로 설명하겠습니다.레이어의 특징어떻게 테스트하면 좋을지영속성 레이어영속성 레이어?DB에 접근하는 계층비즈니스 로직이 들어가지 않은 순수하게 데이터에 대한 처리 및 조회를 수행영속성 레이어의 테스트무엇을 확인해야할까?원하는 데이터에 정확히 접근하는지쿼리가 길어졌을 때, 내가 원하는 데이터에 맞게 작성되었는지어떻게 테스트를 해야할까?영속성 계층이 의존하는 계층이 대부분 상황에서 없기 때문에 단위 테스트 형식으로 진행비즈니스 레이어비즈니스 레이어?비즈니스 로직이 전개되는 계층영속성 레이어가 사용된다.도메인 개념이 적용트랜잭션 개념 적용비즈니스 레이어의 테스트하나의 트랜잭션을 보장하는지 확인비즈니스 로직이 정확히 수행되는지 확인여러 케이스에 대해 테스트하자.프레젠테이션 레이어프레젠테이션 레이어란?외부 세계와 가장 가까운 계층요청과 관련한 데이터를 받는다.요청에 대한 데이터를 전달한다.프레젠테이션 레이어의 테스트요청에서 건너온 값들에 대한 검증도메인 규칙을 제외한 간단한 검증상황에 대한 정확한 응답이 반환되는지 확인

백엔드테스트레이어

징니

인프런 워밍업 클럽 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 테스트 종료

백엔드

바커스

콘텐츠마케팅 트래픽 설계 | 온라인 비즈니스 성공을 위한 핵심 전략4가지

🔍 트래픽 설계의 의미와 핵심 개념단순히 방문자 수를 늘리는 것이 아니라, 타겟 고객이 있는 채널을 공략하고 이들을 비즈니스 자산으로 전환하는 전략입니다. 광고만으로는 매출로 이어지지 않으며, 체계적인 트래픽 설계가 필수입니다.💡 왜 트래픽 설계가 중요한가?좋은 제품도 노출되지 않으면 의미 없습니다. 브랜드 인지도 확보, 리드 수집, 매출 증대, 장기적인 유입 유지 등 비즈니스 성장을 위한 토대가 되며, 소유 트래픽 구축이 핵심입니다.📈 단기 vs 장기 트래픽 전략의 차이단기 트래픽은 빠른 유입이 가능하지만 지속성이 낮고 광고비가 필요합니다. 반면, 장기 트래픽은 시간은 걸리지만 안정적인 유입이 가능해 장기적으로 더 효율적인 구조를 만들 수 있습니다.✅ 실전 사례로 보는 트래픽 설계사례 1: 유튜브와 이메일 리스트를 통해 콘텐츠 기반 장기 트래픽 구축, 수천 명의 구독자와 매출 증가 달성.사례 2: 인스타그램 콘텐츠 바이럴로 SNS 유입 확대, 웹사이트 및 구독자로 유도해 방문자 5배 증가.🛠 실행 가능한 전략 4가지① 타겟 고객이 있는 커뮤니티에서 콘텐츠로 자연스럽게 노출② 이메일, 유튜브 등 소유 트래픽 자산 구축③ 단기 광고와 장기 콘텐츠 전략 병행④ 분석 툴을 활용해 최적화 지속적으로 진행좀 더 상세한 정보가 필요하시면 아래 링크를 참조하시기 바랍니다.콘텐츠마케팅 트래픽 설계란 무엇인가?(온라인 비즈니스)

마케팅콘텐츠마케팅콘텐츠마케팅트래픽콘텐츠마케팅트래픽설계온라인비즈니스

junghlee234

[인프런 워밍업 클럽 스터디 3기 - 프로덕트 디자인 (Figma)] 3주차 발자국

학습 내용저번 주에 처리하지 못한 입력 및 디스플레이 컴포넌트 관련 내용을 학습하였습니다.  이번 주와 관련하여 피드백 컴포넌트 관련 내용을 학습하였습니다.이번 주 발자국을 작성하고 난 후 네비게이션 컴포넌트와 모드 관련 내용을 학습할 예정입니다.미션 및 실습특히, 이번 주 컴포넌트 실습에 있어서 '티끌 모아 태산'이라는 말이 많이 생각이 났습니다. 여러 컴포넌트를 불러와 인스턴스로 또 하나의 컴포넌트를 조합하는 일이 매우 많았기 때문에, 하나의 컴포넌트가 잘 완성이 되어 있지 않으면 연쇄적으로 영향을 주는 경우가 많았습니다.프로토타입을 피그마에서 사용시 스피너, 스켈레톤을 움직이게 만드는데 활용한 경우는 없었고, 터치로 인한 스크린 전환 같은 UI/UX 흐름에만 활용을 했었는데 관련하여 많은 배움을 얻을 수 있었습니다.사실 개발시에도 라이브러리를 import 하는 식으로 구현하는 경우가 많아서 이번 구현이 처음이었습니다.회고이번 주는 아직 전체 진도를 따라가진 못했지만, 정말 많은 것들을 배울 수 있었던 주였습니다.오늘 밤과 내일 사이에 남은 진도를 따라 잡아 다음 주부터는 여유롭게 지금까지 했었던 것들을 차근차근 확인하며 마무리 할 수 있는 시간을 가지도록 노력할 것입니다.

UX/UI인프런_워밍업_클럽UX/UIFigma디자인시스템볼드UX

hee j

[인프런 워밍업 클럽 3기 풀스택 ] 3주차 발자국

목차다른 페이지에서 데이터를 받아 전달rocoil을 사용하는 방법Supabase에서 maybeSingle()과 single()react-intersection-observeruseInView 함수 사용법useInfiniteQuery 기본 사용법3주차 미션supabase에 컬럼 추가찜 기능 설정하기찜한 영화를 화면 최상단으로 보여주도록 정렬Netflix Clone Project 1. 다른 페이지에서 데이터를 받아 전달search 검색 란은 header에 있고 해당 값을 전달해서 받아 오는 곳은 movie-card-list.tsx 에 있으므로 Recoil(전역 상태 관리 라이브러리)을 사용하여 해당 값을 넘겨준다reccoil도 레이아웃에서 react query와 materia ui를 사용할 수 있게 해준 것처럼 <RecoilRoot>가 있음하지만 recoil은 기본적으로 클라이언트 라이브러리라 별도의 provider를 정의 해준다.(app/config/RecoilProvider.tsx)[rocoil을 사용하는 방법]atom 함수를 사용/utils/recoil/atoms.ts 파일 생성recoil을 사용할 페이지에 atomes.ts에 생성한 search atom을 넣어줌ex) header.tsx const {search, setSerch} = useRecoilState(searchState)ex) movie-card-list.tsxqueryKey에 search 값을 넣어야 search 값이 변경될 때마다 query function이 재호출 됨 const search = useRecoilValue(searchState); const getAllMoviesQuery = useQuery({ queryKey: ["movie",search], queryFn: () => searchMovies({ search }), }); [Supabase에서 maybeSingle()과 single()]single(): 반환되는 데이터가 무조건 한 행이여야 하며, null이 존재 또는 데이터 1개 초과 조회 시 오류 발생maybeSingle(): 반환되는 데이터에 null이 존재해도 오류가 발생하지 않고, 빈 값을 반환함 2. react-intersection-observerreact-intersection-observer 설치npm install react-intersection-observer화면에 이 컴포넌트가 몇 퍼센트 들어왔을 때, inView 값이 true가 됨즉, 특정 요소가 화면에 노출되었는지 감지하는 기능import { useInView } from 'react-intersection-observer'; const [ref, inView, entry] = useInView({ threshold: 0, });ref는 감지할 요소에 연결해야 하는 참조이며, inview는 해당 요소가 화면에 노출되었는지 여부를 나타내는 불리언 값즉, 현재 observe할 엔티티에 대해 레퍼런스를 넣기 위한 값<div ref={ref}> {inView && 'Element is in view!'} </div> ** 우리가 왜 이것을 사용해야 하나?스크롤 맨 아랫부분에 보이지 않는 태그를 넣어서 해당 태그가 보이면 다음 페이지를 가져올 수 있도록 함수를 만들 예정즉, 페이징을 커서 방식으로 개발[useInfiniteQuery]기존에 사용한 useQuery로 무한 스크롤을 구현하기에는 매우 복잡함(다양한 값 필요)useInfiniteQuery는 react-query 라이브러리의 핵심 기능 중 하나입니다. 이를 사용하면 무한 스크롤과 같은 기능을 쉽게 구현할 수 있다.isFetchingNextPage: isLoading 대신 사용fetchNextPage: 다음 페이지hasNextPage: 가지고 있는 다음 페이지const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status, } = useInfiniteQuery('todos', fetchTodos, { getNextPageParam: (lastPage, pages) => lastPage.nextPage, });3. 3주차 미션 [구현 이미지][supabase에 컬럼 추가]찜 기능을 구현할 favorit 이라는 컬럼 추가0이면 false, 1이면 true[찜 기능 설정하기]하트를 클릭하면 하트의 상태가 바뀌면서 데이터 저장import { updateFavorit } from "actions/movieActions"; import Link from "next/link"; import { useState } from "react"; export default function MovieCard({ movie }) { const [isFavorit, setIsFavorit] = useState(movie.favorit); const handleClick = async () => { setIsFavorit(!isFavorit); await updateFavorit(movie.id, movie.favorit); }; return ( <div className="col-span-1 relative"> {isFavorit ? ( <button onClick={handleClick} className={`absolute top-2 right-2 z-20 fa-solid fa-heart text-red-500 text-3xl`} /> ) : ( <button onClick={handleClick} className={`absolute top-2 right-2 z-20 fa-regular fa-heart text-red-500 text-3xl`} /> )} {/* image */} <div> <img src={movie.image_url} className="w-full" /> <Link href={`/movies/${movie.id}`}> <div className="absolute flex items-center justify-center top-0 bottom-0 left-0 right-0 z-10 bg-black opacity-0 hover:opacity-80 transition-opacity duration-300"> <p className="text-xl font-bold text-white">{movie.title}</p> </div> </Link> </div> </div> ); }아이디와 상태 값을 가져와 1 이면 0, 0이면 1로 바꾸어 update해줌 export async function updateFavorit(id, state) { const supabase = await createServerSupabaseClient(); state = state == 1 ? 0 : 1; const { data, error } = await supabase .from("movie") .update({ favorit: state, }) .eq("id", id); handleError(error); return data; }   [찜한 영화를 화면 최상단으로 보여주도록 정렬]favorit 값을 0과 1로 설정했기 때문에 order에서 ascending를 사용해 내림차순으로 정렬export async function searchMovies({ search, page, pageSize }) { const supabase = await createServerSupabaseClient(); const { data, count, error } = await supabase .from("movie") .select("*", { count: "exact" }) .like("title", `%${search}%`) .order("favorit", { ascending: false }) .range((page - 1) * pageSize, page * pageSize - 1); const hasNextPage = count > page * pageSize; favorit 값이 1이면 꽉찬 하트, 0이면 빈 하트로 보여주며, 이미지보다 상단에 띄워 놓아 클릭 시 해당 값이 바뀌도록 설정함export default function MovieCard({ movie }) { return ( <div className="col-span-1 relative"> <button className={`absolute top-2 right-2 z-20 ${ movie.favorit ? "fa-solid fa-heart" : "fa-regular fa-heart" } text-red-500 text-3xl`} /> <div> <img src={movie.image_url} className="w-full" /> <Link href={...}> <div className="absolute flex items-center justify-center top-0 bottom-0 left-0 right-0 z-10 bg-black opacity-0 hover:opacity-80 transition-opacity duration-300"> <p className="text-xl font-bold text-white">{movie.title}</p> </div> </Link> </div> </div> ); } 

풀스택풀스택워밍업

huihui

워밍업 클럽 3기 BE 클린코드&테스트 - 3주차 발자국

학습 내용섹션6. Spring & JPA 기반 테스트Layered Architecture: 관심사의 분리 때문에 레이어를 분리해야 함Persisitence Layer쿼리가 의도대로 작성되었는지 확인쿼리를 구현하는 기술이 바뀌어도 기능의 동작을 보장하도록 테스트를 작성@DataJpaTestBusiness Layer비즈니스 로직 흐름과 트랜잭션 처리 검증트랜잭션을 보장하는지 확인@TransactionalPresentation Layer외부 요청, 파라미터 위주로 검증의존관계를 가짜 객체를 사용해(Mocking) 환경 재현@WebMvcTest, @MockBean, @MockMvc테스트 코드단위 테스트: 작은 코드 단위를 독립적으로 검증하는 테스트통합 테스트: 여러 모듈이 협력하는 기능을 통합적으로 검증하는 테스트  회고이번주는 Layered Architecture 구조의 Layer별로 테스트를 작성해보며 TDD를 적용하는 과정을 배웠다.점점 수업 내용을 이해하고 내 것으로 만들기 어려워지는 것 같다. 워밍업 클럽 시작 전 커리큘럼을 보고 학습양이나 난이도가 만만치 않겠다고 생각하긴 했지만, 직접 수업을 들어보니 내 부족한 점들이 더 잘 보인다. 이번주가 특히나 힘들었는데, 진도를 맞추는 데 급급해 강사님이 라이브코딩하는 것을 보며 코드를 따라치는게 고작이었다. 그래서인지 수업을 다 들어놓고도 내가 제대로 배우고 이해한 게 맞나?라는 의문이 머릿속을 떠나지 않았던 한 주였다. 우선은 완주를 목표로 진도표에 맞춰 학습하고, 일정이 끝난 후 강의를 다시 들으며 개념을 보강하는 과정이 필요할 것 같다.

백엔드

보키

[인프런 워밍업 클럽 3기 - BE/Project] 3주차 회고 발자국 🐾

2주차와 마찬가지로 KPT 회고 프레임워크를 선택해서 작성해보려고 한다! Keep(만족, 지속하고 싶은 부분)이번 3주차는 개인프로젝트에서는 프로젝트쪽에서는 조회 API를 만들었다. API 테스트에서는 mockMVC를 사용하지 않고, 실제 인증 엔드포인트를 호출하고, AccessToken을 받아서 Authorization 헤더에 넣어서 Project 목록을 가져오는 것을 RestClient로 작성했다.DB url은 src단과 test단에서 다른 곳을 바라보게 만들었다. 개인적으로는 컨트롤러->서비스->레포지토리->서비스->컨트롤러 로 흐르는 스택트레이스 요청&응답을 제대로 검증하기위해서는 실제로 사용하는 DB 벤더까지 맞춰야 한다고 생각한다.실제로 배포했다면 AWS 인스턴스로 요청을 날려보면 되겠고! 갠적으로 h2는 학습용으로는 괜찮지만 실제 프로젝트를 할때는 테스트에도 비슷하거나 같은 환경으로 해야한다고 생각한다.강의에서는 ADMIN단을 진행했다. 거의 끝나가고있다!! 좋아! Problem(부족, 아쉬웠던 부분)사실, 이번주에 면접이 있었고, 면접준비도 하느라고 조회과제 테스트코드랑, 금요일까지였던 미션을 완료 못했다.늦게라도 제출을 해서 마무리를 하는 멋쥔 사람이 되려고 노력할 것이다!Try(도전, P에 대한 해결책 등으로 다음번에 보완하거나 시도할 부분)거의 다 온거, 내 프로젝트도 천천히 그리고 차근차근 보완해가며 완성시켜보고 싶다.프론트엔드도 예전에 학습한거를 바탕으로 현업에서 썼던 Vue3대신 React(CRA)로 붙여보고 싶다.테스트코드도 한번 작성해놓으니까 Fixture-Monkey나 Kotest, mockk 사용법이 새록새록 기억도 나고 새로 배우는 것도 있고 그렇다.앞으로 쭉 잘하면 된다!! 나 자신을 응원하자!!!

웹 개발인프런워밍업클럽회고KPT3주차백엔드프로젝트KotlinSpringboot

강신욱

[인프런 워밍업 클럽 Full Stack 3기] 스터디 3주차

강의 메모 정리함수와 메서드maybeSingle() vs single()maybeSingle(): 리스트가 아닌 단일 데이터를 가져오는 함수로, null을 반환할 수 있음single(): 단일 데이터를 가져오지만, null일 경우 에러 발생따라서 null 가능성이 있을 때는 maybeSingle()을 사용하는 것이 안전함메타 데이터 처리는 서버 단에서 처리하는 것이 효율적SEO 작업SEO(검색 엔진 최적화) 구성 요소타이틀과 설명(Description)카카오톡 등으로 URL 공유 시 표시되는 정보검색 엔진에 노출되는 중요 정보OG Image메타 태그의 일종소셜 미디어에서 링크 공유 시 표시되는 이미지카카오톡 등에서 웹사이트 공유 시 나타나는 이미지React 관련 기능react-intersection-observer 라이브러리useInView 훅컴포넌트가 화면에 보이는지 여부를 감지threshold 값을 통해 화면에 얼마나 보여야 감지할지 설정 가능반환값: [ref, inView, entry]ref 활용법특정 DOM 요소(예: div)에 ref를 연결하면 해당 요소가 화면에 보이는지 감지 가능주요 사용 사례: 무한 스크롤페이지 하단에 보이지 않는 태그를 배치해당 태그가 화면에 보이는 순간 다음 페이지 데이터 로드useInfiniteQueryReact Query에서 제공하는 무한 스크롤 구현을 위한 훅일반 useQuery보다 무한 스크롤 구현에 적합주요 반환값:data: 모든 페이지 데이터를 포함fetchNextPage: 다음 페이지 데이터 요청 함수hasNextPage: 다음 페이지 존재 여부isFetchingNextPage: 다음 페이지 로딩 상태(isLoading 대체 가능)status: 전체 쿼리 상태사용 시 현재 페이지와 다음 페이지를 합친 전체 상태 값을 data가 관리함미션 수행이번 미션에서는 실습에서 구현한 넷플릭스 클론에서 '찜' 기능을 추가하는 것이 목표였다.

채널톡 아이콘