💸딱 하루, 인프런 천원샵 오픈!

블로그

김문진

[인프런 워밍업 클럽 BE 3기] 백엔드 프로젝트 - 1주차 발자국

1주차 발자취참여 계기사이드 프로젝트나 회사 프로젝트를 진행하다 보면, 몇 달 전에 작성한 내 코드를 보면서 ’이게 무슨 의미지?’라고 생각하는 일이 점점 많아졌다.더 이상 이렇게는 안 되겠다는 생각에 클린코드와 리팩토링에 관한 책을 읽고 나름대로 적용해 보았다. 하지만 내가 제대로 적용한 건지, 아니면 공부한 방식이 잘못된 건지에 대한 의문이 들던 중 워밍업 클럽을 알게 되었다.워밍업 클럽에 참여하면 강사님의 개선되는 코드뿐 아니라 과제와 다른 사람들의 코드도 볼 수 있어, 읽기 좋은 코드를 작성하는 데 많은 도움이 될 것 같아 참여하게 되었다.학습내용추상클린코드를 하는 이유?가독성을 높인다.가독성 상승 -> 코드가 잘 읽힌다 -> 이해하기가 쉽다 -> 유지보수하기 쉽다 -> 시간과 자원이 절약된다. 추상사물을 정확하게 이해하기위해서 중요한 정보는 가려내어 남기고, 덜 주요한 정보는 생략하여 버리는 것 추상화복잡한 데이터와 복잡한 로직을 단순화하여 이해하기 쉽도록 돕는다적절한 추성화: 도메인의 문맥 안에서 핵심 개념만 남겨서 표현하는 것추상화의 가장 대표적인 행위 = 이름 짓기논리 사고의 흐름인지적 경제성최소한의 인지만 가져가 최대의 효율을 내보자사고의 depth 줄이기사용할 변수는 가깝게 선언하기부정어를 대하는 자세부정어구를 쓰지 않아도 되는 상황인지 체크부정의의미를 담은 다른 단어가 존재하는지 고민 or 부정어구로 메서드명 구성해피 케이스와 예외 처리예외가 발생할 가능성 낮추기어떤 값의 검증이 필요한 부분은 주로 외부 세계와의 접점의도한 예외와 예상하지 못한 예외를 구분하기객체지향객체지향이란?관심사를 분리해여 객체로 만들어 높은 응집도와 낮은 결합도의 프로그램을 만드는 것객체지향 SOLIDSRP(Single Responsibility Principle): 단일 책임 원칙하나의 클래스는 하나의 책임을 가져야한다.주문이라는 클래스가 존재할 때 주문 클래스는 주문만 처리해야하고, 결제, 포인트와 같은 다른 책임을 시행하지 않아야한다.OCP(Open Closed Priciple): 개방 폐쇄 원칙확장에는 열려있고, 수정에서는 닫혀있다.수정이 많이 필요한 구현체를 의존하지 않고, 변하지 않는 추상에 의존해야한다.LSP(Listov Substitution Priciple): 리스코프 치환 원칙하위 타입 객체는 상위 타입 객체에서 가능한 행위를 수행한다.상위 타입의 객체에 구현된 기능이 상속받은 하위 타입의 객체에서 의도하지 않은 동작을 하지 말아야한다.ISP(Interface Segregation Principle): 인터페이스 분리 원칙자신이 사용하는 메소드에만 의존interface를 잘게 분리하여, 구현체에서 인터페이스 메서드를 사용하지 않도록 한다.DIP(Dependency Inversion Principle): 의존 역전 원칙변하기 쉬운 것 (구체적인 것) 보다는 변하기 어려운 것 (추상적인 것)에 의존상위 클래스는 하위 구현체에 의존하지 않고, 인터페이스에 의존하여 구현체를 쉽게 변경할 수 있게 해야한다.객체지향 적용하기상속과 조합상속보다 조합을 사용하자부모와 자식의 결합도가 높다조합과 인터페이스를 활용하는 것이 유연한 구조Value Object도메인의 어떤 개념을 추상화혀여 표현한 값 객체값으로 취급하기 위해서, 불변성, 동등성, 유효성 검증 등을 보장해야 한다. VO / EntityEntity는 식별자가 존재한다. 식별자가 아닌 필드의 값이 달라도, 식별자가 같으면 동등한 객체로 취급한다.VO는 식별자 없이, 내부의 모든 값이 다 같아야 동등한 객체로 취급한다.일급 컬렉션일급시민다른 요소에게 사용 가능한 모든 연산을 지원하는 요소일급 컬렉션컬렉션을 포장하면서, 컬렉션만을 유일하게 필드로 기지는 객체컬렉션을 추상화하며 의미를 담을 수 있고, 가공 로직의 보금자리가 생긴다getter로 컬렉션을 반환할 일이 생긴다면, 외부 조작을 피하기 위해, 새로운 컬렉션으로 만들어서 반환해주자.EnumEnum은 상수의 집합이며, 상수와 관련된 로직을 담을 수 있는 공간이다.특정 도메인 개념에 대해서 그 종류와 기능을 명시적으로 표현해줄 수 있다.만약 변경이 정말 잦은 개념은 Enum보다 DB로 관리하는 것이 나을 수 있다.회고메소드 들을 관심사에 따라 분리하여 이름을 짓고 객체로 만들어 관리하는 방법을 배웠다.내가 영어를 잘하지 못하여서 메서드를 지을 때 마다 이름들이 제각기 일 때가 있었는데 좋은 컨벤션을 본것 같다.그리고 메서드 이름을 더 잘 짓기위해서 영어공부를 해야할 것 같다.

백엔드워밍업클럽

leeebug

워밍업 클럽 스터디 3기 FS - 4주차 발자국

3주차 과제를 일찍 마무리하고 4주차는 조금 일찍 학습을 시작했기 때문인지 유난히도 길었던 마지막 주차도 끝나간다.이번주는 특히나 굉장히 많은 이슈를 경험했다. 결론부터 말하자면 채팅방 생각보다 구현하기 쉽지 않았다. 간단하게 하나만 언급하자면 URL로 장난질 치는 것에 대한 방어로직 구현이 특히 어려웠다. 사실 예전에 firebase 기반으로 미니 SNS를 구현했던 경험이 있는데 이번에 채팅을 구현했으니 이 프로젝트를 그대로 고도화해서 SNS를 완성해볼 계획이다. (실제로 완성할 수 있을지는...)📝 4주차 학습Supabase Auth이메일/비밀번호, OAuth, Magic Link, SMS 인증 등 다양한 인증 방식을 지원하는 인증 서비스supabase.auth.signUp, signInWithOAuth, getUser() 등으로 유저 관리와 세션 제어가 가능JWT 기반으로 RLS와 연동되며, 로그인 상태 자동 유지 및 세션 갱신 기능도 제공Supabase RealtimePostgreSQL의 Listen, Notify 기능을 기반으로 실시간 데이터 동기화 제공테이블 변경(Insert, Update, Delete)을 클라이언트에서 실시간으로 브로드캐스트supabase.chaeenel()로 원하는 이벤트를 구독Supabase RLS(Row Level Security)데이터베이스의 행(Row) 단위로 접근 제어를 설정하는 보안 기능Create Policy를 적용하여 유저별로 조회/ 수정 권한을 세밀하게 조정활성화 시 명시적인 권한 정책 필수아래는 개인적으로 나머지 공부로 학습하고 적용해본 라이브러리입니다.ZodTS 환경에서 런타임 스키마 검증과 타입 추론을 제공하는 유효성 라이브러리z.object() 등 메서드로 구조화된 데이터의 유효성 검사 수행 및 타입 자동 생성서버 및 클라이언트 모두에서 안전한 폼 및 api 검증에 활용React Hook Form과 함께 사용하기 유용한 라이브러리React Tostify토스트 메시지를 손쉽게 띄울 수 있는 라이브러리간단한 API로 다양한 유형의 토스트 알림 제공강력한 커스터마이징 제공Kyfetch API 기반의 모던한 HTTP 클라이언트간결한 문법 제공자동 재시도, 에러 핸들링, 인터셉터 등 확장성과 다수의 편의 기능 포함📋 4주차 미션💬GitHub 저장소👉체험하러 가기 미션 해결 과정 요약이번주 미션의 필수 구현 과제는 Supabas Auth를 사용한 회원가입, 로그인 기능 구현 및 Supabase Realtime을 활용한 1:1 채팅 기능 구현하기였다. 추가 구현 과제는 메시지 삭제, 메시지 알림, 메시지 읽음 여부 표시, 채팅 신고, 유저 차단 기능 등 자유롭게 구현하기였는데 시간 관계 상 전부 구현하긴 어려워서 비교적 쉬운 메시지 삭제와 메시지 알림을 제한적으로 구현했다. 원래는 DB 스키마를 꼼꼼하게 고민하고 시작했어야하는데 급하게 하다보니 처음 계획했던 내용과 많이 달라졌다.myon_users.id -> auth.users.idSupabase Auth로 회원가입된 유저만 등록 가능myon_rooms.userA_id -> myon.users.idmyon_rooms.userB_id -> myon.users.id회원가입된 유저만 채팅에 참여 가능myon_messages.sender_id -> myon_users.id회원가입된 유저만 채팅 전송 가능myon_rooms.last_message_id -> myon_messages.id가장 최근 메시지 미리보기 시 테이블 join에 활용myon_users.username회원가입 시 입력한 닉네임을 기반으로 한글과 특수 문자 등을 제거한 후 중복 발생 시 유틸함수를 통해서 suffix를 불여서 고유한 username을 자동 생성(회원가입 시 입력 폼의 간소화를 위한 선택)✅ 이메일 로그인, 회원가입GET app/auth/signup/callbackPOST api/user/email/register✅ OAuth 로그인, 회원가입GET app/auth/oauth/kakao/callbackPOST api/user/oauth/register우선 회원 기능부터 만들기 시작했는데 강의 패턴을 참고하여 자동 생성되는 auth.users 테이블만 사용하여 회원 로직을 만들었는데 메타 데이터의 형태가 provider 별로 일정하지 않고.. 무엇보다도 auth.users 테이블은 커스텀이 제한적이기 때문에 public.users 테이블을 별도로 관리하였다. 카카오 계정 로그인의 경우 user_metadata를 커스텀 인터페이스로 관리하여 타입 오류를 방지하였다.또한 auth.users 테이블만 단독 사용시의 문제는 회원가입 단계에서 사전에 이메일 중복 검증이 어렵다는 점도 단점이었다.찾아보니 보안상의 이유로 Supabase 내부적으로 auth.users 테이블을 직접 조회하는 기능은 별도로 제공하지 않아서 가입 요청 후에 에러를 캐치할 수 있는 구조이기 때문에 이부분도 public.users를 조회하여 이메일 중복 검증을 통과한 경우에만 회원가입 요청을 할 수 있도록 처리했다.회원가입이나 로그인 인풋 유효성 검증은 Zod + react-hook-form 라이브러리도 대체했다.이메일 회원가입이메일 로그인✅1:1 채팅POST, GET api/rooms/[roomId]POST api/messagesGET api/messages/[roomId]문제는 채팅 기능 구현이었는데 채팅 기능 자체는 Realtime 구독으로 어렵지않게 완성했으나 문제는 방어로직 구현이었다. 몇 가지 예시를 들자면 /direct-message 로 접근 시에 해당 페이지에서 나 자신을 제외한 모든 유저 리스트를 불러온 뒤, /direct-message/:roomId 로 동적 라우트를 구현할 때, 처음에는 고유성을 보장하기 위해서userA_username-userB_username-suffix 형태로 roomId를 생성하는 유틸 함수를 사용했는데 렌더링 시 마다 suffix가 변동되기 때문에 서버단에서 해당 URL이 유효한 URL인지 검증하기가 쉽지 않아서 userA_username과 userB_username을 정렬하여 항상 동일한 roomId를 생성하는 순수 유틸 함수로 변경하여 해당 문제를 해결하였다.export function generateRoomId({ usernameA, usernameB }: { usernameA: string; usernameB: string }) { const sortedUsernames = [usernameA, usernameB].sort() return `${sortedUsernames[0]}-${sortedUsernames[1]}` }과제 추가 구현 기능✅ 메시지 삭제(Soft Delete)PATCH api/message/[messageId]메시지 삭제는 두가지 방식이 있는데 DELETE 메서드를 사용하여 DB Row에서 아예 삭제하는 하드 삭제와 실제 DB Row에서 삭제하지 않지만 is_delete 같은 플래그를 true 하여 클라이언트단에서 감추는 방식인 소프트 삭제 방식이 있다. 하드 삭제의 경우 DB 공간 절약이 필요하거나 탈퇴 회원 정보 등 영구 삭제가 필요한 경우에 적합하고 소프트 삭제의 경우는 복구가 필요하거나 삭제 이력을 추적해야하는 경우에 적합한데 채팅은 로그를 남기는게 중요해서 개인적으로는 소프트 삭제로 구현했다.메시지 호버 시 삭제 아이콘 표시삭제된 메시지✅ 메시지 알림(토스트 메시지 활용)별도 API route 없이 구독으로 구현그냥 마무리하기 아쉬워서 추추가 기능으로 구현했다. 예전부터 토스트 메시지에 관심이 많았는데 직접 구현해보니 생각보다 비효율적이라서 react-tostify 라는 라이브러리를 적용했다.토스트 메시지는 스크롤이 최하단이 아닌 지난 메시지를 읽고 있을때만 우측 상단에 스택 형태로 알림을 보내도록 구현했다. 👀 4주차 회고이번 주는 지난 스터디 기간 동안 진행했던 프로젝트를 배포하는 과정이 포함되어 있었기 때문에, 추가적인 기능보다는 안정적인 배포에 중점을 둘 계획이었으나.. 다행히도 지난주에 미리 매를 맞아두었기 때문에 이번 주 과제 배포는 크게 문제 없이 마무리할 수 있었다.다만 실제 배포 경험이 많지 않다 보니 환경 변수 관련 이슈를 자주 겪게 되었고, OAuth Redirect URL 설정 누락, 빌드 시 타입 오류 등의 경험으로 배포 시 고려해야 할 요소들을 더 잘 이해하게 되었다. 앞으로는 다른 프로젝트 배포 시 참고할 수 있도록 트러블슈팅 내역을 꼼꼼하게 정리하는 습관을 들일 계획이다.이번주에 과제를 진행하면서 딱 한가지 아쉬웠던 점이라면 2주차부터 꾸준히 적용해오던 Container-Presentational Component 패턴을 이번에는 적용시키지 못했다는 점이다. 이번주 과제가 전반적으로 복잡도가 높다보니 관심사 분리를 코드에 녹여내지 못했으나 점진적으로 리팩토링을 통해서 개선해나가기 위해서 백로그에 기록해두었다. 스터디 이후..이번에 구현한 채팅 기능은 실제 배포를 해보니 전송 시 약간의 딜레이가 발생하는 것을 발견했다. 지금 타이밍에서 메시지 전송에 대한 낙관적 업데이트를 적용해서 최적화하는것이 가장 시급한 과제라고 생각한다.끝으로 이번 프로젝트는 이후에 포트폴리오로 활용할 수 있도록 고도화 작업을 이어갈 생각이며, 동시에 타입스크립트에 대한 이해를 더 심화시키고, 쉽진 않겠지만 최근 관심이 생긴 테스트 코드 작성 관련 학습도 병행해나가 보려 한다.정말 마지막으로.. 풀스택 과정을 포함한 모든 3기 스터디 러너분들, 멘토님들과 서포터분들, 워밍업 클럽 관계자 여러분들 모두 고생하셨습니다👏 여러분들 덕분에 좋은 인사이트 얻어갑니다 :)  

풀스택워밍업클럽3기풀스택Next.js4주차회고미션

suover

인프런 워밍업 클럽 스터디 3기 - 백엔드 클린 코드, 테스트 코드 4주차 발자국

Readable Code: 읽기 좋은 코드를 작성하는 사고법Practical Testing: 실용적인 테스트 가이드강의와 함께한 인프런 워밍업 클럽 스터디 3기 - 백엔드 클린 코드, 테스트 코드 (Java, Spring Boot)4주차 발자국 입니다.학습 내용 요약이번 주에는 Practical Testing: 실용적인 테스트 가이드 강의에서 계층형 아키텍처(Layered Architecture) 의 개념을 다시 한번 정리하고, 스프링 기반 테스트에서 자주 사용하는 Mock/Spy 기법과 관련 애너테이션들을 배웠습니다.Layered Architecture (계층형 아키텍처)Presentation Layer: 사용자 입력과 응답을 담당. API 요청/응답 혹은 프론트엔드 인터페이스를 제공하며, 기본적인 파라미터 검증과 변환만 수행.Business Logic Layer: 애플리케이션의 핵심 비즈니스 로직과 규칙을 구현. 서비스(Service)나 도메인(Domain) 로직이 위치하며, 트랜잭션 관리의 주된 대상이 됨.Persistence Layer: 데이터베이스/파일시스템 등 영속성 저장소와 직접적으로 상호작용. Repository(DAO) 형태로 CRUD를 담당.Mockito & Spring Test 관련 애너테이션@Mock / @Spy / @InjectMocks: 순수 Mockito 환경(스프링 컨텍스트 없이)에서 가짜 객체(또는 부분 가짜 객체)를 만들어 단위 테스트를 쉽게 작성할 수 있게 함.@MockBean / @SpyBean: 스프링 애플리케이션 컨텍스트를 기동하는 통합 테스트 환경에서 특정 Bean을 Mock 또는 Spy로 교체하여, 외부 의존성을 간단히 제어하고자 할 때 활용.테스트 설계 방향각 레이어별로 테스트 범위를 명확히 하여, “Persistence → Business → Presentation” 순으로 책임을 분리해 점진적으로 테스트 커버리지를 넓힘.@MockBean / @SpyBean을 과도하게 사용하지 않도록 주의: 꼭 필요한 부분만 모의(Mock/Spy) 처리하고, 나머지는 실제 로직을 이용해 통합 테스트를 안정적으로 수행.테스트 시나리오별로 @BeforeEach를 활용하는 방법과, 각 테스트 메서드에서 독립적으로 given-when-then을 구성하는 방법을 비교.학습 회고얻은 인사이트레이어별 책임과 역할이 분명해야 복잡한 서비스에서도 테스트 설계가 한결 명확해짐을 느꼈습니다.Mock/Spy를 올바른 상황에서만 사용하면 외부 의존성을 최소화하여 빠른 단위 테스트를 작성할 수 있으나, 반대로 불필요하게 남발하면 테스트 유지보수가 어려워짐을 체감했습니다.어려웠던 점@MockBean과 @SpyBean을 사용해 스프링 컨텍스트를 띄울 때 테스트 속도가 느려지거나, 동일 타입 Bean이 여러 개일 때 어느 Bean을 대체하는지 혼동될 수 있었습니다.레이어별 테스트를 작성하면서, 중복으로 보이는 코드(예: 테스트 준비 로직)를 어느 정도까지 @BeforeEach로 추출해야 할지 고민이 되었습니다.미션🎯 Day16 미션: Layered Architecture 구조의 레이어별 특징과 테스트 방법요구사항Layered Architecture에서 각 레이어가 하는 역할, 특징, 그리고 테스트 방식을 자기만의 언어로 정리하기나만의 정리Presentation Layer역할: 사용자나 외부 시스템의 요청을 받고 응답을 전달. 파라미터 검증, DTO 변환, HTTP 상태 코드 결정 등을 담당.특징: 비즈니스 로직을 직접 수행하지 않고, 유효성 체크 후 Service 호출 → 결과 반환에 집중.테스트 방법: 컨트롤러 통합 테스트(@SpringBootTest, @WebMvcTest + MockMvc)를 통해 실제 요청/응답 형식을 모의하거나, 프론트엔드와의 e2e 테스트를 진행해볼 수도 있음.Business Logic Layer역할: 애플리케이션의 핵심 규칙, 알고리즘, 트랜잭션 등의 로직 담당.특징: 여러 Repository를 조합하여 도메인 로직을 수행하고, 예외 처리, Validation 등의 업무 로직을 포괄적으로 관리.테스트 방법: 단위 테스트를 통해 특정 비즈니스 로직의 정확성을 검증. 필요 시 Repository를 Mock 처리(예: @Mock, @MockBean)하여 DB 의존성을 제거하고 로직만 집중 테스트.Persistence Layer역할: 데이터의 저장/조회/수정/삭제 등 영속성 처리에 집중.특징: 쿼리 작성, DB 연결, CRUD 로직을 추상화한 Repository/DAO 형태로 제공.테스트 방법: 실제 DB 혹은 In-Memory DB(H2 등)를 활용한 통합 테스트(@DataJpaTest). 쿼리 정확도, 트랜잭션 처리, 성능 등을 검증하기에 좋음.🎯 Day18 미션 1: @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks의 차이점 정리요구사항각 애너테이션이 어떤 환경(단위 테스트/통합 테스트)에서 사용되고, 어떤 특징이 있는지 정리하기1) @Mock주로 사용 환경: Mockito (단위 테스트)스프링 빈 등록 여부: X (별도 Bean 아님)특징:순수 Mock 객체를 생성하여, 내부 로직 없이 호출 기록/결과(Stubbing)만 지정할 수 있음단위 테스트에서 외부 의존성을 배제하고 특정 로직만 검증할 때 유용2) @MockBean주로 사용 환경: Spring Boot Test(통합 테스트)스프링 빈 등록 여부: O (빈으로 등록됨)특징:스프링 애플리케이션 컨텍스트에 등록된 실제 Bean을 Mock으로 교체다른 Bean이 해당 Bean을 의존하고 있으면, 그 의존성도 가짜(Mock)로 처리됨@SpringBootTest나 @WebMvcTest 등 스프링 컨텍스트가 뜨는 환경에서 사용3) @Spy주로 사용 환경: Mockito (단위 테스트)스프링 빈 등록 여부: X (별도 Bean 아님)특징:부분 가짜(Spy) 객체를 생성기본적으로 실제 메서드 동작을 유지하면서, 필요한 부분만 가짜(Stubbing)로 설정 가능복잡한 객체를 테스트할 때, 일부는 실제 로직을 실행하고 일부만 Mock 처리할 수 있어 유연함4) @SpyBean주로 사용 환경: Spring Boot Test(통합 테스트)스프링 빈 등록 여부: O (빈으로 등록됨)특징:스프링 컨텍스트 내 실제 Bean을 Spy로 교체기본적으로는 실제 로직을 수행하되, 특정 메서드만 가짜로 동작시키거나 호출을 기록할 수 있음통합 테스트 환경에서 부분 Mocking이 필요할 때 적합5) @InjectMocks주로 사용 환경: Mockito (단위 테스트)스프링 빈 등록 여부: 해당 없음특징:@Mock 또는 @Spy로 만들어진 객체들을 자동으로 주입(생성자, 세터, 필드 순)해줌예: @InjectMocks UserService라면, UserService가 의존하는 Repository나 다른 객체들을 @Mock으로 생성해 주입받을 수 있음단위 테스트에서 여러 의존 객체를 편리하게 Mock/Spy로 대체할 수 있도록 지원 🎯 Day18 미션 2: 테스트 시나리오(@BeforeEach, given, when, then) 구성요구사항아래 3개의 테스트 메서드가 있을 때, “어떤 준비 로직을 @BeforeEach로 뽑고, 어떤 부분을 각 테스트의 given절에 둘지”, “when절을 어떻게 구성할지”를 구상해보기예시 테스트 3종사용자가 댓글을 작성할 수 있다.사용자가 댓글을 수정할 수 있다.자신이 작성한 댓글이 아니면 수정할 수 없다.나의 구성@BeforeEach void init() { // 공통 데이터를 미리 생성하지 않습니다. /* * 각 테스트가 각각의 상황(사용자, 게시물, 댓글)을 자유롭게 구성할 수 있도록, * 여기서는 사전에 아무것도 세팅해두지 않습니다. * 테스트마다 필요한 데이터 타입이나 조건이 다를 수 있으므로, * 각 테스트 메서드 내부에서 직접 객체를 생성하고 준비해 주는 방식을 채택했습니다. */ } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given // 1-1. 사용자 생성에 필요한 내용 준비 // 1-2. 사용자 생성 // 1-3. 게시물 생성에 필요한 내용 준비 // 1-4. 게시물 생성 // 1-5. 댓글 생성에 필요한 내용 준비 // when // 1-6. 댓글 생성 // then // 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given // 2-1. 사용자 생성에 필요한 내용 준비 // 2-2. 사용자 생성 // 2-3. 게시물 생성에 필요한 내용 준비 // 2-4. 게시물 생성 // 2-5. 댓글 생성에 필요한 내용 준비 // 2-6. 댓글 생성 // when // 2-7. 댓글 수정 // then // 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given // 3-1. 사용자1 생성에 필요한 내용 준비 // 3-2. 사용자1 생성 // 3-3. 사용자2 생성에 필요한 내용 준비 // 3-4. 사용자2 생성 // 3-5. 사용자1의 게시물 생성에 필요한 내용 준비 // 3-6. 사용자1의 게시물 생성 // 3-7. 사용자1의 댓글 생성에 필요한 내용 준비 // 3-8. 사용자1의 댓글 생성 // when // 3-9. 사용자2가 사용자1의 댓글 수정 시도 // then // 검증 }@BeforeEach정말 모든 테스트에 공통으로 필요한 세팅(예: DB clean-up, 동일한 테스트 데이터 세팅 등)이면 이곳에 배치.하지만 테스트마다 시나리오가 크게 다른 경우, 오히려 각 테스트 내부(given 절)에서 데이터를 생성하는 편이 가독성과 유연성이 좋아질 수 있음.given 절테스트에 필요한 사전 조건(사용자, 게시물, 댓글 등) 및 Mock/Stubbing이 필요하다면, 해당 로직을 배치.시나리오별로 조금씩 다른 상황을 구성할 때, @BeforeEach보다 각 테스트 내부의 given 부분에 명시적으로 작성하는 방식이 선호됨.when 절실제 테스트 대상 메서드를 호출하는 구간. 한 개의 테스트에서 “정확히 하나의 when 절”을 유지해, 명확히 어떤 동작을 검증하는지 드러내도록 함.then 절결과 검증(Assertions) 수행. 저장된 데이터 확인, 예외 발생 여부 확인, 반환값 확인 등.미션 회고레이어별 특징을 자기 언어로 설명단순히 문서상 개념을 반복하기보다, 실제 현업/프로젝트에서 마주치는 구조를 떠올리며 다시 풀어내보니, 각 계층이 왜 필요한지, 테스트를 어떻게 접근해야 하는지 더 명확해졌습니다.Mock/Spy 애너테이션 정리정리를 해보니 실제로 코드에 적용할 때 어떤 상황에서 무엇을 써야 하는지가 더 분명해졌습니다.@MockBean/@SpyBean이 스프링 컨텍스트를 사용하는 통합 테스트에서 유용하지만, 속도와 Bean 교체 이슈가 있어 주의가 필요하다는 점이 인상적이었습니다.3가지 테스트 시나리오에서 @BeforeEach & given/when/then 배치시나리오에 따라 @BeforeEach를 최소화하거나, 여러 테스트에 공통적으로 필요한 부분만 추출하는 방식이 각각 장단점이 있음을 확인했습니다.테스트 구조를 “given-when-then”으로 고정하면 가독성이 올라가지만, 너무 세세한 단계 분리는 오히려 장황해질 수 있으므로 균형을 잡아야겠습니다.회고칭찬하고 싶은 점레이어별 테스트 기법과 Mock/Spy 전략이 한층 체계적으로 잡혔습니다.실제 코드를 작성하며, “가짜 객체를 어디까지 써야 하나? 외부 연동은 어떻게 테스트하나?” 같은 고민을 많이 해 봐서, 앞으로는 목적에 맞는 테스트를 설계하는 능력이 향상될 것 같습니다.아쉬웠던 점 & 보완 계획Mock 남발 시 발생할 수 있는 문제(설정 복잡도 상승, 실제 로직 변경 시 Stubbing 깨짐)를 더 구체적인 예제로 다뤄보면 좋을 것 같습니다.@BeforeEach와 개별 테스트 내부 given 절의 적절한 균형점을 찾으려면, 실제 현업 수준의 다양한 테스트 케이스를 더 경험해 봐야겠습니다.다음 목표예외 상황 테스트 강화단순 성공 케이스뿐 아니라, 비정상 입력이나 예외 케이스를 좀 더 체계적으로 정리하고 테스트에 반영. 도메인 별로 테스트 슬라이싱@DataJpaTest, @WebMvcTest 등 스프링이 제공하는 슬라이스 테스트 방식을 적극 활용해 보기.테스트 실행 속도 최적화통합 테스트와 단위 테스트를 적절히 조합하여, 빠르면서도 신뢰성 있는 테스트 환경을 구축해 보기.이번 4주차에는 계층형 아키텍처를 다시 한번 복습하면서 Mock/Spy, @MockBean/@SpyBean 등 스프링 테스트 환경에서 자주 쓰이는 기법들을 정리하고 적용해 보았습니다. 학습 내용과 미션을 통해 각 레이어가 가진 의미와 책임이 더욱 또렷해졌고, 가짜 객체를 어떻게 잘 활용해야 하는지 감이 잡힌 것 같습니다.앞으로도 학습 내용을 기록하고 회고하면서, 점점 더 탄탄한 테스트 코드를 작성해 가도록 하겠습니다.감사합니다!

백엔드인프런워밍업클럽스터디백엔드클린테스트코드발자국회고3기

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

Layered Architecture 구조의 레이어별 테스트 정리Persistence Layer특징: 데이터를 직접 접근하고 관리하는 계층.테스트 방법: CRUD 중심으로 테스트하되, 비즈니스 로직은 포함하지 않는다.유의점: 테스트 수행 후 데이터 정리(clean-up)를 철저히 해야 한다.Business Layer특징: 비즈니스 로직이 전개되는 중심 계층.테스트 방법: 통합 테스트로 비즈니스 로직의 정확성을 검증하며, 예외 상황 처리에 더 많은 집중이 필요하다.유의점: 예외 케이스를 잘 다루는 것이 개발자의 역량이다.Presentation Layer특징: 외부 요청을 처리하는 계층.테스트 방법: 입력 값의 유효성을 검증하며, 하위 레이어는 모킹 처리하여 독립적으로 테스트한다.유의점: 유효성 검증이 어느 레이어에서 이루어져야 하는지 신중하게 고민해야 한다.Mocking에 대한 이해와 활용Mock 객체를 활용하는 것이 단위 테스트의 핵심이라고 할 수 있다. Mock 객체는 실제 객체의 행위를 대신할 수 있는 가짜 객체로, Mockito 같은 라이브러리를 활용해 쉽게 만들 수 있다. 중요한 것은 Mock 객체를 사용할 때, 실제 객체의 동작을 완벽하게 대체한다고 생각하지 말고 의심하고 검증하는 태도를 유지하는 것. 특히, Mock 객체의 역할과 Stub의 역할을 동시에 수행할 수 있다는 점을 배웠다.또한, Mocking이 들어가는 순간 그것은 단위 테스트라는 개념을 다시 한번 되새길 수 있었습니다.@Mock, @MockBean, @Spy, @SpyBean, @InjectMocks의 차이@Mock: Mockito에서 단위 테스트를 위한 Mock 객체 생성. 행위 검증이 가능하다.@MockBean: Spring Boot 테스트 환경에서 Mock 객체를 사용하기 위해 Bean을 Mock으로 교체한다.@Spy: 실제 객체를 사용하면서 일부만 Stubbing을 할 수 있다.@SpyBean: Spring Boot 테스트 환경에서 Bean을 Spy 객체로 교체한다.@InjectMocks: @Mock과 @Spy로 생성된 객체를 테스트 대상 객체에 자동으로 주입해준다.테스트 환경의 독립성 유지에 대한 고민테스트의 독립성을 유지하는 것이 얼마나 중요한지를 다시 한번 느낄 수 있었다. 테스트 데이터가 @BeforeEach로 설정되면 모든 테스트에서 공유되는 데이터로 사용될 수 있다. 하지만, 테스트가 추가되거나 변경되면 해당 데이터가 정확히 어떤 테스트에서 필요한 것인지 혼란을 초래할 수 있다. 개인적으로는 각 테스트에서 필요한 데이터를 명시적으로 작성하는 방식이 더 명확하고 안전하다고 생각한다.특히 예외적인 상황을 테스트하거나, 특정 요구사항을 검증할 때 명시적으로 데이터를 정의하는 것이 더 적합하다고 판단했다.Spring REST Docs와 SwaggerSpring REST Docs와 Swagger는 문서화를 위한 좋은 도구들이다.둘 다 장단점이 존재하므로, 팀의 환경과 목적에 맞게 적절히 선택하는 것이 중요하다고 생각한다.Spring REST Docs: 코드 기반으로 문서를 자동으로 생성하며, 정확성과 신뢰도가 높다.Swagger: UI 기반의 문서화가 가능하고 사용자가 직접 API를 테스트할 수 있는 장점이 있다.회고와 느낀 점이번 강의를 통해 테스트의 중요성과 모킹의 활용 방법을 깊게 배울 수 있었다.특히, 단위 테스트와 통합 테스트를 구분해서 작성하는 방법을 익힌 것이 큰 성과라고 생각한다.테스트 코드를 작성할 때 각 레이어의 특징을 이해하고, 적절한 테스트 방법을 적용하는 것이 중요하다는 것을 느꼈다.또한, Mock 객체를 사용할 때는 항상 ‘정말 잘 모킹이 되었는가?’ 라는 의심을 가지고 테스트를 설계해야 한다고 생각한다.앞으로는 테스트를 작성할 때도 더 효율적이고 의미 있게 작성하도록 노력해봐야겠다. 출처 : https://inf.run/EBCNE

백엔드

[4주차 발자국] 보다 더 나은 테스트를 위해

이번 주차는 시간이 많이 부족했던 관계로, 강의를 통한 느낀점 위주로 작성하겠습니다.인프런 ‘Readable Code: 읽기 좋은 코드를 작성하는 사고법’을 수강한 후, 작성한 내용입니다.📌 강의 내용Presentation Layer기존에 프로젝트에서 Presentation Layer 테스트는 건너뛴 경우가 많았다. 대부분 Business Layer까지만 테스트를 작성했고 사실 어떻게 Presentation Layer 테스트를 작성해야할지 잘 몰라서 안했던 것도 컸다,이번 강의를 통해 MockMVC을 사용하여 Presentation Layer 테스트에 대해 익힐 수 있었고, 프로젝트에도 적용해봐야겠다.Mock을 마주하는 자세Mock은 주로 테스트 하는 대상에 대해 집중하기 위해 사용한다. 예를 들어, 외부 클라이언트에 의존하는 기능이 있을 때 실제 이 기능을 사용하면서까지 테스트할 필요는 없다. 이러한 상황에서 Mock을 사용하게 된다.우리는 주로 테스트를 작성할 때 BDD 스타일로 작성하게 되는데, Mock을 사용하게 되면 given 절에 when().thenReturn()을 사용하게 된다. 이러한 상황에 BDDMockito를 사용하게 되면 given 절에서 given().thenReturn() 형식으로 훨씬 자연스럽게 구성할 수 있다.Mock을 사용하는 관점에서 Classicist와 Mockist가 존재한다. 간단하게 말하면, Classicst는 Mock 사용을 최소하하여 꼭 필요한 경우에만 사용하고, Mockist는 따로따로 다 테스트를 할 수 있기에 보장된 기능은 Mocking 처리를 통해 빠르게 테스트를 한다는 입장이다.두 관점에 대해 확실한 정답은 없다. 소프트웨어의 안정성을 고려하면 Classicst의 입장의 손을 들 수 있다. 프로덕션 코드에서 런타임 시점에 일어날 일을 정확하게 Stubbing 하는 것은 확실하지 않고 모르는 일이다.더 나은 테스트를 작성하기 위한 구체적 조언테스트도 하나의 코드다. 테스트를 작성할 때에도 클린 코드에서 배웠던 내용을 상기시키면서 작성해보자. 또한 테스트 코드에서 테스트하기 어려운 영역이 있다면, 이 것이 리팩토링 또는 영역 분리의 신호는 아닌지 생각하자.또한 @BeforeEach 등으로 테스트마다 중복되는 코드를 분리할 수도 있는데, 중복된다고 모두 분리하는 것은 아니어야 한다. 중복이라고 느껴져서 분리하면 테스트에 대한 이해에 걸림돌이 될 수 있다. 예를 들어, 댓글에 관한 테스트인데 댓글 생성을 @BeforeEach로 분리하면, 테스트를 이해하기에 어려울 수 있다.테스트를 확인하기 위해 모든 테스트를 한꺼번에 돌리면, 스프링이 여러 번 띄워지는 경우를 확인할 수 있다. 이는 테스트 마다 설정이 달라서 그런 것인데, 테스트 환경을 통합하여 스프링 서버가 띄워지는 횟수를 줄여 더 빠르게 테스트하고 확인할 수 있게 하자.private 메서드 테스트는 어떻게 하나요?private 메서드 테스트는 할 필요는 없고 해서도 안된다.만약 그런걸 느낀다면, 객체를 분리할 시점인지 고민해보자!테스트에서만 필요한 메서드가 생겼는데, 프로덕션 코드에서는 필요없다면..?만들어도 되지만, 보수적으로 접근하자.어떤 객체가 마땅히 가져도 될만한 행위이고, 미래에도 충분히 사용될 수 있는 성격의 메서드 정도는 가능하다.Appendix새로운 라이브러리를 사용하다보면, 기능에 익숙하지 않다. 보통 검색을 통해 해당 라이브러리를 익히거나 하는데 이떄 테스트를 활용할 수 있다. 테스트 코드를 통해 라이브러리에 대한 행동을 정의하고 검증하는 과정을 통해 구체적인 동작과 기능을 학습할 수 있다.테스트 코드로 API 문서를 작성할 수 있다. Spring REST Docs인데, 사실 전에도 많이 들어봤던 이름이다. 하지만 Swagger가 간단하고 편하게 작성할 수 있으므로 자주 사용했는데, Swagger를 사용해보면 프로덕션 코드가 지저분해진다. Presentation Layer에 Swagger 관련 코드가 붙으면서 지저분해지면서 단점을 크게 느낀 경험이 있다. 다음엔 Spring REST Docs를 꼭 활용하자.📌 미션레이어 아키텍처의 테스트영속성 레이어영속성 레이어?DB에 접근하는 계층비즈니스 로직이 들어가지 않은 순수하게 데이터에 대한 처리 및 조회를 수행영속성 레이어의 테스트무엇을 확인해야할까?원하는 데이터에 정확히 접근하는지쿼리가 길어졌을 때, 내가 원하는 데이터에 맞게 작성되었는지어떻게 테스트를 해야할까?영속성 계층이 의존하는 계층이 대부분 상황에서 없기 때문에 단위 테스트 형식으로 진행비즈니스 레이어비즈니스 레이어?비즈니스 로직이 전개되는 계층영속성 레이어가 사용된다.도메인 개념이 적용트랜잭션 개념 적용비즈니스 레이어의 테스트하나의 트랜잭션을 보장하는지 확인비즈니스 로직이 정확히 수행되는지 확인여러 케이스에 대해 테스트하자.프레젠테이션 레이어프레젠테이션 레이어란?외부 세계와 가장 가까운 계층요청과 관련한 데이터를 받는다.요청에 대한 데이터를 전달한다.프레젠테이션 레이어의 테스트요청에서 건너온 값들에 대한 검증도메인 규칙을 제외한 간단한 검증상황에 대한 정확한 응답이 반환되는지 확인@Mock, @MockBean, @Spy, @SpyBean, @InjectMocksMock과 Spy의 차이Mock: 행위에 대한 기대를 명세하고, 그에 따라 동작하도록 만들어진 객체Spy: Stub이면서 호출된 내용을 기록하여 보여줄 수 있는 객체일부는 실제 객체처럼 동작시키고 일부만 Stubbing할 수도 있다.Stub이란?테스트에서 요청한 것에 대해 미리 준비한 결과를 제공하는 객체. 그 외에는 응답하지 않는다.여기서 그러면 ‘Mock이랑 Stub이랑 무슨 차이지? 같은 내용같은데?’라는 의문이 들 수 있다.https://inf.run/XaZqkstub은 상태 검증, mock은 행위 검증과 관련되어 있다.public interface MailService { public void send (Message msg); } public class MailServiceStub implements MailService { private List<Message> messages = new ArrayList<Message>(); public void send (Message msg) { messages.add(msg); } public int numberSent() { return messages.size(); } } // 테스트 public void testOrderSendsMailIfUnfilled() { Order order = new Order(TALISKER, 51); MailServiceStub mailer = new MailServiceStub(); order.setMailer(mailer); order.fill(warehouse); assertEquals(1, mailer.numberSent()); } Stub은 메일을 몇 번 보냈는지를 나타내는 상태를 검증한다.// 테스트 public void testOrderSendsMailIfUnfilled() { Order order = new Order(TALISKER, 51); Mock warehouse = mock(Warehouse.class); Mock mailer = mock(MailService.class); order.setMailer((MailService) mailer.proxy()); mailer.expects(once()).method("send"); warehouse.expects(once()).method("hasInventory") .withAnyArguments() .will(returnValue(false)); order.fill((Warehouse) warehouse.proxy()); } } Mock은 행위를 중심으로 검증한다.@Mock, @MockBean@Mock어노테이션이 붙은 객체를 Mock 객체로 생성@MockBean어노테이션이 붙은 객체를 Mock 객체로 생성하고, Spring Context에 등록된 빈을 Mock 객체로 대체한다.@Spy, SpyBean@Spy어노테이션이 붙은 객체를 실제 객체 기반으로 만들지만, 일부 기능만 Stubbing 할 때 사용@SpyBean해당 객체를 Spy 객체로 생성하고, Spring Context에 등록된 빈을 Spy 객체로 대체한다.@InjectMocks@InjectMocks 어노테이션이 붙은 객체가 필요로하는 필드 중 @Mock으로 생성된 객체를 주입해준다.BDD 스타일로 테스트 코드 배치하기아래 3개의 테스트가 있다.@BeforeEach void setUp() { ❓ } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { 1-1. 사용자 생성에 필요한 내용 준비 1-2. 사용자 생성 1-3. 게시물 생성에 필요한 내용 준비 1-4. 게시물 생성 1-5. 댓글 생성에 필요한 내용 준비 1-6. 댓글 생성 // given ❓ // when ❓ // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { 2-1. 사용자 생성에 필요한 내용 준비 2-2. 사용자 생성 2-3. 게시물 생성에 필요한 내용 준비 2-4. 게시물 생성 2-5. 댓글 생성에 필요한 내용 준비 2-6. 댓글 생성 2-7. 댓글 수정 // given ❓ // when ❓ // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { 3-1. 사용자1 생성에 필요한 내용 준비 3-2. 사용자1 생성 3-3. 사용자2 생성에 필요한 내용 준비 3-4. 사용자2 생성 3-5. 사용자1의 게시물 생성에 필요한 내용 준비 3-6. 사용자1의 게시물 생성 3-7. 사용자1의 댓글 생성에 필요한 내용 준비 3-8. 사용자1의 댓글 생성 3-9. 사용자2가 사용자1의 댓글 수정 시도 // given ❓ // when ❓ // then 검증 } 내용을 살펴보고, 각 항목을 @BeforeEach, given절, when절에 배치한다면 어떻게 배치해야 할까?(@BeforeEach에 올라간 내용은 공통 항목으로 합칠 수 있습니다. ex. 1-1과 2-1을 하나로 합쳐서 @BeforeEach에 배치)✔ 게시판 게시물에 달리는 댓글을 담당하는 Service Test✔ 댓글을 달기 위해서는 게시물과 사용자가 필요하다.✔ 게시물을 올리기 위해서는 사용자가 필요하다.직접 코드 배치해보기@BeforeEach void setUp() { 사용자 생성에 필요한 내용 준비 사용자 생성 사용자의 게시물 생성에 필요한 내용 준비 사용자의 게시물 생성 } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given 댓글 생성에 필요한 내용 준비 // when 댓글 생성 // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given 댓글 생성에 필요한 내용 준비 댓글 생성 // when 댓글 수정 // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given 사용자2 생성에 필요한 내용 준비 사용자2 생성 사용자1의 댓글 생성에 필요한 내용 준비 사용자1의 댓글 생성 // when 사용자2가 사용자 1의 댓글 수정 시도 // then 검증 } 공통된 준비 작업을 @BeforeEach로 분리댓글을 달기 위한 조건 작업게시물을 작성할 사용자 생성게시물 생성given검증하고자 하는 행동에 필요한 작업 수행댓글 생성과 관련된 내용은 테스트마다 관리할 수 있도록 given 절에 배치when검증하고자 하는 행동이번 중간 점검에서 Day 18에 대한 공통 피드백이 있었다.핵심은 중복 제거가 아닌 도메인사용자, 게시물은 간접적이므로 setUp()으로, 댓글은 직접적이므로 given절@BeforeEach로의 분리는 중복 제거가 아니라 도메인이다. 중복 제거로 접근하면 좋지 못한 테스트로 이어질 수 있다.

백엔드테스트코드

워밍업 클럽 3기 BE 클린코드&테스트(4주차)

섹션 6. Spting & JPA 기반 테스트Persistence Layer 테스트@SpringBootTest : 애플리케이션의 모든 빈을 로드하여 통합 테스트를 수행@DataJpaTest : JPA 관련 빈만 로드하여 테스트Business Layer 테스트Persistence Layer와의 상호작용을 통해 비즈니스 로직을 전개Day18 미션@Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이@Mock : 가짜(Mock) 객체를 생성하고 메서드 호출 시 기본적으로 null 또는 기본값을 반환@MockBean : 기존 spring bean을 mock 객체로 대체@Spy : 식제 객체를 감싸서 부분적으로 Mocking 가능하며 기본 동작은 실제 객체의 동작을 따름@SpyBean : 기존 spring bean을 spy 객체로 대체@InjectMocks : @Mock 또는 @Spy가 주입된 객체를 생성하여 테스트 대상 객체를 자동으로 생성 @BeforeEach, given절, when절에 각 항목 배치@BeforeEach void setUp() { // 공통 코드 사용자 생성에 필요한 내용 준비; 사용자 생성; 게시물 생성에 필요한 내용 준비; 게시물 생성; } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given 댓글 생성에 필요한 내용 준비; // when 댓글 생성; // then 검증; } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given 댓글 생성에 필요한 내용 준비; 댓글 생성; // when 댓글 수정; // then 검증; } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given 사용자1의 댓글 생성에 필요한 내용 준비; 사용자1의 댓글 생성; // when 사용자2가 사용자1의 댓글 수정 시도; // then 검증; }

윤기숙

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

4주차 - 학습 내용 정리1. B2B 이커머스 어드민 페이지 디자인2. B2C 이러닝 페이지3. 모바일 OTT 서비스 페이지  4주차 - 회고 및 느낀 점 😆좋았던 점사실 작년에 시작한 강의를 초반에 보다가 미루고 있었는데, 완강을 할 수 있었던 점이 진짜 최고로 좋았다. (완벽히 다 따라간 강의는 이 강의가 처음이다. 워밍업클럽 덕분이다.)작년에 이 강의를 시작할 때 UI3가 워낙 많이 변경된 부분이 많아서 어려운 부분이 있었는데, 이렇게 강의 업데이트를 해주신 것이 너무 좋았다. 처음에 UI3가 상당히 불편하다고 느꼈는데, 이번 강의 들으면서 베리어블을 이해하면서 왜 피그마에서 이렇게까지 많이 변경하면서 UI업데이트를 강행했는지 이해할 수 있었던 부분도 좋았다.그동안 실습해 본 모든 것을 토대로, 어드민 페이지, 이러닝 페이지, 모바일 페이지에 반응형과 모드를 적용해가면서 실제 사이트 연습하면서 마무리한 것도 좋았다.😅아쉬웠던 점한달동안 매일 조금씩이라도 강의를 들으면서 실습하는 습관이 들었었는데, 이번주에 먼 여행으로 참여하지 못해서 가기 전과 다녀와서 몰아치기로 마무리 했던 것이 아쉽다. 그래도 완강과 미션에 부담이 많았는데, 무사히 끝낼 수 있어서 다행이었다.😍 앞으로 바라는 점이전에 만들었던 디자인 시스템을 이번에 배운 내용을 적용해서 다시 적용해보는 작업을 진행할 수 있으면 좋겠다.피그마에서 처음 다크모드와 함께 베리어블 기능을 선보였을 때 그때 느꼈던 감정이 생각난다. 상당히 놀랍고 신기했지만 저걸 언제 배워서 실무에 쓸 수 있을까? 그런 마음도 있었던 것 같다. 상당히 바쁘게 진행되는 프로젝트에 무언가 새로운 것을 적용하기가 쉽지 않았기 때문이다. 디자이너로서 뭔가 더 개발을 이해해야할 것만 같은 부담감을 느꼈던 것도 같고, 아무튼 시간이 나면 배워보자 하면서 미뤄왔다가 이제서야 이해하게 된 것이 뭔가 좀 후회스러운 마음이 들기도 한다. 이번 일을 계기로 많은 생각을 해보게 된다. 모든 강의를 다시 UI3로 업데이트를 하신 볼드님을 보면서, 디자인 시스템도 마찬가지라는 생각이 든다. 기존 것을 다시 세우는 일은 분명 엄청난 시간과 노력, 그리고 리스크가 있다. 하지만 앞으로 미래를 바라본다면 분명 가치있는 투자가 된다. 다음번 서비스를 구축할 때는 이점을 기억하며서 디자인을 하려고 한다.

UX/UI볼드UX디자인시스템피그마워밍업클럽

박윤영

[인프런 워밍업 클럽 3기 백엔드 ]발자국 4주차

해당 글은 ‘입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기(정보근)’ 강의 를 수강하고 작성한 내용입니다.https://www.inflearn.com/course/입문자-spring-boot-kotlin-포트폴리오/dashboard📝 강의 내용 정리[실습] 스프링 시큐리티 로그인 개발스프링 시큐리티를 사용하기 위해선 의존성 추가 필수build.gradle.kts //스프링 시큐리티 implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity6") testImplementation("org.springframework.security:spring-security-test") 의존성이 추가되면 프로젝트의 모든 경로를 호출할 때 로그인을 요구함AdminSecurityConfiguration해당 설정 클래스에서 스프링 시큐리티를 사용하기 위해 필요한 Bean들을 생성BCryptPasswordEncode: BCrypt 해시 함수를 이용해 암호화된 비밀번호를 생성하는 비밀번호 인코더. 단방향 해시 함수로서 입력된 데이터를 일정한 길이의 해시값으로 변환함SecurityFilterChain: 프로젝트의 보안 필터를 체인 형태로 구성.requestMatchers(AntPathRequestMathcer(”경로”)해당 경로에 대해서만 시큐리티를 요구formLogin :관리자 정보를 저장을 할 테이블 생성 → entity/Account.classUserDetials인터페이스를 상속받아서 생성 → 해당 클래스에 어카운트에 관련된 메서드들 정의되어 있음override fun getAuthorities(): MutableCollection<out GrantedAuthority> = mutableListOf(SimpleGrantedAuthority("ADMIN"))유저의 Role부여계정 관리자가 1명이라는 가정하에 유효계정 체크 등의 기능은 다 true로 리턴AdminSecurityService/Repository레포지퇴에서 로그인 정보로 계정 찾는 메서드 생성서비스에선 로그인 정보로 계정을 찾는 로직 작성(= 이후 검증 로직은 스프링 시큐리티 내부에 구현되어있음!)데이터이니셜라이저에 넣을 비밀번호를 테스트를 통해 얻어옴테스트코드에 BcryptPasswordEncoder를 사용해서 암호 하나 생성 → 생성된 암호 해시코드를 데이터이니셜라이저 코드에 삽입[실습] Docker로 MySQL 실행하기도커를 실행시키려면 커맨드 라인 인터페이스를 활용해야 하는데 매번 터미널에 명령어를 치는 일은 번거로움Docker Compose미리 정의된 파일을 작성해 두면 그 파일의 내용대로 도커 명령어를 생성해서 실행시켜줌도커 컴포즈에는 데이터베이스 비밀번호가 필요함데이터 베이스 비밀번호가 깃에 올라가면 안돼기 때문에 해당 파일은 로컬에서만 저장하도록docker-compose.yml 파일 설명version: '2' services: mysql: image: mysql container_name: mysql ports: - "3306:3306" environment: - "MYSQL_ROOT_PASSWORD=dkssudgktpdy" - "TZ=Asia/Seoul" - "LC_ALL=C.UTF-8" command: - --character-set-server=utf8mb4 volumes: - /var/lib/docker/volumes/mysql/_data:/var/lib/mysql version: 도커 버전service: 현재 프로젝트의 서비스를 구성image: 이미지 이름container_name: 컨테이너 이름→ 도커 허브에서 해당이름의 이미지를 찾아서 다운받은 후 컨테이너를 생성함port호스트포트: 컨테이너포트호스트 포트: 도커 컨테이너가 돌아가는 컴퓨터의 포트컨테이너 포트: 컨테이너 내부에 있는 포트⇒ 도커 안에 컨테이너를 컨테이너 포트로 띄움, 해당 포트를 로컬의 호스트 포트와 연결enviroment환경변수 입력, 비밀번호와 시간대 설정,volumns로컬 컨테이너에 있는 디렉토리와 도커 컨테이너 내부의 디렉토리를 연결→ 그래서 만약 컨테이너를 지웠다가 다시 실행해도 같은 볼륨에 마운트가 되어서 데이터를 유지하고 계속 사용할 수 있음도커 실행방법터미널에서 실행시키기or인텔리제이(도커 플러그인이 깔려있을 시) 내부에서 실행[실습] Docker로 프로젝트 빌드하기DockerFileFROM openjdk:17 LABEL maintainer="pyy2114@gmail.com" VOLUME /tmp EXPOSE 8080 ARG JAR_FILE=build/libs/portfolio-0.0.1-SNAPSHOT.jar ADD ${JAR_FILE} portfolio-yoon.jar ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom", "-jar", "/portfolio-yoon.jar"], "-jar", "/portfolio-yongback.jar"] 도커 빌드 설정인텔리제이로 Dockerfile을 실행할 경우 Edit Configurations에서 아래 옵션을 넣어줍니다.Image Tag: {도커 아이디}/{이미지명}Build options: --platform linux/amd64Apple Silicon 맥북(M1 등)을 사용하는 경우 넣어주세요.Run Gradle task 'portfolio: clean'Run Gradle task 'portfolio: build'⇒ 도커 빌드 시키면 도커 이미지 파일 생성됨도커 이미지 파일을 가지고 컨테이너를 만들어서 실행docker-compose.ymlversion: '2' services: mysql: image: mysql container_name: mysql ports: - "3306:3306" environment: - "MYSQL_ROOT_PASSWORD=dkssudgktpdy" - "TZ=Asia/Seoul" - "LC_ALL=C.UTF-8" command: - --character-set-server=utf8mb4 volumes: - /var/lib/docker/volumes/mysql/_data:/var/lib/mysql portfolio-yoon: image: pyy2114/portfolio-yoon container_name: portfolio-yoon ports: - "8080:8080" environment: - "SPRING_PROFILES_ACTIVE=docker" - "jasypt.encryptor.key=q1w2e3" volumes: - /var/lib/docker/volumes/portfolio-yoon/_data:/tmp depends_on: - mysql portfolio-yoon: 생성한 이미지 이름ports: 스프링부트 기본 포트인 8080environment스프링 프로파일 설정 → docker로 설정되어있으니 application-docker.yml이 프로파일로 실행되어서 해당 환경변수로 셋팅"jasypt.encryptor.key=q1w2e3"db패스워드가 환경변수 타입에 노출되면 안되기 때문에 jasypt라는 라이브러리로 암호화해서 전달할 예정(해당 비밀번호는 암호화 키? 값임, 실제 암호화할 데이터는 안보여질 예정)→ 암호화된 값을 application-docker.yml파일에 넣어주면 됨 datasource: username: root url: jdbc:mysql://mysql:3306/portfolio password: ENC(암호화된 비밀번호 작성) driver-class-name: com.mysql.cj.jdbc.Driver depends_on: mysql이 먼저 실행되어야 정상적으로 동작하므로 순서를 지정해줌Jasypt 구현build.gradle에 의존성 주입implementation("com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.5")domain/configuration에 JasyptConfiguration 클래스 작성다 작성한 후 Dockerfile 빌드(이미지 생성) → dock-compose.yml 실행(도커실행)localhost:8080으로 접속해서 제대로 뜨는지 확인(만약 오류 발생시 이제는 인텔리제이에서 보는게 아니라 도커에서 로그 확인)✅미션[미션6] 가상 프로필을 나의 프로필로 바꾸기미션 6은 꽤나 간단한 미션이였다. 기존 datainitializer에 있는 데이터들을 나의 데이터로 바꿔놓는거였다. 그래서 관련 데이터로 바꿔놓고, 데이터 변경하는 김에 프로필 이미지도 바꿔놓았다.나름 열심히 사이즈를 조절해서 pc에선 알맞게 맞춰놨는데 모바일 화면에서는 깨져나왔다. css로 사이즈를 변경해야되나 싶다..[미션7] 배포한 프로젝트 공유하기드디어 마지막 미션이다. 수업을 따라하면서 도커 이미지 생성과 컨테이너 배포는 완료를 했었지만, 바뀐 데이터를 적용하기 위해 다시 배포를 하였다. 배포를 혼자서는 처음 해보는데 확실히 도커로 빌드를 하니 간편했다. 이미 배포한 후 다시 배포하는 순서는 다음과 같이 정했다. 도커 이미지 재빌드 → 도커 허브에 push → 구글 클라우드에서 해당 이미지 pull → 컨테이너 재실행 위의 방법대로 진행하니 무리없이 배포를 진행할 수 있었다. 구글 클라우드도 처음 써봤는데, 강의를 보고해서 그런건진 몰라도 심플하게 배포가 가능했던 것 같다. 배포를 하고 테스트를 하면서 느낀점이 있는데 페이지 로드가 너무 느리다는 것이다. 무료 버전을 써서 그런건지 정확한 이유는 모르겠지만 현재 프로젝트는 모든 페이지 로드 시간이 너무 길다. 이 이유를 분석하고 해결해보는 것도 좋은 공부가 될 것 같다는 생각이 들었다.📅4주차 회고이번주는 수업과 미션 모두 도커를 이용해 이미지 생성하고 컨테이너를 실행해서 서버를 배포하는 작업이 주 였다. 처음엔 강의 시간을 보고 따라하기만 하면 되니 쉽게 할 수 있을 줄 알았는데 처음 다뤄보는 툴과 플랫폼이다 보니 많이 허덕였다. 그래서 생각보단 배포에 시간이 오래걸렸다. 하지만 한번 하고 나니 도커에 한번 올려놓기만 하면 쉽게 배포가 되어 다시 따라해보는데도 별로 어렵지 않았다. 이래서 다들 도커를 쓰나보다.. 이제 수업과 미션은 다 끝났고 개인 프로젝트 개발만 남았다. 남은 시간동안 짬을 내어 최대한 완성해보고 싶다.마지막 발자국이여서 워밍업 클래스의 전체적인 회고를 해보자면 확실히 모니터링 해주는 사람들이 있고, 기간 등이 정해져 있다 보니 다른 인강을 들을 때보다 더 부지런하게 진행했던 것 같다. 매주 주어지는 미션들도 그 주에 배운 내용들을 복습하기 좋았다. 해당 프로젝트 덕분에 한 달을 나름 알차게 보낸 것 같아 마음이 좋다. 강의에서 배운 지식들을 토대로 실력을 더 디벨롭 시키는 좋은 경험이였다.

찬우 이

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

4주차 학습 내용Part 1. Git Repository 생성 및 초기 설정 진행이번 프로젝트는 기존 강의에서 학습했던 방식대로 GitHub에 레포지토리를 생성하고, 초기 설정을 진행하며 시작했다.폴더 구조와 기본적인 세팅은 이전과 동일하게 구성하여 빠르게 시작할 수 있었다.새롭게 만든 message 테이블에는 다음과 같은 컬럼들을 추가했다.id: 기본 키 (primary key)message: 메시지 내용sender: 보낸 사람의 UUIDreceiver: 받는 사람의 UUIDis_deleted: 삭제 여부 (boolean)created_at: 생성 시간 (timestamp) Part 2. 회원가입, 로그인 화면 제작이전에 하던대로 틀 잡고, components로 구분지으면서 제작했다.새롭게 배운 부분으로는 배경색을 그라데이션으로 준 부분이였다.  테일윈드 css로 편하게 gradient값을 줬다.사용법bg-gradient-to-r: 그라디언트를 오른쪽 방향으로 흐르게 함 / ex) to-r = to right  from-{color}: 그라디언트의 시작 색상 지정 / ex) from-green-400 to-{color}: 그라디언트의 끝 색상 지정 / ex) to-blue-500via-{color}: 그라디언트의 중간 색상 지정 / ex) via-pink-500  Part 3. Supabase Auth 소개 및 인증방식 기획Supabase Auth는 이메일 기반 인증부터 소셜 로그인까지 다양한 인증 방식을 제공하며, 회원가입, 로그인, 세션 유지 등을 쉽게 처리할 수 있는 백엔드 인증 서비스다.1. Confirmation URL 방식사용자 이메일로 인증 링크를 전송하고, 사용자가 해당 링크를 클릭함으로써 인증이 완료되는 방식이다.사용자가 회원가입(또는 로그인)을 하면, 이메일로 인증 링크가 전송되고사용자가 해당 링크를 클릭하면 Supabase가 사용자의 인증을 완료함  2. 6-Digit OTP 방식사용자 이메일로 6자리 숫자 코드(OTP)를 전송하고 사용자가 해당 코드를 입력해서 인증하는 방식  사용자가 이메일을 입력하면, Supabase는 해당 이메일로 6자리 OTP 코드를 전송하고사용자는 입력창에 이 코드를 입력하고 인증을 완료하게 된다.  Part 6. 채팅 화면 구현1. 유저 아바타 랜덤 이미지 사용https://randomuser.me/photos API를 활용해 유저 프로필 사진을 랜덤으로 가져옴별도 이미지 업로드 없이 테스트용 아바타를 빠르게 구현할 수 있음  const randomImage = https://randomuser.me/api/portraits/men/${index}.jpg; 2. javascript-time-ago 라이브러리채팅 메시지 시간 표시를 "1분 전", "3시간 전"처럼 사람이 보기 쉬운 형태로 변환국제화(i18n)도 지원함 (예: 한글/영어/중국어 등)import TimeAgo from "javascript-time-ago"; import ko from "javascript-time-ago/locale/ko.json"; TimeAgo.addDefaultLocale(ko); const timeAgo = new TimeAgo("ko"); timeAgo.format(new Date()) // → "방금 전" Part 7. Supabase Realtime 소개 & 채팅목록 구현 Supabase Realtime은 PostgreSQL 데이터베이스의 변경 사항을 실시간으로 감지해서 클라이언트에 push해주는 기능.기본적으로 WebSocket을 기반으로 작동하고, 내부적으로는 PostgreSQL의 logical replication 기능을 활용한다. 🔧 작동 방식클라이언트가 WebSocket으로 채널 생성.channel() 메서드를 이용해서 원하는 테이블과 이벤트 종류를 구독함예: message 테이블의 INSERT 이벤트Supabase 서버에서 PostgreSQL의 변경 스트림 감지PostgreSQL에서 발생하는 INSERT, UPDATE, DELETE 이벤트를 감지이를 wal2json 등의 logical decoding plugin을 통해 JSON으로 변환Supabase Realtime 서버가 이를 브로드캐스트WebSocket 연결된 클라이언트에게 변경된 데이터(payload)를 push로 전달함프론트에서 실시간 UI 반영받은 payload로 상태를 업데이트하거나 refetch()를 통해 데이터를 다시 불러와서 렌더링Part 9. 배포하기 vercel 배포Add New 파일을 열고 프로젝트를 선택해준다. 나의 깃허브와 연동시켜주고 배포를 원하는 프로젝트를 선택해준다 Deploy를 누르면 배포가 되게 되는데 그전에 프로젝트의 이름을 정하고,프로젝트에서 npm run build를 해서 배포시에 발생할 에러가 있는지 미리 체크한다.그리고 환경변수에는 프로젝트에 있는 .env 파일에 있는 코드를 복사해서 넣어줘야한다. 사진과 같은 에러가 발생했고, 현재 에러를 해결하는 방법은 2가지가 있다.하나는 직접 에러의 원인을 찾아 제거하는 방식 = <Spinner onPointerEnterCapture={undefined} onPointerLeaveCapture={undefined} />)}둘째는 any로 지정하는것 처럼 무시하고 지나가라는식의 방법으로 해결하는 방법이다.루트에 index.d.ts를 만들고, declare module "@material-tailwind/react";를 해줌으로써 material-tailwind로 발생하는 에러는 넘겨주는 것. 배포가 끝났고 이제 휴대폰에서도 정상적으로 동작한다!  미션: 그동안 만든 4개 프로젝트 배포하기선택 미션에는 채팅 메세지 삭제 기능 이나 채팅 읽음, 안 읽음 표시 등 추가 작업인데 우선 후순위로 미루고 배포에 집중함.  미션 이외로미션 이외로 나는 휴대폰으로 채팅하는 모습을 확인하고 싶어서 배포를 진행하고 휴대폰을 확인했다.하지만 이메일 인증을 하고 이동하는 코드는 아래처럼 되어 있어서 연결이 깨지게 되어 있다. const signupMutation = useMutation({ mutationFn: async () => { const { data, error } = await supabase.auth.signUp({ email, password, options: { emailRedirectTo: "http://localhost:3000/signup/confirm", }, }); if (data) setConfirmationRequired(true); if (error) alert(error.message); }, });그렇다고 여기서 저부분을 배포 주소로 바꾸자니, 나중에 버그나 새로운 기능을 넣기 위해 작업할때는 또 번거롭게 바꿔줘야 하기때문에 이 문제를 어떻게 해결할지에 대한 고민을 했다. const redirectUrl = process.env.NEXT_PUBLIC_REDIRECT_URL || (process.env.NODE_ENV === "development" ? "http://localhost:3000" : "https://supabase-instagram-clone.vercel.app"); const signupMutation = useMutation({ mutationFn: async () => { const { data, error } = await supabase.auth.signUp({ email, password, options: { emailRedirectTo: `${redirectUrl}/signup/confirm`, // ✅ 변수 사용 }, }); if (data) setConfirmationRequired(true); if (error) alert(error.message); }, });우선 리다이렉트를 구분하기 위해 로컬호스트에서는 로컬호스트를 보내고 수퍼베이스에서는 수퍼베이스만 보내도록 수정함.버셀에서 환경변수를 추가해주고수퍼베이스에서도 배포한 링크를 연결시켜주니 해결했다.말은 쉬운데 사실 해결하려고 몇시간을 싸매고 한 결과다..ㅠ성공한 사진!!🎉🎉UI적으로는 다듬지 못해서 엉망인 모습을 보이고, 남들이 보기엔 별거 아닐지라도 나한테는 휴대폰과 노트북으로 서로 소통을 하는게 이렇게 돌아가는구나 라는것을 느끼고 되게 신기했다.---📝 마지막 회고4주차는 지금까지 중 가장 힘든 한 주였던 것 같다.평소엔 기능을 하나씩 구현해왔는데 이번 주차는 로그인과 채팅 기능을 함께 다루다 보니 정신없고 벅찼다.점점 기능 난이도가 깊어지고 있다는 느낌도 들어서, 생각보다 어렵게 다가왔다.특히 이번 주에는 코드를 따라 치긴 했지만 이해가 부족했던 순간들도 많았고주말에 큰 약속이 있어서 과제를 빨리 끝내야 한다는 부담감 때문에 급하게 하다 보니 놓친 부분도 많았던 것 같다.그렇게 어느새 4주차.. 이 스터디의 마지막 주차가 되어버렸다.돌이켜보면 한 달 전의 나와 비교해 분명 많이 성장한 시간이었다. 📌 이번 한 달간, 내가 얻은 것들처음엔 next.js와 tailwind를 조금만 다룰 줄 알았고 전역 상태 관리도 zustand 정도만 써봤었다.하지만 이번 강의를 통해→ next.js의 프로젝트 구조 잡는 방식→ tailwind를 더 효율적으로 사용하는 법→ 그리고 새롭게 배운 recoil, supabase, server actions, vercel 배포까지!정말 다양한 기술들을 직접 써보면서 익힐 수 있었다. 🙃 좋았던 순간, 힘들었던 순간강사님 코드랑 똑같이 썼는데도 에러가 나서 괜히 억울했던 순간,반대로 내가 직접 구현한 기능이 잘 작동해서 뿌듯했던 순간도 있었다.물론 한 번 배웠다고 해서 금방 능숙하게 다루진 못하지만이 강의를 통해 전반적인 흐름을 훑을 수 있었고이제는 Supabase를 활용해 어떤 기능을 만들어볼까? 상상해보는 재미도 생겼다. 🎉 마지막으로다음엔 이번 주차에서 만들었던 인스타그램 클론을 좀 더 확장해서게시물 기능까지 구현해보고 싶은 욕심도 있다.좋은 아이디어가 생긴다면? 더 멋있는 기능을 섞어서 구현해보고 싶다...어쨌든, 이번 스터디는 정말 기억에 남는 한 달이었다.한 달 동안 함께 완주해온 스터디 분들 정말 수고 많았고,좋은 강의 만들어주신 강사님께도 진심으로 감사합니다! 🎉

풀스택풀스택미션인프런워밍업클럽supabasenext.js

강현

인프런 워밍업 클럽 스터디 3기 - 백엔드 코드 4주차 발자국

강의 수강섹션7. Mock프로젝트할 때, 다른 팀원이 mock을 이용해 테스트코드 짠것을 보고 살짝 느낌만? 이해해서 테스트를 작성한 경험이 있다. 이번 강의를 수강하면서, stubbing을 한다라는 개념을 이해하고 내가 작성했던 코드를 더 잘 이해할 수 있게 되었다.Mock vs StubMock은 Stub이 아니다.Mock : 행위에 대한 기대를 명세하고 그에 따라 동작Stub : 테스트에서 요청한 것에 대해 미리 준비한 결과를 제공. 그 외에는 응답하지 X둘의 차이는Stub의 목적은 상태 검증Mock의 목적은 행위 검증Mock vs SpySpy란? 객체에서 일부는 Stubbing 하고 싶고, 일부는 실제 객체 동작처럼 하고 싶을때는 Spy를 사용한다.참고디비는 트랜잭션이 끝날때까지 커넥션을 가지고 있는데 메일전송같은 네트워크를 타거나 긴 작업의 경우에는 트랜잭션을 걸지 않는 것이 좋다.섹션8. 더 나은 테스트테스트 간 공유하는 변수같은 공유자원은 쓰지 말자. 각 테스트에서 given 데이터가 겹치는 경우가 많다.중복 제거하려고 beforeach나 beforeall에서 공유 변수들을 생성하려고 하는 경우가 있는데, 이거는 지양하자.이유-> 테스트 간 결합도를 높인다.-> 각 테스트를 이해할 때 자꾸 스크롤하면서 봐야됨.그럼 beforeeach는 언제?각 테스트 입장에서 봤을 때, 아예 몰라도 테스트 내용을 이해하는 데에 문제가 없는가?수정해도 모든 테스트에 영향을 주지 않는가?이 두 조건을 만족하면 beforeach에 있어도 됨.TextFixtur 클렌징deleteAll()order만 지워도 orderProduct까지 같이 지워진다.-> 성능 이슈가 있을 수 있다.엔티티 삭제 순서 고려 해야함.테스트도 비용이기 때문에, deleteAll()은 훨씬 비용이 크다. 그래서 강사님은 deleteAllInBatch() 선호함.이외 유용한 테스트 방법@Parameterized값이나 환경을 바꿔가면서 테스트를 확장하고 싶을 때 사용@DaynimicTest 하나의 환경을 설정하고, 사용자 시나리오를 테스트하고 싶을 때.환경에 변화를 주면서 중간 중간 검증을 하고 싶을 떼 사용테스트 비용같은 스프링부트테스트여도, profile 차이나 환경이 조금만 달라져도 다시 스프링부트가 실행된다. -> 실행 속도 느려짐.테스트 환경 통합하자.private 메소드는 테스트할 필요 X테스트 해야될 거 같으면 객체 분리 필요한지 생각해보고, 분리해서 테스트 수행섹션9. Spring REST Docs스웨거만 사용해봤었는데, 나중에 REST Docs도 프로젝트에 적용해봐야겠다.REST Docs장점 :테스트를 통과해야 문서가 만들어진다.프로덕션 코드를 건드리지 않고 문서 작성 가능단점:코드 양이 많다.설정이 어렵다. Swagger장점 :적용이 쉽다.문서에서 바로 API 호출을 수행해볼 수 있다.단점 :프로덕션 코드를 수정해야 한다.테스트와 무관해서 신뢰도가 떨어진다.미션Day16강의를 수강하면서 짬뽕처럼 뒤섞여 있던 각 레이어별 테스트가 과제를 통해 내용을 적어보면서 머리에서 비교적 정리가 된 것 같다. 그리고 통합테스트, 단위테스트, mock 테스트에 대해 정리해보면서 각 테스트에 대한 개념과 차이에 대해 이해할 수 있었다. 나중에 공부한 내용을 까먹더라도, 테스트할 때 어떤 테스트를 수행할지 찾아보고 적합한 테스트를 골라서 테스트코드를 작성할 수 있을 것이다.Day18강의를 들으면서는 mock과 spy 차이에 대해서만 인지했다. 과제 수행을 위해 인터넷을 뒤져가며 공부를 한 끝에 @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이에 대해 알 수 있었다. 이제 필요한 때에 적절한 mock이나 spy, injectmocks를 사용할 수 있게 되었다. 그리고 given, when, then에 맞추어 테스트 명령어를 정리하는 실습을 통해 시나리오에 맞추어 테스트를 수행한다라는 개념을 이해하게 된 것 같다.

보키

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

마지막까지 KPT 회고 프레임워크를 선택해서 작성해보려고 한다! Keep(만족, 지속하고 싶은 부분)마지막 4주차는 개인프로젝트는 잠시 stop하고 강의와 관련된 부분을 이어 나갔다.Kotlin, Springboot, MySQL, JPA, Docker/compose, Thymeleaf, Bootstrap으로 개발하고 GCP에 Docker로 배포하고 Domain을 구입하고, certbot nginx로 인증서를 발급받아서 https까지 적용했다.2주 쫌 안되게 걸렸는데, 신기했던 부분이 몇 가지 있었다. 첫번째, 코틀린은 언어의 특성으로 인해 주 생성자 부분에 인자를 나열하는 스타일, 주 생성자에는 파라미터만 받고 { } 본문에 val/var등을 붙여 프로퍼티를 만드는 방식, 주 생성자에는 최소한의 인자를 받고 부 생성자로 처리하는 스타일, 주 생성자의 호출을 private으로 막고 정적 팩토리 메서드 패턴을 사용하는 방식 등 다양한 스타일이 나올 수 있는데 지식공유자님은 이 중 어떤 하나의 스타일을 선호하셨다..!! ㅎ내가 말한 것들을 예로 들면 아래와 같은 코드이다.// 스타일 1: 주 생성자 부분에 인자를 나열하는 스타일 class Person1(val name: String, val age: Int) // 스타일 2: 주 생성자에는 파라미터만 받고, 본문에서 프로퍼티를 생성하는 방식 class Person2(name: String, age: Int) { val name = name var age = age } // 스타일 3: 주 생성자에 최소한의 인자를 받고, 부 생성자로 처리하는 스타일 class Person3(val name: String) { var age: Int = 0 constructor(name: String, age: Int) : this(name) { this.age = age } } // 스타일 4: 주 생성자의 호출을 private으로 막고, 정적 팩토리 메서드 패턴을 사용하는 방식 class Person4 private constructor(val name: String, val age: Int) { companion object { fun create(name: String, age: Int): Person4 { return Person4(name, age) } } }참고로 프로퍼티(주생성자에 붙이던, 클래스 바디부분에 붙이던)에 private을 붙인다면 딱 생성자에만 사용할 수 있고 어떠한 getter나 setter로 인한 조작을 제공받을 수 없게된다(코틀린을 써본 사람이 있다면 이걸로 검증할 수 있을 것 같다ㅎㅎ) 두번째, 테스트코드의 클래스 최상단에는 DisplayName을 명시하지 않고 테스트함수(메서드)에만 붙여왔었는데, 강의에서는 최상단의 클래스에도 붙이니 depth가 생겨서 읽기 좋았다. 세번째, 실제 쿼리를 처리하는 JPA또는 CRUD Repository들은 domain 레이어에 두고, 이 repository들을 따로 모아서 presentation/repository 패키지에서 PresentationRepository로 사용하신 부분이었다. 일종의 Facade로 사용하신 것 같은데 강의에서는 처음 봤다. 그리고 이와 관련해서 Spring에는 Repository로 메타데이터를 표시하면(어노테이션), 그 내부에서 호출하는 예외를 DataAccessException으로 Wrapping해준다. 그래서 계속 @Repository를 사용 안하시길래 의아했었는데, Presentation Layer쪽에서 여러 레포지토리들을 사용하는 부분에서 마킹해준걸 보고 오호~! 했다. 네번째, 나는 회사에서 application-{환경}-example.yaml 형태는 github에 올리고 example이 안붙은거는 gitignore에 추가하는 방식으로 사용한다던지.. 아니면 아예 application관련 파일들은 다 gitignore에 추가하고 AWS, GCP 등의 터미널에서 복사해와서 사용한다던지, 외부 환경변수도 그냥 export를 사용한 linux방식으로 외부에서 주입한다던지 요런 방법들만 써왔었고 다른 사람들의 프로젝트에서 우연히 jasypt(JAva Simplified encrYPTion)을 사용한 걸 보긴 했는데 이번 강의에서 처음 써봐서 신기했다. 하지만 현재 프로젝트를 시간이 지나서 관리를 안한다던가, 과거의 것이 되어버린다면 plainPW를 까먹을수도 있으니 어딘가에 기억을 해두고 관리를 해야한다는 점은 있었다. 마지막으로, 거의 끝에 Docker로 빌드하고 Docker-Compose로 사용하는 부분에서 CLI보다는 UI 위주로 알려주는게 좋았다. 개인적으로 나는 docker buildx build --platform linux/amd64,linux/arm64 -t ... 를 사용하는 CLI 방식을 썼었는데 강의에서는 builx를 사용하는 것은 아니었지만, IntelliJ의 UI를 사용하는.. 정확히는 Run Configuration의 IntelliJ Docker 관련 기능을 사용하는 것이 신기했다. 도커 빌드가 실행되기전에 gradle task 2개를 추가하고, 태그를 붙이고 build option을 리눅스에서 돌아가는 환경으로 쫙 쓸 수 있는게 좋았다.나도 전회사에서 프론트엔드분이 도커나 shell script에 대해서 모르셨는데.. 음 WebStorm인가 IntelliJ를 쓰셨는데.. 프론트가 켜지기 전에(npm) 쉘스크립트와 도커컴포즈가 실행되게 Before launch에 Task를 추가해서 프론트엔드분은 프론트 개발에만 치중할 수 있게 셋업해드린 경험이 있었는데 도커로는 처음 써봐서 좋았다.그리고 Docker나 Docker Compose나 Docker Desktop으로 보여줘서 좋았다. 개인적으로는 CLI도 정말 잘써야하지만 그것을 잘 쓸 수 있게 해주는 UI 툴도 잘 사용할 줄 알아야 좀 더 손이 빠른 개발자가 된다고 생각한다. 물론 git/github CLI도 모르면서 UI로만 잘하면 좀 가오떨어지긴하니깐 둘 다 쓸 줄 알아야한다고도 생각한다.UIUI를 사용한 빌드결국 완성한 귀여운 프로젝트 ㅎㅎ 궁금하신 분들은 https://boki-dev.com/로 접속하면 볼 수 있다.메인 화면 Problem(부족, 아쉬웠던 부분)목요일에 어머니가 병원에 입원하셔서 다음날인 금요일에 수술하셨고, 쭉 어머니를 돌보느라 2차 중간 점검 라이브때 참여를 못했다 ㅠㅠ.강의에서 아쉬웠던 부분은.. 음 많은 데이터를 추가한 테이블에서 Pagination이 필요한 경우에는 fetch join으로만으로는 해결이 안될텐데 JPA를 사용하면서 그 부분에 대해서도 있으면 좋았을 것 같다.그 외에는 지식공유자님의 첫 강의였던만큼 부족한 부분이 조금씩은 누구나 있을 것이라고 생각한다. 그리고 나는 약간 젊꼰(젊은꼰대)에 속해서 Backend 영역 즉, Spring을 깊게 공부하고 싶은 사람이라면(내 생각)1. CS & Java Language(언어 학습)2. Java Application Project(프로젝트 실습) - 순수 Java프로젝트, JDBC정도3. JVM, GC 공부4. Spring(프레임워크 학습)5. Spring boot(프레임워크 학습2)6. Springboot Project(프로젝트 실습)7. Kotlin(언어 학습)8. Kotlin/Springboot Project(최종)이렇게 가야지 자신의 실력이나 깊이에 대해서 좌절 또는 현타가 좀 덜 오는 현업에서 부족함 없는 실력자 될 수 있지 않을까 생각하는데..얕게 알고 취업하면 언젠가는 자신의 개발실력에 헌타가 오기도 하고 그러더라..주위에서 보기도 했고 나도 조금은 겪었었다(나도 알고싶지 않았다). 근데 이렇게 말하면 꼰대소리 들으며 왜 코프링(코틀린스프링)부터 시작하면 안되냐고 반문할 지도 모르겠다..ㅎㅎ Try(도전할 부분)강의를 보고 만든 프로젝트는 완성했으니, 기존의 내 개인 프로젝트로 돌아가서 계속 살과 뼈를 붙여가며 완성해보고 싶다.백엔드도 첨에 사이즈를 크게 잡아서.. Redis도 RedisTemplate, Spring Annotation 방식(@Cacheable..), Repository(@RedisHash) 방식들 차이에 대해서 알아보고 더 잘 사용하고, Kotlin에 좀 더 친화적인 Kotlin JDSL도 사용해보고 활성유저의 트래킹이나, 매니저->작업자의 Task 할당에 MQ를 사용한다던지 기능추가를 하며 고도화를 해보고 싶다.프론트쪽은 현업에서 Angular/Vue를 주로 썼었는데, React/Ts 강의도 들어서 프론트엔드 부분은 CRA로 만들 것이다.

풀스택인프런워밍업클럽백엔드프로젝트KotlinSpringbootJPA스터디워밍업클럽스터디

김태영

[인프런 워밍업 클럽 3기 BE 클린코드 & 테스트 스터디 ] 발자국 4주차

[4주차] 섹션7Mockito로 Stubbing하기외부 서비스를 테스트하려면 실제로 수행하거나, Mocking하여 가짜 객체를 만들어서 사용Mocking하여 Mock 객체의 응답을 정해줌으로써, 외부 서비스와의 독립성을 유지외부 서비스가 있어도 간단하게 서버 로직에 대한 검증 가능Test DoubleDummy : 아무것도 하지 않는 깡통 객체Fake : 단순한 형태로 동일한 기능은 수행하나, 프로덕션에서 쓰기에는 부족한 객체Stub : 테스트에서 요청한 것에 대해 미리 준비한 결과를 제공하는 객체 그 외에는 응답하지 않는다.Spy : Stub이면서 호출된 내용을 기록하여 보여줄 수 있는 객체, 일부는 실제 객체처럼 동작시키고 일부만 Stubbing할 수 있다.Mock : 행위에 대한 기대를 명세하고, 그에 따라 동작하도록 만들어진 객체Stub -> 상태 검증, Mock -> 행위 검증@Mock, @Spy, @InjectMocksMock은 정해진 행위를 하는 객체를 만드는 것Spy는 실제 객체의 메서드를 수행하지만, 특정 메서드만 mocking한 것InjectMocks는 Mock과 Spy 객체를 자동 주입해주는 것BDDMockito행동주도개발TDD -> when().thenReturn(), BDD -> given().willReturn()Classicist VS MockistClassicist : Mock을 통한 검증은 완전함을 보장할 수 없다.Mockist : Mock을 통해서 각각 테스트를 성공했으면 된다. 섹션 8한 문단에 한 주제완벽하게 제어하기현재 날짜, 현재 시간 등 제어할 수 없는 값은 파라미터로 넘겨서 상위 계층에서 관리테스트 환경의 독립성을 보장하자테스트가 실패하는 부분은 when절 또는 then절이어야 한다. given 절에서 실패하면 안된다.given 절에서 객체를 생성할 때는 순수한 생성자 또는 builder 기반으로 만들면 좋다.테스트 간 독립성을 보장하자테스트 간 공유 자원을 사용하면 안된다.공유 자원을 사용하면 테스트 간 순서에 따라 성공, 실패가 갈린다.한 눈에 들어오는 Test Fixture 구성하기@BeforeEach를 사용하여 설정할 경우, 각 테스트 입장에서 봤을 때, 아예 몰라도 테스트 내용을 이해하는데 지장이 없어야 한다.뇌의 메모리를 적게 사용Test Fixture 클렌징deleteAll, deleteAllInBatch@ParameterizedTestenum 클래스의 각 값에 대하여 테스트를 하고 싶을 때, ParameterizedTest를 수행하면 좋다.ValueSource() 파라미터가 1개일 때NullSOurce, EmptySource, NullEmptySourceCsvSource() csv 형식으로 여러개의 파라미터 사용여러 개의 파라미터 값을 테스트하고 싶을 때 사용@DynamicTest한 문단에 한 주제, 하지만 연속된 상황을 검증하고 싶다면?연속된 상황을 부여하여, 일련의 시나리오를 테스트할 수 있다.테스트 수행도 비용이다. 환경 통합하기테스트 환경이 달라지면, 서버가 각 테스트 클래스마다 새로 부팅한다.통합 테스트 클래스를 만들어서, 통합 테스트 클래스가 실행될 때 1번 서버가 실행되도록 한다.MockBean, SpyBean 등 환경이 달라지면 통합 테스트 클래스를 상속받아도 서버가 새로 부팅된다.MockBean, SpyBean을 통합 테스트 클래스에 옮겨놓으면 1번으로 유지 가능DataJpaTest를 repository 테스트를 할 때 쓴다면 이것도 환경이 달라졌으므로, 서버를 새로 부팅WebMvcTest, SpringBootTest, DataJpaTest 각각 통합 테스트 클래스를 만들어주면 3번만 부팅private 메서드의 테스트는 어떻게 하나요?private 메서드는 테스트할 필요 X외부로 노출되지 않는 기능까지는 알 필요가 없다.단위 테스트 검증 시에 어차피 검사하게 된다.그래도 하고 싶다면, 객체 분리를 통해서 public으로 만든 후 검증테스트에서만 필요한 메서드가 생겼는데 프로덕션 코드에서는 필요 없다면?만들어도 되지만, 지양하자 [회고]벌써 마지막 주차가 되어버렸다. 1달 동안 클린코드와 테스트코드에 대해 전혀 무지했던 내가 개념을 익히고 실습을 하며어떻게 하면 더 깔끔하게, 더 읽기 편하게 적을 수 있을까를 고민하게 되었고, 어떻게 검증해야 할까, 무엇을 검증해야 할까를 고민할 수 있게 되었다.1달이라면 길다면 길고, 짧다면 짧은 시간 동안 한층 더 나아갔고, 더 우수한 사람이 되기 위해 노력했다. 한달 전의 나보다 성장했기에 뿌듯하고, 앞으로 성장할 나를 위해 다시금 노력하자워밍업 클럽을 통하여 궁금했던 질문도 하였고, 앞으로 나아갈 길과 동기를 얻을 수 있었다. 다음 기수에도 신청하고 싶다  [출처]인프런 워밍업 클럽 : https://inf.run/Y4cf2강의 : https://www.inflearn.com/course/readable-code-%EC%9D%BD%EA%B8%B0%EC%A2%8B%EC%9D%80%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%EC%82%AC%EA%B3%A0%EB%B2%95/dashboard

test

정기석

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

회고변경이 있을때 테스트는 깨져야 한다. 변경의 영향도를 확인할 수 있기 때문처음 테스트를 작성할때는 클래시스트였는데 테스트가 깨지는게 나쁘지 않다면 속도를 고려해서 mock을 활용하는 게 더 좋지 않을까?어차피 각 계층별 테스트를 하는데 하위 계층을 모킹을해서 속도를 높이는게 더 좋을 거 같다.미션@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의 댓글 수정 시도 검증 }

겸손한 북극곰

[워밍업-백엔드3기] Practical Test 2주차와 워밍업 완주 회고

테스트 코드의 목적을 이해하려는 목표로 워밍업을 완주했다. 🔢 테스트코드의 두번째 학습Practical Testing: 실용적인 테스트 가이드2주차 새롭게 배운 내용Layered Architecture에서 레이어별 역할은 나눠져야 한다. 테스트도 마찬가지Mock 을 활용해서 테스트 하려는 클래스에 집중할 수 있다.Mock/Spy 의 차이실제 프로세스를 신뢰할 수 있도록 Mock과 실제 객체의 적당한 활용이 필요Test code는 결국 문서다.테스트는 독립적이고 완전히 제어할 수 있어야한다.@ParameterizedTest 와 @DynamicTest 를 활용한 시나리오 멀티 테스팅 🤔 워밍업 4주차 회고라이브 코딩을 따라가며, 필요하면 스스로 TDD 절차에 따라서 테스트 코드를 작성하기도 하였다.단순히 Junit과 Mockito 의 사용법을 이해하는 과정이 아니라,어떤 목적으로 테스트 코드를 작성하고 이것을 협업 관점을 고려하며 무엇을 조심해야하는지 배울 수 있었다. Readable Code 와 Practical Test 두 강의를 묶어서 워밍업 플랜이 나왔을 때는 의아했다."테스트 코드랑 클린 코드랑 무슨 관계지?" 강의가 끝나고 깨달은 것은 구현하는 코드도 테스트하는 코드도 모두 문서다.팀에서 함께 보며 누구나 쉽게 읽을 수 있어야 한다. 라이브 코딩이지만 스킵할 수 있는 장면은 없었다.중간 중간 많은 꿀팁과 좋은 개발자로 성장하기 위한 이야기들을 해주셨다. 워밍업 클럽을 통해 평소보다 2배는 많은 양을 빠르게 학습 할 수 있었고,라이브 중간점검을 통해서, 다른 사람들과의 코드 공유 뿐만아니라 가치있는 이야기들을 들을 수 있었다.앞으로도 박우빈님 강의와 워밍업 클럽을 들을 기회가 생긴다면 또 참여할 것이다.   

채널톡 아이콘