블로그
전체 62025. 06. 17.
0
Day16레이어드 아키텍처와 레이어별 테스트 작성법
레이어드 아키텍처와 테스트-Persistence Layer-Business Layer-Presentation Layer=>3가지가 레이어드 아키텍처의 구조통합테스트풍부한 단위 테스트 & 큰 기능 단위를 검증하는 통합테스트가 있다Spring & JPA를 배우면서 기본 엔티티 설계 있어서JPA(Java Persistence API)로 ORM(Object-Relational Mapping)기술 표준임 Persistence LayerPersistence Layer는 영속성을 걸어주는 Layer라고 볼수 있다Order 에 @Entity, @Table, @OnetoMany 처럼 관련 persistence entity로 종속관계를 걸어주는게 Persistence Layer에서 하는 역할이다 Testgiven, when, then으로 정리를 해주는게 좋다test를 한다면 기본적으로 Order 상태나 Product 상태를 생각해볼수 있다@ActiveProfiles("test") @SpringBootTest 또는 @DataJpaTest를 사용하며 쓸수 있다 Business LayerService에서 진행하는 레이어이다Service는 @Repository에 저장하는 형태의 특성을 가진다@Transactional 을 걸어주고 비즈니에서 핵심로직 역할을 한다.@Service를 위에다 넣어주고 필요한 Repository를 필드로 넣어준다 TestServiceTest관련 작성해주면 된다@AutoProfile,@SpringBoot @AfterEach @Autowired를 걸어서 사용할수 있다.@Autowired로 관련Repository를박고 OrderService로 일어날수 있는 여러가지 테스트를 생각해볼수 있다ex) Order가 들어오면 제품 재고는 있나? Order는 확인한 사항이 중복되지않나? 관련 필요성을 Test로 만들어볼수 있다@DisplayName("중복되는 상품번호 리스트로 주문을 생성할 수 있다.") @Test void createOrderWithDuplicateProductNumbers() { Presentation Layer관리자가 관리하는 Layer라고 보면 된다내용이 워낙많아서 다 소화하지 못했습니다. 추후 소화하는대로 다시 정리하겠습니다. 참고사항-Practical Testing-박우빈코치님-모나코AI
springJpa
・
테스트
2025. 06. 15.
1
3주차 회고록-Readable Code & Practical Testing
테스트 코드 적용실습-테스트코드와 함께 지난 시간 리팩토링을 같이 하고싶어 욕심을 냈다그러다 뒤에 practical-testing 분량을 한참바야한다는 것을 몰랐다가 나중에서야 알고나서 미션을 내기에 욕심과 잘못된 착각에 낼수가 없었다. 처음 for반복문에서 두번째 foreach를 따라하며 고민하고 좌절하며 따라하고 만들어보니 지난날 그냥 인강으로 볼때보다 이해와 흡수가 잘 되었다. 세번째로 stream이 아주 달리 보였다.자연스럽게 stream 을 쓸때에는 그에대한 클래스나 메서드가 완성되었을때는 사용할수 있었다. public List getSellingProducts() { List products=productRepository.findAllBySellingTypeIn(ProductSellingType.forDisplay()); return products.stream() .map(ProductResponse::of) .collect(Collectors.toList()); } 테스트 케이스에 따른 세분화하기테스트를 하면서도 어떻게 무엇을 써야할지 몰라 망설인다 우왕좌왕 하면서도 assertThat 써야하는곳의 전개과정을 보며 어떻게 돌아가는지 흐름과 진짜 반대로 버그가 일어날 만한 상황을 역으로 확인하는 부분에서 이해할 수 있었다.형식적인 테스트 부터 시작하여 경계값설정테스트는 소비자 입장에서 생각해볼 수 있는 부분이다@Test void addZeroBeverages(){ CafeKiosk cafeKiosk=new CafeKiosk(); Americano americano=new Americano(); assertThatThrownBy(()-> cafeKiosk.add(americano,0)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("음료는 1잔 이상 주문하실 수 있습니다."); }난 예외처리를 던지는건 알겠는데 () -> 이게 표현이 다소 생소하였다. 값을 반환할때 저렇게 쓴다는걸 우테코유트브를 보다 알았다 나한테 아주 요상하게 생긴 녀석이었다 머야 괄호를 던지나???? assertThatThrownBy(()-> cafeKiosk.add(americano,0)) DisplayName 은 섬세하게 displayName을 굳이 왜써야하나 생각해보았다한글이 지원되어 무엇보다 도메인사용에 대해 이해할수 있게 명확하게 쓰기 최적의 명령어였다.모르는 사람이 바도 테스트의 목적을 이해를 돕는 문서의 결과가 될수 있다는 생각에 공감이 간다.-백날 영어쓰고 설명이 없다면 진짜 영어로 바도 이해가 쉽다면 상관없다. 그러나 만드는 개발자도, 기획자도 관리 유지에 있어 모호한 부분의 확장에선 둘다 필요하다 Spring Jpa & persistence Layer 포트폴리오를 만들기위해 애썼던 지난 시간들이 떠올랐다. 당시도 작년도 난 눈앞에 내용을 쳐내려가기 바빴다 영속성관계를 깊이 있게 보질 못했다. 이번에 보다 보지못한 지나쳤던 부분을 이해하게 되었다.@OneToMany(mappedBy="order")이 어노테이션은 양방향 관계에서 사용되며 mappedBy 는 주체가 아닌쪽에서 관계를 관리하는 필드를 지정한다@Entity public class Order { @Id private Long id; @OneToMany(mappedBy = "order") private List items; } @Entity public class OrderItem { @Id private Long id; @ManyToOne @JoinColumn(name = "order_id") private Order order; }@MapsId 와 @OneToOne(일대일관계)@OneToOne 두 엔티티 간의 일대일 관계를 정의한다 @MapsId@OneToOne 관계에서 같이 사용되며 두개체가 동일한 기본키를 공유할 때 그 관계를 설정하는데 사용된다@Entity public class UserProfile { @Id private Long id; @OneToOne @MapsId private User user; }아주 비슷하게 생겨서 처음엔 헷갈렸는데 영어보구 알았다. 눈을 크게 뜨고 바야겠다. @SpringBoot VS @ DataJpaTest@DataJpaTest-DataJpaTest 관련 기능중에 @Transactional 기능이 포함되어 rollback 기능이 되어 완료후 초기화를 할 필요하 없다는 사실은 나에게 새삼 놀라웠다. 우라질....Transactional 에 rollback 기능이 있다는것은 알고 있었다 그런데 그 뿌리가 @DataJpaTest 안에도 포함된 내용인지는 새롭게 알았다import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.transaction.annotation.Transactional; import static org.assertj.core.api.Assertions.assertThat; @DataJpaTest @Transactional public class OrderRepositoryTest { @Autowired private OrderRepository orderRepository; @Test public void testSaveOrder() { Order order = new Order(); order.setProductName("Test Product"); order.setQuantity(1); orderRepository.save(order); // 데이터베이스에서 주문을 조회하여 확인 Order foundOrder = orderRepository.findById(order.getId()).orElse(null); assertThat(foundOrder).isNotNull(); assertThat(foundOrder.getProductName()).isEqualTo("Test Product"); } } @Transanctional 과 @ SpringBoot (일반적인 로직) SpringBoot 는 Transactional 과 같이 있을경우에는 rollback기능이 작용한다import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class OrderService { private final OrderRepository orderRepository; public OrderService(OrderRepository orderRepository) { this.orderRepository = orderRepository; } @Transactional public void createOrder(Order order) { // 주문 생성 로직 orderRepository.save(order); // 추가적인 비즈니스 로직 } } 한시간 강의를 듣는데도 정리하고 이해하는데 4-5시간이 걸려 후딱갔다 나한테는 너무 많은 강의량에 눈이 빠질거같다..머리에 구멍난 부분은 차곡차곡 정리되어 가는데 다른분들은 그 많은 강의를 듣고도 스터디에 올려서 난 그러고도 분량이 한참 남아 올리지를 못했다. 내가 하고 싶은 욕심과 내가 소화할 수 있는 부분에 대한 분량에 대해서 시간제한, 자원배분에 대해 생각해보는 한주였다. 참고사항강의 practical testing -박우빈 코치님우테코 람다,오브젝트 유트브-저 닉네임 또 까먹었습니다. 죄송합니다.
테스트코드
・
persistenceLayer
・
springJPA
2025. 06. 08.
0
2 주차 회고록-Readable Code
1) 상속과 조합에 대해서 다시 생각해 보게되었다상속은 inferface,abstract class 라면 조합은 Solid 다여태 조합이 interface 구현물인줄 알았다하나가 되면 또다른 하나가 안되서 열이받는다2주차 회고록 즐거운 마음으로 쓰고 싶었는데 쉽지가 않다책임과 응집성 refactoring Optional 부분에서 버그가 잡히지 않는다...삽질을 정말 얼마나 하는건가....부들부들 이노무 Optional..그래 하다보면 될거야 다시 차분하게 삽을 제정비하고 다시 해보자 다짐한다! 2) 알고리즘교체하기-Stack 은 나에게 용기가 되어 Deque 로 돌아왔다자료구조와알고리즘을 제대로 활용이 원만하게 되지않아 늘상 고구마 100개 먹은 마음 이었다Stack 을 이번에 듣다보니 아니 맨마지막 콜라는 꺼내는데 제일 오래걸리는데 Stack 으로 재귀함하수를꺼낸다? Deque 나 다른걸로 쓰지 라는게 자연스럽게 떠올랐다 그래 고구마100개는 아니구나 다행이다 조금 용기가 생격서 다행이라는 생각이 들어 좋다private void openAndPushCellAt(Stack stack) { CellPosition currentCellPosition=stack.pop(); ..... List surroundedPositions=calculateSurroundedPositions(currentCellPosition, getRowSize(), getColSize()); for(CellPosition surroundedPosition:surroundedPositions){ stack.push(surroundedPosition); } } private void openSurroundedCells2(CellPosition cellPosition) { Deque deque=new ArrayDeque(); deque.push(cellPosition); while (! deque.isEmpty()) { openAndPushCellAt(deque); } } 3) 필드나 메서드 순서나 관련된거끼리 나열하기늘상 메서드고 필드고 하기 바빴지 그렇게 정리하고 상대방이 보기 좋게 해볼 생각은 그닥 해보질 못했다보기도 좋은 떡이 먹기도 좋다고 했던가? 그래 이쁘게 포장한 꽃이나 상품은 소비자나 상대방 보기에도 좋다->그래도 어느정도 정리가되면 상대방이 알아보게 정리는 해야 여러모로 나도 상대방도 시간절약, 스트레스 감소이다버그가 잡히지 않아 열이받을 지라도 앞으로도 힘냅시당~!!! 참고사항-Readable Code/ 박우빈코치님Modern Java우테코 유트브 -상속과 조합-크루님 닉네임 기억을 잘 못해서 죄송합니다. 까먹었어요....
2025. 06. 01.
0
1주자 Redable 회고록
우선 흐릿한 부분에서 추상과 구체과 무엇인지 명확하게 잡혀서 개인적으론 반갑고도 알고싶었던 강의라고 생각합니다.Readable Code 이라는 부분에서 그래 클린코드나 읽기 좋은 코드나 그게 그말이지 라고 생각했습니다.데이터+코드=추상화로 이름짓기 위한 하나의 기본 핵심축이라는걸 듣고 나서야 알았습니다. 필드, 메서드, 클래스로 단위가 커질수록 이름짓기에 대해서 혼란스럽고 명확하게 구분이 헷갈려서 그부분에서 확실히 잡아주었습니다.도메인 용어 사용하기-도메인 용어를 먼저 정의하는 과정(도메인용어사전)이 먼저 필요할 수도 있다ex) shop, store =>비슷한 단어를 중구난방 많아짐 클래스,패키지 늘어날때 문제커짐=>store인지, shop인지 하나로 통일!! 결국은 데이터,코드도 문단이나 하나의 문장으로 필드를 볼수 있는 점에서 새삼스럽지만 저에게는 코드맛집을 찾은것처럼좋았습니다.private int, public static final, private static, private static int gameStatus=0; 처럼 정말 접근제어자를 클래스별로하나하나 추가될때마다 어떻게 써야할지를 몸소 눈으로 확인하고 그 흐름을 알수 있어서 저에게는 자신감을 가져다주는 강의가 된거 같습니다. setter나 getter를 생각없이 쓰는게 아니라 어떻게 필요할때만 써야 한다는 점이 무엇보다 좋았습니다.결국은 객체설계를 다시보게 되었습니다. Stream 에대해서 낯설지만 하나 하나 알아가는 흥미가 생겨서 새롭습니다.무조건 급하게 막 만들려고 하는 도메인의 설계의 원리를 배우는거 같아 이 강의를 만난게 다행이라고 생각합니다. 참고사항모던자바인액션Readable Code :박우빈코치
2025. 05. 30.
0
Day4 SOLID
SOLID 5가지 원칙SRP(Single Responsibility Principle) 단일 책임 원칙하나의 클래스는 하나의 책임만 가진다.ex) 사용자 삭제하는 클래스는 삭제하는 기능만 가진다 다른 사용자 등록, 조회,수정기능등의 여러가지를 집어넣지 않는다 최근에 전화번호부관리 장난감프로젝트를 만들었습니다.각각의 클래스는 일반인, 대학생, 회사원으로 각각 만들고 메뉴는 3가지로 1번은 입력 2번은 검색 3번은 삭제로 입력방식을 분류하였습니다.-MenuViewer클래스는 메뉴종류기능만 입력하는형식public class MenuViewer { public static Scanner keyboard=new Scanner(System.in); public static void showMenu(){ System.out.println("선택하세요..."); System.out.println("1. 데이터 입력"); System.out.println("2. 데이터 검색"); System.out.println("3. 데이터 삭제"); System.out.println("4. 프로그램 종료"); System.out.print("선택 : "); } }-전화번호부정보자메서드는 지우기 기능만합니다.public boolean deleteData() throws MenuChoiceException { System.out.println("데이터 삭제를 시작합니다."); System.out.print("이름 : "); String removeName = MenuViewer.keyboard.nextLine(); for(PhoneInfo info:infoList){ if(info.name.equals(removeName)){ boolean remove = infoList.remove(info); System.out.println("데이터 삭제가 완료되었습니다. \n"); return remove; }else{ System.out.println("해당하는 데이터가 존재하지 않습니다. \n"); } } return false; }이렇게 각각의 클래스나 메서드는 하나의 기능만 부여하는 단일 책임 원칙을 따르고 있습니다. OCP(Open-Closed Principle) 개방 폐쇄 원칙기능추가는 가능하지만 기존코드는 수정하지 말아야 한다-새로운 기능을 추가할 때 기존 코드를 건드리지 않고 확장할 수 있도록 설계해야한다.public class PhoneCompanyInfo extends PhoneInfo { String company; public PhoneCompanyInfo(String name, String num, String company) { super(name, num); this.company = company; } public void showPhoneInfo(){ super.showPhoneInfo(); System.out.println("company : "+company); } public int hashCode(){ return name.hashCode(); } public boolean equals(Object obj){ PhoneCompanyInfo cmp=(PhoneCompanyInfo)obj; if(name.compareTo(cmp.name)==0){ return true; }else{ return false; } } }public class PhoneCompanyInfo extends PhoneInfo{} 처럼 외부라이브러리라도 그 기능을 extedns 또는 implements로 구현부를 새로운 기능으로 추가할 수 있습니다.주의사항protected로 시작하는 메서드나 필드는 외부라이러리의 경우 기능추가를 할수 없어 개방-폐쇄 원칙에 위반됩니다. LSP(Liskov Substitution Principle) 리스코프 치환 원칙부모 클래스의 기능을 자식 클래스가 대체할 수 있어야 한다-인터페이스로 부모클래스에서 호출기능을 만들었다면 자식클래스에서는 부모가 물려준 기능을 구현할수 있습니다. interface Animal{ void sound(); void sleep(); }class Dog implements Animal{ @Override public void sound(){ System.out.println(”멍멍”); } @Override public void sleep(){ System.out.println(”강아지는 코골고 궁시렁거리면서 잡니다”); } } ISP(Interface Segregation Principle) 인터페이스 분리 원칙사용자는 자신이 사용하지 않는 인터페이스에 의존하지 않는다.-필요한 메소드만 있는 작은 인터페이스를 사용하는것이 좋다class Vehicle { public void startEngine() { } } class Car extends Vehicle { @Override public String startEngine() { return "Car engine started"; } } class Bicycle extends Vehicle { @Override public void startEngine() { throw new UnsupportedOperationException("Bicycles don't have engines"); } }Vehicle은 이동수단이라 public void startEngine(){}을 정의하였습니다. 그러나 Car는 자동차 엔진으로 시작합니다. 다른 Bicycle은 엔진이 필요없는 특정 이동수단의 인터페이스를 만들어야 합니다. DIP(Dependency Injection Principle) 의존성 역전의 원칙구체적인 구현보다 인터페이스나 추상 클래스에 의존하는 주입방식입니다.class EmailService { public void sendEmail(String message) { System.out.println("Sending email: " + message); } } class Notification { private EmailService service; public Notification(EmailService service) { this.service = service; } public void notify(String message) { service.sendEmail(message); } } // 사용 예 public class Main { public static void main(String[] args) { EmailService emailService = new EmailService(); Notification notification = new Notification(emailService); notification.notify("Hello, World!"); } } this.service=service는 필요한 EmailService를 주입받아사용하고 있습니다.service.sendEmail(message)또한 Notification에서 EmailService클래스의 service의 데이터를 sendEmail(message)로 주입받아 Notification에서 사용할수 있습니다.class Notification { private EmailService service; public Notification(EmailService service) { this.service = service; } public void notify(String message) { service.sendEmail(message); } } 참고사항-Monica ai-ReadableCode : 박우빈 코치님-내 블로그 https://www.notion.so/protected-VS-abstract-VS-interface-19ad3d93797281aebe49eba361c78e0f
2025. 05. 28.
1
Day2 추상과 구체 -Readable Code
Readable Code에 대한 추상과 구체추상과 구체=추상(데이터)+구체(코드)ex) String(데이터)store(코드); =>프로그램=데이터(추상) + 코드 (구체) 1) 추상데이터가 추상에 해당하는 대표적인 표현중의 하나이다ex) char one=1byte //C언어 , 자바 2bytesex) int one=4byte //자바여기서 char, int, String, boolean... 등등이 추상이고 이 추상은 데이터타입에 해당한다. 2) 구체구체는 이 추상데이터타입을 구체적인 핵심개념표현으로 변수(코드)로 나타낸다ex) one, selectedColIndex 3) 추상과 구체가 문장으로 나타날 경우int selectedColIndex=convertColFrom(cellInputCol);=>여기서 int 는 추상 selectedColindex=convertColFrom(cellInputCol);=>구체(변수)에 해당하는 코드핵심개념표현이다 4) 추상과 구체가 메서드로 나타날 경우gameStatus=-1;=> changeGameStatusToLose();public static void changeGameStatusToLose(){return gameStatus=-1;}changeGameStatusToLose라는 의미는 -1은 게임실패를 의미하기때문에 그부분을 반영하여 최대한 구체에 해당하는 코드 핵심개념을 표현한다. 추상과 구체로 이름짓기 주의사항1) 스펠링 단수,복수 구분하여 축약하지 말고 이름 줄이지 말기2) 은어,방언 사용하지 않기3) 도메인 용어 사용하기=> 추상과 구체의 이름짓기목적: 읽기좋은 코드가 유지보수성과 가독성이 높아진다 참고사항-강의명 : Readable Code : 읽기 좋은 코드를 작성하는 사고법-지식 공유자 :박우빈 코치님-강의 링크: https://inf.run/pZXb7-스터디 : 인프런 워밍업 클럽 4기 BE 클린코드 & 테스트 회고사항추상과 구체가 그냥 늘상 형식적으로 쓰기만 했다 구체(변수)의 기능은 알고 있었다 과거에도 최대한 상대방이 내가 만든 프로그램은 어떻게 무슨 생각을 가지고 클래스를 만드는지 왜 호출하고 변수이름을 왜 이렇게 만들었는지 나를 뒤돌아아보게한다. 코드작성을 위한 흐름이 끊기지 않게 도메인설계의 핵심사항이자 기본원리를 이해해서 좋다.
백엔드
・
클린코드