블로그

박준형

[워밍업 클럽 4기 백엔드] 2주차 발자국

2주차에 배운 것들 📌자동화된 테스트를 만들자지금까지 개발을 하면서 테스트 코드를 만들어서 검증을 한적이 한번도 없었다. 강의에서도 언급된 콘솔을 통한 검증 방법을 통해 원하는 값이 잘 나오는지 직접 눈으로 확인했다.@Test void add() { // 옳바른 테스트일까? CafeKiosk cafeKiosk = new CafeKiosk(); cafeKiosk.add(new Americano()); System.out.println(">>> 담긴 음료 수 : " + cafeKiosk.getBeverages().size()); System.out.println(">>> 담긴 음료 : " + cafeKiosk.getBeverages().get(0).getName()); }이 방식에는 두 가지 문제점이 있었다.테스트 최종 단계에서 사람이 개입된다.다른 사람이 테스트 코드를 봤을 때, 무엇을 검증하고 어떤 것이 맞는 상황인지 틀린 상황인지 알 수 없다는 것이다.이런 문제를 해결해주는 것이 바로 자동화된 테스트이다. 위 코드는 사용자가 음료를 추가하면 키오스크에 정상적으로 담기는지 확인하는 코드로 단위 테스트를 통해 검증할 수 있다. JUnit5와 AssertJ를 사용하여 자동 테스트를 만들어보자.단위 테스트 : 작은 코드 단위(클래스 or 메서드)를 독립적으로 검증하는 테스트로 검증 속도가 빠르고 안정적이다.   @Test void add() { CafeKiosk cafeKiosk = new CafeKiosk(); cafeKiosk.add(new Americano()); assertThat(cafeKiosk.getBeverages()).hasSize(1); assertThat(cafeKiosk.getBeverages().get(0).getName()).isEqualTo("아메리카노"); }이렇게 테스트 코드를 구성하게 되면 테스트 최종 단계에서 사람의 개입 없이 검증할 수 있고, 기능이 정상적으로 동작했을 때의 결과를 통해 기능을 검증하므로 어떤 것을 검증하는지 명확하다.📌 테스트하기 어려운 영역을 분리하자public Order createOrder() { LocalTime currentTime = LocalDateTime.now().toLocalTime(); if(currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)) { throw new IllegalArgumentException("주문 시간이 아닙니다. 관리자에게 문의하세요."); } return new Order(beverages); }이 코드는 Order 객체를 생성하는 코드이다. 코드 내부에서 현재 시간을 기준으로 주문 시간이 아닌 경우 예외를 던지도록 해두었다. 이 코드를 단위 테스트를 통해 검증할 수 있을까?그럴 수 없다. 테스트 코드를 실행하는 시점에 따라서 결과가 계속 달라지기 때문에 테스트의 정합성을 보장할 수 없기 때문이다. 그렇다면 어떻게 테스트를 해야할까?💡시간을 매개변수를 통해서 외부에서 받으면 된다!public Order createOrder(LocalDateTime currentDateTime) { LocalTime currentTime = currentDateTime.toLocalTime(); if(currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)) { throw new IllegalArgumentException("주문 시간이 아닙니다. 관리자에게 문의하세요."); } return new Order(currentDateTime, beverages); }@Test void createOrderWithCurrentTime() { CafeKiosk cafeKiosk = new CafeKiosk(); Americano americano = new Americano(); cafeKiosk.add(americano); // 시작 시간의 경계값 Order order = cafeKiosk.createOrder(LocalDateTime.of(2025, 5, 28, 10, 0)); assertThat(order.getBeverages()).hasSize(1); assertThat(order.getBeverages().get(0).getName()).isEqualTo("아메리카노"); }이 방식을 통해 시간을 외부에서 입력 받으면 테스트를 실행하는 시점과 관계 없이 우리가 원하는 값으로 테스트를 진행할 수 있게 된다. 게다가 영업 시작 시간(10시), 영업 종료 시간(22시)과 같은 경계값 테스트도 수행할 수 있어 유연한 테스트 코드를 작성할 수 있게 된다.경계값 : 범위(이상, 이하, 초과, 미만), 구간, 날짜 등물건 5개 이상 구매 시 할인 적용물건을 5개로 설정하고, 할인이 적용되는지 확인물건을 4개로 설정하고, 할인이 적용되지 않는지 확인그렇다면 구현 단계에서 이런 부분까지 신경쓸 수 있을까? TDD를 통해 기능 구현 전 테스트를 먼저 작성하여 테스트가 어려운 영역을 인지할 수 있고, 테스트에 유연한 코드를 작성할 수 있다!📌 TDD를 통해 테스트에 유연한 코드를 작성하자TDD란? 프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 방법론🔄TDD 사이클실패하는 테스트를 작성하여 빨간불 보기 -> 테스트가 아직 성공하지 않았다고 명시적으로 표현주먹구구식으로 엉터리 구현을 해서라도 테스트를 통과하는 기능 구현하기 -> '기능 구현'에 초점을 두고 코드 작성초록불을 유지하면서 구현 코드 개선하기 -> 기능에 대한 검증은 끝났으므로 그 틀안에서 '읽기 좋은 코드'에 초점을 두고 리팩토링 🚨선 기능 구현, 후 테스트 작성의 문제점테스트 코드 자체를 까먹고 작성하지 않을 수 있음특정 테스트 케이스(해피 케이스)만 검증할 가능성잘못된 구현을 다소 늦게 발견할 가능성 💡 TDD는 빠른 피드백을 받을 수 있다!테스트에 유연하며 유지보수가 쉬운 코드로 구현할 수 있게 한다.위에서 언급한 테스트하기 어려운 영역을 미리 생각하여 구현 단계에서 해당 영역을 의식할 수 있음발견하기 어려운 엣지 케이스를 놓치지 않게 해준다.구현에 대한 빠른 피드백이 가능하다.과감한 리팩토링이 가능하다.초록불을 보았으면, 기능에 대한 검증이 된 테스트 코드로 인해 리팩토링 중 잘못된 구현을 해도 리팩토링이 잘못되고 있다는 것을 빠르게 알 수 있음📌 테스트는 문서다!내가 작성한 코드와 문서가 다른 팀원에게는 어떻게 비춰질지 생각하면서 작성하는 것이 중요하다!프로덕션 기능을 설명한다.다양한 테스트 케이스를 통해 프로덕션 코드를 이해하는 시각과 관점을 보완한다.어느 한 사람이 과거에 경험했던 고민의 결과물을 팀 차원으로 승격시켜서, 모두의 자산으로 공유할 수 있다. 그렇다면 이 문서를 어떻게 잘 작성할 수 있을까❓ 💡@DisplayName 을 섬세하게 작성하자음료 1개 추가 테스트 ❌ -> ~'테스트' 를 지양음료를 1개 추가할 수 있다. ⭕ -> 명사의 나열보다는 문장으로 구성음료를 1개 추가하면 주문 목록에 담긴다. ⭕ -> 테스트 행위에 대한 결과까지 기술하면 다른 팀원 또는 미래의 내가 봤을 때 이해하기 쉬워짐  특정 시간 이전에 주문을 생성하면 실패한다. ❌ -> 보는 사람 입장에서 '특정 시간'이 언제인지 불분명함영업 시작 시간 이전에는 주문을 생성할 수 없다. ⭕ -> 도메인 용어인 '영업 시작 시간'을 사용하여 한층 추상화된 내용을 담아 테스트를 진행할 명확한 시간대를 알 수 있게됨메서드 자체의 관점보다 도메인 정책에 관점을 두고 작성테스트 현상을 중점으로 기술하기 보다, "어떤 행동이 어떤 결과를 만든다" 식의 표현으로 작성 💡 BDD 스타일로 작성하자BDD(Behavior Driven Development)는 TDD에서 파생된 개발 방법으로 함수 단위의 테스트에 집중하기보다, 시나리오에 기반한 테스트케이스 자체에 집중하여 테스트하는 방법을 말한다. 이 방법은 개발자가 아닌 사람이 봐도 이해할 수 있을 정도의 추상화 수준을 권장한다.Given : 시나리오 진행에 필요한 모든 준비 과정 (객체, 값, 상태 등) → 어떤 환경에서When : 시나리오 행동 진행 → 어떤 행동을 진행했을 때Then : 시나리오 진행에 대한 결과 명시, 검증 → 어떤 상태 변화가 일어난다'음료를 1개 추가하면 주문 목록에 담긴다' 테스트를 BDD 스타일로 구성해보자@Test @DisplayName("음료 1개 추가하면 주문 목록에 담긴다.") void add() { // given CafeKiosk cafeKiosk = new CafeKiosk(); Americano americano = new Americano(); // when cafeKiosk.add(americano); // then assertThat(cafeKiosk.getBeverages()).hasSize(1); assertThat(cafeKiosk.getBeverages().get(0).getName()).isEqualTo("아메리카노"); }✅ 이 방법을 통해 @DisplayName 을 명확하게 작성할 수 있게 된다!미션을 통해 배운 것들📌 Day 7여유롭게 리팩토링을 진행하기 위해서 진도를 미리 나간 후 3일 동안 리팩토링을 진행했다. 처음 시작했을 때 어떤 것부터 리팩토링을 해야할지 막막했었다. 그리고 처음부터 '완벽한 리팩토링'을 해야한다고 생각했기 때문에 고려할 요소들을 한번에 적용하려고 했다. 1시간 동안 어떻게 할지 생각만 했고, 생각만 하고 있으면 아무것도 안되기에 한 가지 요소만 집중해서 차근 차근 바꿔나갔다.한 가지 요소에만 집중하니까 확실히 수월하게 리팩토링을 진행할 수 있었다. 한 가지 요소를 해결하니 전에는 보이지 않던 문제들이 보이기 시작했고, 다른 요소들을 적용할 수 있는 방법들이 떠올랐다. 이를 통해 한 번에 해결하려는 욕심을 버리고, 단계적으로 나아가는 방법에 대해서 깨닫게 되었다.그리고 강의를 통해 이론을 배우고, 코드를 따라 치는 것만 해서는 내용을 제대로 '체득'하는 것이 어렵다는 것을 알게 되었다. 강의를 수강하고, 이제부터는 읽기 좋은 코드를 작성할 수 있을 것 같다는 자신감이 생겼는데 직접 코드에 적용시키려니 잘 되지 않았다. '읽기 좋은 코드를 작성하는 능력'은 스스로 생각하고, 많은 시행착오를 거쳐야 비로소 얻을 수 있는 것이구나 깨닫게 되었다.📌 중간 점검중간 점검에는 다른 사람들의 코드와 고민했던 점을 볼 수 있어서 좋았다. 같은 코드, 같은 언어, 같은 기능을 리팩토링 하는데도 접근 방식과 구현 방법이 다르고, 내가 생각하지 못한 것들을 보고 많이 배울 수 있었다. 그 중에서 코치님께서 좋은 접근 방법이라고 하신 것들은 따로 작성해두었다.그리고 직접 해주신 코드 리뷰 덕분에 내가 작성한 코드가 다른 사람의 관점에서는 어떻게 보이는지 알 수 있었다. 잘 한 부분과 부족한 부분을 알려주셔서 잘 한 부분은 더 발전시키고, 부족한 부분은 보완하는 과정을 통해 더 성장할 수 있는 좋은 기회였다고 생각한다.  출처: Readable Code: 읽기 좋은 코드를 작성하는 사고법

백엔드워밍업클럽4기백엔드2주차발자국

eus247

인프런 워밍업 클럽 스터디 1기 FE - 2주차 발자국

리액트 강의 정리(섹션1~5)Section 1컴포넌트- 리액트로 만들어진 앱을 이루는 최소한의 단위(함수형/클래스 컴포넌트)가상돔- 인터렉션이 많은 웹의 경우 불필요한 DOM을 조작하는 비용이 큰 문제를 해결하기 위해 나오게 됨- 데이터가 바뀌면 가상돔에 렌더링이 되고 이전에 생긴 가상돔과 비교해서 바뀐 부분만 실제 돔에 적용시켜 줌 Section 2SPA Single Page Application- 웹 사이트의 전체 페이지를 하나의 페이지에 담아 동적으로 화면을 바꿔가며 표현한 것- HTML5 History API를 사용하여 페이지 전환 해줌JSX Javascript Syntax eXtension - 자바스크립트, HTML 구조 같이 사용할 수 있음 → 기본 UI에 데이터가 변하는 것을 쉽게 구현할 수 있음 - createElement 쉽게 사용하기 위해 사용React State컴포넌트의 렌더링 결과물에 영향을 주는 데이터를 갖고 있는 객체 Section 3React Hooks- class없이 state를 사용할 수 있는 기능- useAuth(), useState() ...Propsproperties상속하는 부모 컴포넌트에서 자식 컴포넌트에 데이터 등을 전달하는 방법읽기 전용, 자식 컴포넌트 입장에서는 변하지 않음변하게하려면 부모 컴포넌트에서 state 변경해야 함State해당 컴포넌트 내부에서 데이터 전달변경 가능state가 변하면 re-render 됨구조 분해 할당 Destructing → 클린코드 목적- 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JS 표현식// 구조 분해 할당 function buildAnimal(animalData){ let {accessory, animal, color, hairType} = animalData; ... } // 깊게 들어간 객체 구조 분해 할당 let {address:{zipcode, street, number}} = person;React.memo- React 컴포넌트를 메모이제이션하여 불필요한 리렌더링을 방지하는 데 사용 - 컴포넌트의 props가 변경되지 않으면 이전에 렌더링된 결과를 재사용useCallback- 동일한 콜백 함수가 계속 재생성되는 것을 방지하고, 의존성이 변경되지 않으면 이전에 생성된 콜백 함수를 재사용useMemo- compute함수에 넘겨주는 a,b 값이 이전과 동일하다면 컴포넌트가 리렌더링 되더라도 연산을 다시 하지 않고 이전 렌더링 때 저장해주었던 값을 재활용하게 됨Section4,5Axios- http 비동기 통신 라이브러리, promise api 활용- 인스턴스화 하여 사용React 애플리케이션에서 라우팅을 관리하기 위한 도구라우팅 컴포넌트 및 구조 관련- 중첩 라우팅 : 라우팅 시스템에서 하위 경로에 대한 라우팅을 부모 경로의 컴포넌트 내에서 처리하는 것- outlet : 라우팅 구성 요소 내에서 렌더링된 컴포넌트의 자식 컴포넌트를 표시하는 데 사용라우터 훅 관련- useNavigate : 특정 이벤트 또는 조건에 따라 사용자를 다른 경로로 이동시키고 싶을 때- useParams : 현재 라우팅된 경로의 URL 매개변수에 액세스하는 데 사용- useLocation : 현재 애플리케이션의 위치(경로, 쿼리 매개변수 등)에 대한 정보 제공. 이를 통해 현재 경로나 쿼리 매개변수를 읽을 수 있음- useRoutes : 라우터 구성을 동적으로 생성할 때 사용Debounce- 연이어 호출되는 함수 중 마지막 호출 이후 일정 시간이 지난 후에만 실제로 함수를 실행하도록 하는 기술- 일정 시간 동안 함수 호출을 무시하고, 마지막 호출 이후에만 함수를 실행하여 성능을 향상시키거나 불필요한 작업을 방지할 때 사용useRef- React 함수 컴포넌트 내에서 변수를 유지하고 DOM 요소에 접근하는 데 사용- 컴포넌트 재렌더링 시에도 값이 유지되며, 변경 시에는 컴포넌트가 다시 렌더링되지 않음후기리액트를 처음 접했을 때, 컴포넌트 기반 설계의 아이디어는 매력적으로 다가왔다. 한 페이지에서 모든 요소를 관리하고 조작할 수 있다니!! 하지만 이를 실제로 코드로 구현하는 것은 예상보다 훨씬 복잡했다.컴포넌트 간의 상호작용과 데이터 흐름을 이해하는 데 어려웠다. 예를 들어, 상태가 변경될 때마다 컴포넌트가 어떻게 업데이트되는지 이해하는 것이 쉽지 않았다. 이해하는 것에 매몰되지 말고 일단 구현해보자 하고 예산 계산기 앱 과제를 진행하는데컴포넌트를 나누는 것까지는 어떻게 했는데 props를 전달하는 메커니즘을 생각하는 게 어려웠다. 부모 컴포넌트에서 자식 컴포넌트로 어떤 데이터를 props로 전달해야 하는지, 어떤 형식으로 전달해야 하는지.. 또한, useState와 useEffect 같은 리액트 훅의 개념을 이해하고 적용하는 것도 쉽지 않았다.처음에는 막막했지만, 구글과 챗GPT의 도움을 받으면서 조금씩 이해가 되기 시작했다. 그리고 중간 점검때 강사분께서도 실제로 기능 구현을 많이 해보고 다른 사람의 소스코드도 보면서 공부하라는 조언을 새겨들으면서 남은 3주차도 열심히 해보자!!

웹 개발워밍업클럽프론트엔드FE2주차발자국

채널톡 아이콘