블로그
전체 102025. 03. 30.
0
워밍업 클럽 3기 BE 클린코드 4주차 발자국
4주가 정말 빠르게 지나가서 벌써 스터디의 마지막 주차가 지나갔다. 가독성좋은 코드를 작성하는 것에 대해, 테스트코드에 대해 막연한 관심만 가지고, 알던 사용법과 지식 안에서 머무르던 나였는데 이번 스터디와 우빈님의 강의를 통해 지식과 경험의 확장이 시작된거 같아 만족한다. 또한 우빈님께서 매번 정말 열정적으로 중간점검 라이브를 진행해주셨는데 그 정성과 진심이 감동이었다. 강의 중 주제에 관련된 내용들 뿐만 아니라 스프링관련 이라던가 배경지식으로 필요한 여러 개념들을 설명해주시는 방식들도 좋았고 여러모로 배운 것이 정말 많은 강의였다. 추후에 우빈님의 강의도 스터디도 또 열린다면 참여할 의향이 크다. 스터디 참여를 통해서 혼자 강의를 수강했다면 느슨해져서 집중도 있게 학습하지 못했을 수 있는데, 스터디 참여를 하며 미션 수행, 회고 작성들을 기한내에 하기 위해서라도 학습에 집중도가 높아졌던 거 같고 미션들을 해결하는 과정이 학습에 많은 도움이 되었다. 또한 중간점검 라이브를 통해 강사인 우빈님께서 직접 소통하며 질의응답시간과 코드리뷰 등을 해주셔서 정말 스터디를 안할 이유가 없다고 생각한다. 이번 인프런 스터디는 값진 경험이었고 기대이상이었다. 다음에도 다른 스터디들에도 참여해보려한다. 강의 출처 :Practical Testing: 실용적인 테스트 가이드
2025. 03. 27.
0
인프런 워밍업 스터디 클럽 3기 백엔드 Day 18미션
1. @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이를 한번 정리해 봅시다. @Mock 기본적인 목(mock) 객체를 생성하고, 가짜 객체로 특정 메서드의 동작을 지정할 수 있다. 단위 테스트에서 실제 로직 대신 더미 데이터를 반환하는 데 사용한다.@MockBean Spring 컨텍스트 내에 목 객체를 빈으로 등록한다. 애플리케이션의 특정 빈을 대체하여 통합 테스트를 진행할 수 있다.@Spy 실제 객체를 생성하면서 일부 메서드만 모킹할 수 있다. 객체의 전체 동작을 유지하면서 특정 부분만 스텁으로 대체할 수 있다.@SpyBean Spring 컨텍스트에서 실제 빈을 스파이로 등록한다. 특정 Spring 빈의 일부 메서드를 테스트용으로 대체할 수 있다.@InjectMocks 테스트 대상 클래스의 인스턴스를 생성하고, 의존 객체들을 자동으로 주입한다. 외부 의존성 없이 독립적인 테스트를 가능하게 한다. 2. 아래 3개의 테스트가 있습니다. 내용을 살펴보고, 각 항목을 @BeforeEach, given절, when절에 배치한다면 어떻게 배치하고 싶으신가요? (@BeforeEach에 올라간 내용은 공통 항목으로 합칠 수 있습니다. ex. 1-1과 2-1을 하나로 합쳐서 @BeforeEach에 배치)@BeforeEach void setUp() { // 사용자 생성에 필요한 내용 준비 // 사용자 생성 // 게시물 생성에 필요한 내용 준비 // 게시물 생성 } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given // 댓글 생성에 필요한 내용 준비 // when // 댓글 생성 // then // 댓글이 정상적으로 작성되었는지 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given // 댓글 생성에 필요한 내용 준비 // 댓글 생성 // 댓글 수정에 필요한 내용 준비 // when // 댓글 수정 // then // 댓글이 정상적으로 수정되었는지 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given // 사용자1 생성 // 사용자2 생성 // 사용자1의 게시물 생성 // 사용자1의 댓글 생성 // 사용자2가 댓글 수정 시도에 필요한 내용 준비 // when // 사용자2가 댓글 수정 시도 // then // 댓글 수정이 실패했는지 검증 } 강의 출처 - Practical Testing: 실용적인 테스트 가이드
2025. 03. 25.
0
[인프런 워밍업 클럽 3기] BE 테스트 코드 - Day16 미션
레이어별 특징 & 어떻게 테스트 하면 좋을지 Persistence LayerData Access의 역할. (비즈니스 가공 로직없이 순수하게) Data CRUD에만 집중한 레이어. SQL이 제대로 동작하는지.스프링을 띄우는 통합테스트 이지만 Persistence Layer 로직만 수행되기에 단위테스트의 성격을 가진다.test용 profile 설정과 data 주입이 되도록 설정 해두면 편하다. Business LayerPersistence Layer와 상호 작용을 통해 비즈니스 로직을 수행.트랜잭션이 보장되어야 함. Persistence Layer를 주입하여 테스트를 진행.비즈니스 로직에 대해 단위 테스트를 작성해도 좋을 거 같다. Presentation Layer외부세계에서 넘어오는 값들에 대하여 최소한의 검증은 마치고 유효한 값들을 비즈니스 레이어로 넘겨줘야 한다.컨트롤러 테스트를 할때는 하위의 business layer 와 persistence layer는 모킹처리를 하고, 단위 테스트 느낌으로 진행한다.
2025. 03. 23.
0
워밍업 클럽 3기 BE 클린코드 3주차 발자국
금주 부터 본격적으로 테스트코드 강의 부분 학습을 시작하였다.강의를 들으며 개인적으로 조금 더 와닿게 느꼈거나 정리하고 싶었던 포인트들만 정리해보았다.우선 가장 첫번째, 가장 중요한 것은 WHY.귀찮고, 시간도 많이 쏟아야하는, 비용이 발생하는 테스트코드 작성. 왜 테스트를 해야하는가.(테스트 코드 없는 상황) 사람이 직접 프로덕션으로 나온 프로그램을 테스트 하면?커버할 수 없는 영역 발생경험과 감에 의존늦은 피드백유지보수 어려움소프트웨어 신뢰 down작은 프로젝트면 티가 안날 수도 있지만, 프로젝트가 커지고 기능이 늘어나고 고도화 되면 감당할 수 없게 된다.그래서 테스트 코드를 통해서 다음을 얻고자 하는것.빠른 피드백자동화안정감 TDDTDD의 장점 : 특정 케이스(해피케이스)만 검증할 가능성을 막아준다.TDD 하면 처음 부터 메서드 설계가 테스트 하기 좋게 된다.TDD는 관점의 변화를 준다.Test가 구현부 검증역할을 위한 보조 수단 ---> Test는 구현부와 상호 작용하며 발전TDD는 클라이언트 관점에서의 피드백을 느낄 수 있다. (메서드/객체를 사용하는 사람 관점에서)TDD가 만능은 아닌데, 적절한 상황에 사용할 수 있어야 한다. 그럴려면 계속 연습해보고 시도해봐야한다. 테스트는 문서다테스트 코드는 프로덕션 코드라는 공유자산에 대해서 잘 설명해주는 문서 역할도 한다. Repository 테스트 왜 해야하는가?JPA로 어떤 쿼리 날라가는지 명확히 아는데도 필요한가?제대로된 쿼리 날라가는 것 보장하기 위함미래에 어떤 형태로 변형 될지 모른다. Day11 미션 @DisplayName("가격과 할인률에 따른 총가격이 정상적으로 계산되어야 한다. 라커 사용 안하는 경우.") @ParameterizedTest @CsvSource({ "100, 0, 100", // price 100, discountRate 0, expectedTotalPrice 100 "200, 0, 200", // price 200, discountRate 0, expectedTotalPrice 200 "300, 0.1, 270", // price 300, discountRate 10, expectedTotalPrice 270 "150, 0.5, 75" // price 150, discountRate 50, expectedTotalPrice 75 }) void totalPriceTest(int price, double discountRate, int expectedTotalPrice) { // given StudyCafeSeatPass pass = StudyCafeSeatPass.of(StudyCafePassType.FIXED, 1, price, discountRate); StudyCafePassOrder order = StudyCafePassOrder.of(pass, null); // when int totalPrice = order.getTotalPrice(); // then assertThat(totalPrice).isEqualTo(expectedTotalPrice); } @DisplayName("가격과 할인률에 따른 총가격이 정상적으로 계산되어야 한다. 라커 사용 하는 경우.") @ParameterizedTest @CsvSource({ "100, 0, 10, 110", // price 100, discountRate 0, lockerPrice 10, expectedTotalPrice 110 "200, 0, 10, 210", // price 200, discountRate 0, lockerPrice 10, expectedTotalPrice 210 "300, 0.1, 10, 280", // price 300, discountRate 10, lockerPrice 10, expectedTotalPrice 280 "150, 0.5, 10, 85" // price 150, discountRate 50, lockerPrice 10, expectedTotalPrice 85 }) void totalPriceTestWithLocker(int price, double discountRate, int lockerPrice, int expectedTotalPrice) { // given StudyCafeSeatPass pass = StudyCafeSeatPass.of(StudyCafePassType.FIXED, 1, price, discountRate); StudyCafePassOrder order = StudyCafePassOrder.of(pass, StudyCafeLockerPass.of(StudyCafePassType.FIXED, 1, lockerPrice)); // when int totalPrice = order.getTotalPrice(); // then assertThat(totalPrice).isEqualTo(expectedTotalPrice); }@CsvSource 와 @ParmeterizedTest 를 사용해보았다. InputHandlerTest@DisplayName("사용자 입력값에 따라 알맞은 이용권이 선택되어야 한다.") @Test void testGetPassTypeSelectingUserAction_HOURLY() { //given InputHandler inputHandler = new InputHandler(); String input = "1\n"; System.setIn(new ByteArrayInputStream(input.getBytes())); // when StudyCafePassType passType = inputHandler.getPassTypeSelectingUserAction(); // then assertThat(passType).isEqualTo(StudyCafePassType.HOURLY); } @DisplayName("사용자 입력값에 따라 알맞은 이용권이 선택되어야 한다.") @Test void testGetPassTypeSelectingUserAction_WEEKLY() { //given String input = "2\n"; System.setIn(new ByteArrayInputStream(input.getBytes())); InputHandler inputHandler = new InputHandler(); // when StudyCafePassType passType = inputHandler.getPassTypeSelectingUserAction(); // then assertThat(passType).isEqualTo(StudyCafePassType.WEEKLY); } @DisplayName("사용자 입력값에 따라 알맞은 이용권이 선택되어야 한다.") @Test void testGetPassTypeSelectingUserAction_FIXED() { //given String input = "3\n"; System.setIn(new ByteArrayInputStream(input.getBytes())); InputHandler inputHandler = new InputHandler(); // when StudyCafePassType passType = inputHandler.getPassTypeSelectingUserAction(); // then assertThat(passType).isEqualTo(StudyCafePassType.FIXED); } @DisplayName("사용자 입력값에 따라 알맞은 이용권이 선택되어야 한다.") @Test void testGetPassTypeSelectingUserAction_INVALID() { //given String input = "4\n"; System.setIn(new ByteArrayInputStream(input.getBytes())); InputHandler inputHandler = new InputHandler(); // when then assertThatThrownBy(inputHandler::getPassTypeSelectingUserAction) .isInstanceOf(AppException.class); }InputHandler에 static으로 선언되어 있는 Scanner를 공유한다. 위의 네개의 테스트를 각각 실행하면 성공한다. 하지만 System.setIn을 사용해 입력 스트림을 변경하고 Scanner가 이 스트림을 사용Scanner는 입력 스트림을 읽을 때, 스트림을 소모. 한 번 사용한 스트림은 소모되므로, 같은 스트림을 여러 테스트에서 공유하면 이후 테스트에서 입력을 읽어들이지 못해 실패따라서, 각 테스트에서 새로운 입력 스트림을 주입하더라도, 이전 테스트에서 스트림을 사용해버렸기 때문에 이후 테스트들은 스트림을 사용할 수 없게 되는 문제가 발생 하여 순차적으로 실행하면 첫번째로 실행되는 테스트만 통과되고 그 이후 테스트 들에서는 오류 발생으로 실패한다. 여러 다른 방법들을 찾아봤지만, 결국은 이 부분을 테스트 할 수 있게 만들려면 InputHandler가 scanner를 주입받도록 구조를 바꿔야 하는 거 같았다. 그런데 테스트를 위해 구조를 바꾸는 것이 타당한가 생각이 들고 테스트를 위해 프로덕션 코드에 뭔가를 추가하거나 수정하는게 어느 정도 선까지 괜찮은 것인지 기준을 모르겠다. 이 부분을 학습해봐야겠다. 자료, 강의 출처 : https://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
2025. 03. 23.
1
[인프런 워밍업 클럽 3기] CS - 3주차 미션 & 발자국
운영체제1. 메모리의 종류는 어떤것들이 있나요? 각 메모리의 특징도 함께 적어주세요. 레지스터가장 빠른 메모리CPU 내에 존재휘발성 메모리 (컴퓨터 꺼지면 데이터 사라짐)CPU 구분시 말하는 32/64bit 는 레지스터의 크기를 말하는 것.CPU는 계산시 메인메모리 (RAM)에 있는 값을 레지스터로 가져와서 계산하고 그 결과를 다시 메인메모리에 저장시킴 메인메모리레지스터와 메인메모리 사이에는 휘발성 메모리인 캐시 가 있음.메인메모리는 너무 느리기 때문에 필요할 것 같은 데이터를 미리 캐시로 가져옴.캐시는 성능의 이유로 여러개를 둠.만약 CPU가 값을 요청해 레지스터로 옮겨야 할 때 L1캐시(가장빠름) -> L2캐시 (L3..) -> 메인메모리순서로 값이 있는지 확인함.===메인 메모리는 실제 운영체제와 다른 프로세스들이 올라가는 공간.전원 공급 안되면 데이터가 지워지기에 휘발성 메모리HDD, SDD 보다 속도가 빠르지만 비싸기 때문에 데이터 저장보단 실행 중인 프로그램만 올림. 보조저장장치 (HDD, SSD)비휘발성 메모리.가격이 쌈. 2. 사용자 프로세스가 메모리의 운영체제 영역에 침범하지 못하도록 만든 레지스터는 어떤 레지스터일까요?경계 레지스터. 메모리 관리자가 사용자 프로세스가 경계 레지스터의 값을 벗어났는지 감시하고, 만약 벗어난다면 해당 프로세스를 종료시킨다. 3. 메모리 할당 방식에서 가변 분할 방식과 고정 분할 방식의 장단점은 뭔가요?가변 분할 방식장점 : 메모리의 연속된 공간에 할당 되기에 더 크게 할당되어 낭비되는 공간인 내부 단편화가 적다.단점 : 한 프로세스가 분산되어 할당되므로 메모리 공간이 남아있어도 추가적 할당을 할 수 없는 외부 다편화가 발생한다. 고정 분할 방식장점 : 구현이 간단하고 오버헤드가 적다.단점 : 한 프로세스보다 더 큰 메모리가 할당 될 수 있고 이로 인해 특정 메모리보다 더 큰 공간이 할당되는 내부 단편화가 발생할 수 있다. 4. CPU 사용률을 올리기 위해 멀티프로그래밍을 올렸지만 스왑이 더 많이 이루어져 CPU 사용률이 0%에 가까워 지는 것을 뭐라고 할까요?스레싱.물리 메모리의 크기가 부족해서 일어나는 현상. SWAP시의 컨텍스트 스위칭으로 인해 발생한다.5. HDD나 SSD는 컴퓨터를 실행시키는데 꼭 필요한 걸까요? 이유를 함께 적어주세요.HDD나 SSD가 "꼭" 필요한 건 아니다. 운영체제를 ROM, USB 또는 외부 저장장치에서 부팅하면 HDD나 SSD 없이도 컴퓨터를 실행할 수 있다. 하지만 대부분의 경우 컴퓨터가 안정적으로 동작하려면 운영체제를 저장할 영구 저장장치가 필요해서, 일반적으로 HDD나 SSD가 필수로 여겨진다.메모리는 휘발성이라 전원이 꺼지면 데이터가 사라진다. 그래서 데이터를 영구적으로 저장할 수 있는 HDD나 SSD 같은 장치가 중요하다. 6. 파일을 삭제해도 포렌식으로 파일을 복구할 수 있는 이유가 무엇일까요?OS의 파일 시스템에는 파일 관리를 위해 빈 공간을 모아 두는 Free block list가 있다. 특정 파일을 삭제하면, 파일 시스템은 파일의 모든 정보를 지우는 대신 파일 테이블의 헤더만 삭제하고, 그 파일이 차지하던 블록을 free block list에 추가한다.이렇게 되면 사용자 입장에서는 파일이 삭제된 것처럼 보인다. 하지만 실제로는 사용된 블록의 데이터가 그대로 남아 있기 때문에, 이후 포렌식을 통해 해당 데이터를 복구할 수 있다. 자료구조와 알고리즘1. 지금까지 배운 5개의 정렬 알고리즘의 장단점과 시간 복잡도를 적어주세요. 버블 정렬시간 복잡도: O(n²)장점: 구현이 쉽고 안정 정렬단점: 매우 비효율적 선택 정렬시간 복잡도: O(n²)장점: 데이터 이동 횟수 최소단점: 안정 정렬이 아님, 느림 삽입 정렬시간 복잡도: O(n²)장점: 거의 정렬된 데이터에 빠름 (O(n))단점: 역순이면 O(n²) 병합 정렬시간 복잡도: O(n log n)장점: 항상 빠르고 안정 정렬단점: 추가 메모리 필요 (O(n)) 퀵 정렬시간 복잡도: O(n log n) (최악 O(n²))장점: 평균적으로 가장 빠름, 메모리 적게 사용단점: 최악의 경우 O(n²) 2. 메모리가 부족한 시스템에서 어떤 문제를 해결하는데 재귀로 쉽게 구현이 가능할 것 같습니다. 여러분이라면 메모이제이션과 타뷸레이션 중 어떤 걸 이용하실 건가요? 이유를 함께 적어주세요. 재귀 호출이 과도하게 깊어지면 스택 메모리 한계를 초과하는 스택 오버플로우가 발생할 수 있습니다. 이 문제를 해결하기 위해 타뷸레이션 방식을 활용하면 반복문과 배열을 사용해 중간 계산 결과를 저장함으로써 메모리를 더 효율적으로 관리하고 성능도 향상시킬 수 있습니다. 짧은 기간이었지만 이번 워밍업 클럽 스터디에 참가하면서 미뤄왔던 CS 학습을 시작하고 집중도 있게 할 수 있었다. 쉽고 재밌게 잘 설명해주시는 강의 덕분에 부담감 적게 흥미를 유지하며 학습해나갈 수 있었고, 강사님께서 직접 내주시는 미션을 수행하며 더 효과적인 학습을 할 수 있었다. 이번 학습을 시작으로 앞으로도 꾸준히 흥미를 갖고 CS 관련 학습을 이어가야겠다.
2025. 03. 16.
0
워밍업 클럽 3기 BE 클린코드 2주차 발자국
이번주는 중간점검 시간에 우빈님의 러너분들 코드리뷰시간이 있었다. 나는 원하는 만큼 충분히 리팩토링을 진행하지 못해서 코드리뷰 신청을 하진 않았지만 다른 분들의 코드리뷰 내용을 보며 정말 많은 인사이트를 얻을 수 있었다. 무려 두시간 가까이 라이브를 진행해주신 우빈님께 정말 감사드린다. 코드리뷰를 받은 러너분 중 나와 유사한 코드를 작성하신 분이 계셨다. 카페이용권 Enum의 필드로 입력값 (1, 2, 3)을 두고 관리하는 코드를 작성 하였는데, 사실 코드를 작성 하면서도 긴가민가 했다.이 부분에 대해 우빈님께서 input과 관련된 내용에 대해 가장 핵심적인 model이 의존성을 가지면 안된다고 하셨다.지금은 1, 2, 3 의 입력값으로 받지만 a, b, c 등 바뀔 수 있고, 바뀐 경우 input과 관련된 사항을 model에 가서 바꾸는 건 이상한 것이다. FileHandler 부분에서도 나와 유사한 경우가 있었다. 코드 리뷰를 받으신 분은 FileHandler가 사용되는 클래스에 상수로 이를 선언하여 초기에 파일을 모두 읽어 오도록 작성하셨다.나는 FileHandler 생성자 내에 파일을 불러오는 메서드를 두어 객체 생성시 파일을 모두 읽어 오도록 하였다.우빈님께서 초기에 파일을 모두 불러온다는 접근은 좋은데, 이런 경우 프로그램 실행 중 해당 정보를 어떻게 생성/수정/삭제 할 것인지 추가적인 고민이 필요하다고 하셨다. 코드리뷰 외에 강의 내용 중 내 기존 사고를 벗어나게 된 기억에 남는 점은 추상화 수준을 맞추는 것이 메서드 분리의 기준이 되기에 아무리 작은 코드량이라도 메서드 분리를 할 수 있다는 것이었다.depth 줄이기는 보이는 depth 줄이기가 절대 진리가 아니고 보다 중요한건 사고의 depth 줄이기! 2주가 정말 빠르게 지나갔는데 처음 마음 만큼 더 열심히 하지 못했다는 아쉬움이 남는다. 벌써 절반이 지났으니, 남은 절반은 후회 없이 열심히 해야겠다.