블로그

whffkaos007

워밍업 클럽 2기 BE 클린코드&테스트 - 회고 1회

해당 회고는 박우빈님의 'Readable Code : 읽기 좋은 코드를 작성하는 사고법' 강의를 참조하여 작성했습니다. 범위는 섹션 1 ~ 4 입니다.회고와 같은 정리는 가끔 귀찮기도 했지만 결과물에 대한 정리와 점검할 수 있는 시간을 준다고 하니 좋은 마음으로 기록하는 습관을 기르겠습니다! 미션   public boolean validateOrder(Order order) { if(notExistItemsFrom(order)){ // 주문에 상품 목록이 존재하면 log.info("주문 항목이 없습니다."); return false; } if (isNotVaildTotalPrice(order)) { // 총액이 유효한 값인지? log.info("올바르지 않은 총 가격입니다."); return false; } if (hasNotCustomerInfoFrom(order)) { // log.info("사용자 정보가 없습니다."); return false; } log.info("주문 항목이 없습니다."); return true; } private static boolean hasNotCustomerInfoFrom(Order order) { return !order.hasCustomerInfo(); } private static boolean isNotVaildTotalPrice(Order order) { return order.getTotalPrice() <= 0; } private static boolean notExistItemsFrom(Order order) { return order.getItems().size() == 0; } 주문에 대한 검증 로직1. 주문 안에 상품이 존재하는가? 없다면 잘못된 주문으로 판별2. 상품의 가격이 1원 이상이라면 정상 처리 / 아니라면 잘못된 주문으로 판별3. 주문에 대한 사용자 정보가 있어야 한다. 없다면 잘못된 로직이다. 적용할 수 있는 읽기 좋은 코드 방식1. 사고의 depth 줄이기2. 부정구 지양하기3. 최대한 추상적으로 접근하기 ( getter로 직접 접근하기 X)4. early return 사용하기 각 조건을 모두 통과해야 유효한 주문이라는 결과를 도출해야 한다. 따라서 비즈니스 로직을 지키며 위 리팩토링 기준을 모두 적용하려 했으나 'not'과 같은 부정어를 사용했다. 이에 대한 이유if 조건 안에 && 처리를 하여 긍정구로 표현하려 했으나 조건이 길어지면 오히려 기억해야 할 정보가 많을 거 같아 이와 같이 부정구를 사용하지만 depth와 if문마다 하나의 조건만 담기 위해 이와 같이 리팩토링했다.   인상적인 부분 테스트 코드와 클린 코드에 대한 중요성은 들어봤지만 기능 구현조차도 쉽지 않다 보니 평소 읽기 좋은 코드에 대해 접해볼 시도조차 없었습니다. 강의 도입 섹션에서는 읽기 좋은 코드의 중요성에 대해 설명하시는데 클린 코드에 관한 새로운 관점(?)도 알 수 있어 좋았고 읽기 좋은 코드 작성에 대한 이유와 실제 적용을 통해 진행해서 더 마음에 와닿았습니다.  학습 정리강의 내용과 제가 따로 정리한 부분이 섞여 있으니 참고해주세요.방대한 강의 내용과 강의에서 제공한 모든 내용 제시를 피하기 위해서 인상 깊었던 섹션 1,2 위주로 정리하겠습니다. 1. 클린 코드와 추상 관계 왜 클린 코드를 추구하는 것일까?결국, 클린 코드는 읽기 쉬운 코드이며 적절히 추상화가 이뤄진 코드이다. 이러한 코드는 유지 보수에 들어가는 시간과 비용을 절약해준다.극단적으로 확장 가능성이 없거나 그 순간에 개인만이 서비스에 대한 코드에 접근하고 작성한다면 보편적인 클린 코드 대신 본인이 알아보기 쉬운 형태의 코드로 작성하는 것이 더 합리적일 수도 있다.하지만 이와 같은 경우보다는 거의 없기에 클린 코드를 추구하는 것이다. 프로그램의 정의는 무엇일까?다양한 의미가 있겠지만 다음과 같이 정의할 수 있다.‘프로그램 = 데이터 + 코드’데이터는 어떠한 정보 자체를 의미하고 코드는 어떠한 논리적 행위를 의미하며 데이터와 데이터 간, 데이터와 코드 간 등 여러 관계에서 논리적 행위가 일어날 수 있다. 그럼 데이터와 코드는 실제 우리가 작성하는 코드에서는 어떤 것을 의미할까?데이터는 객체, 클래스가 가지는 값이 있으며 객체가 어떤 행위를 수행하는 의미를 가진 메서드 선언부를 나타낼 수 있다.(반환값, 파라미터, 메서드명 등)코드는 메서드의 내부 행위 값이 있고 크게는 코드 간 여러 복합적인 메서드 간 상호 작용으로도 볼 수 있다.위 코드에 대한 예시를 보면 calculateChangeMoney 메서드 선언부와 Person 필드 등이 데이터가 볼 수 있고 calculateChangeMoney, donamteMoney 등 메서드에 내부 로직을 코드로 볼 수 있다. 도대체 읽기 좋은 코드에 대한 객관적인 기준이 뭐야?읽기 좋다는 것은 바로 추상과 구체에 의해 나타난다.하나의 예시를 보자.<aside> 💡나는 누군가 쳐다본다. 일반적인 ‘보다’ 라는 느낌과는 다르다. 주위에 있으면 한순간도 놓치지 않고 어떤 표정을 짓는지, 무엇을 하는지 바라보게 된다.이 사람이 슬퍼하기 보단 웃고 행복했으면 좋겠고 이 사람을 생각하면 가슴이 두근두근 뛴다. 슬퍼하면 나도 슬프고 행복해 보이면 괜히 나도 행복해진다.이 사람이 좀 더 행복할 수 있다면 내가 좀 더 손해를 봐도 좋다. 손해를 보면 기분이 나쁘거나 우울한 것과 같이 부정적인 생각이 들어야 하는데 오히려 손해를 보는 것이 더 좋다.</aside>이러한 내용은 무엇을 나타낼까? 하나의 단어로 표현할 수 있다.‘사랑’ 이란 단어로 표현 가능하다. 여기서 ‘사랑’은 위 내용에 대한 추상이며 위 내용은 ‘사랑’이란 단어에 대한 구체이다.2가지 문장 중 어떤 것이 읽기 편하고 합리적이라 생각하는 가?<aside> 💡1번너 요즘 사랑하는 사람이 있어?2번너 요즘 어떤 사람을 보면 가슴이 콩닥콩닥 뛰고 그 사람만 쳐다 보고 싶고 다 해주고 싶고 손해를 봐도 전혀 아쉽지 않고 행복하길 바라고 감정을 나누고 싶은 사람이 있어?</aside>보편적으로 1번이 읽기 편하고 합리적일 것이다.여기서 추상과 구체의 관계를 알 수 있으며 적절한 추상화는 문장을 이해하는 데 적은 비용과 시간이 든다. 쉽게 이해할 수 있기 때문이다.이러한 추상과 구체는 우리가 작성하는 코드에도 적용되고 있다. 결론적절한 추상화는 복잡한 데이터와 복잡한 로직을 단순화하여 이해하기 쉽도록 돕는다. = 읽기가 좋다.우리는 추상과 구체의 관계를 알 수 있으며 추상이 어떤 역할을 하는 지도 알 수 있었다. 추상화를 하면 우리는 복잡한 것을 쉽게 이해할 수 있으니 무조건 추상화를 해야 할까?무분별한 추상은 좋지 않다. 과한 추상은 구체를 유추하지 못할 수 있다. 지속적으로 언급한 ‘적절한 추상화’만이 읽기 좋은 효과를 가져올 수 있다.어떠한 경우가 추상으로부터 구체를 유추하지 못할까?<aside> 💡나는 밤이 좋아졌잘싸나는 친구랑 샤우팅 갔어 </aside>위 3가지 예시에 구체적인 의미를 알 수 있는가?1번은 먹는 밤인지 시간에 따라 나타나는 밤인지 알 수 없다.2번은 ‘졌지만 잘 싸웠다’라는 의미지만 과하게 줄인 탓에 알 수 없었다.3번은 샤우팅이라는 단어의 의미를 알 수 없었다. 소리 지르는 행위에 대한 미미한 유추만 가능하다.위 예시에 대한 문제점이다.추상화 과정에서 중요한 정보를 부각시키지 못했다.상대적으로 덜 중요한 정보를 남기고 중요한 정보는 제거했다.해석자가 동일하게 공유하는 문맥이 없다.중요한 정보의 기준이 다를 수 있다.도메인 영역 별 추상화 기준이 다를 수 있다.즉, ‘잘못된 추상화’는 오히려 추상화 안 한 것보다 못할 수 있다. 야기하는 side-effect는 생각보다 정말로 크다.‘적절한 추상화’는 해당 도메인 문맥 안에서, 정말 중요한 핵심 개념만 남겨서 표현하는 것이다.이름 짓기로 추상화 하기메서드와 추상화 한 문단의 주제는 반드시 하나다.잘 쓰여진 글이라면, 한 문단의 주제는 반드시 하나다. 0개도 2개도 아닌, 무조건 1개이다.실제로 국어/영어 시험에서도 주제가 1개가 아니라면 답이 오직 1개라고 정의하기 힘들다.문장과 문장에 대한 주제는 마치 코드에서의 메서드 선언부와 메서드 로직과 같은 역할을 한다.메서드 이름으로 구체적인 내용을 추상화한 것이다. 다른 예시를 보자.메서드의 로직 내부에서는 2가지 이상의 일을 하고 있다.메서드에서 수행하는 일산책하기, 은행가서 현금 인출하기, 음식점에서 밥 먹기, 책 구매하기오른쪽 메서드를 보자. 더 큰 맥락 안에서 포괄적인 의미를 담았다.즉, 잘 쓰여진 코드라면 하나의 메서드의 주제는 반드시 하나이다.메서드 선언부반환타입메서드 시그니처에 납득이 가는, 적절한 타입의 반환값 돌려주기→ 반환 타입이 boolean인데, 이게 이 메서드에서 무엇을 의미하는거지?void 대신 충분히 반환할 만한 값이 있는지 고민하기→ 반환값이 있다면 테스트도 용이해 진다. 결과값이 없다면 상태와 행위 중, 행위밖에 검증할 수 없다.메서드명추상화된 구체를 유추할 수 있는, 적절한 의미가 담긴 이름파라미터와 연결지어 더 풍부한 의미를 전달할 수 있다.파라미터타입, 개수, 순서를 통해 의미를 전달외부 세계와 소통하는 창String createDailyShopKey(String shopId, String localDateString){ return String.format("%s_%s", shopId, localDateString); } String createDailyShopKey(String shopId, LocalDate sellingDate){ return String.format("%s_%s", shopId, sellingDate.toString()); } 위 두 메서드 중 어떤 것이 더 잘 추상화를 했을까?//S34_2024-06-01 String shopId = "S34"; LocalDate today = LocalDate.of(2024, 6, 1); //1 String dailyShopKey = createDailyShopKey(shopId, today.toString()); //2 String dailyShopKey = createDailyShopKey(shopId, today); 정답은 2번이다. 이유가 뭘까?이유날짜를 String 값으로 나타낼 때, 다양한 형식이 있다. 2024-06-01, 2024.06.01 등 어떤 형식의 문자열로 넘길지 고민하게 된다.따라서 LocalDate 자체로 넘기면 날짜의 의미를 가진 LodatDate 구체화된 타입을 넘기면 된다라는 명확함을 인지할 수 있다.sellingDate와 localDateString의 의미를 보면 메서드 행위에 대한 의미를 보면 sellingDate가 더 많은 정보를 제공하므로 파악하기 쉽다.localDateString에 비해 sellingDate는 판매날짜의 의미를 담고 있다. 추상화 레벨 서점에 위와 같이 진열대에 책이 나열되어 있다. 여기는 여러 책이 진열되어 있는 공간이며 책의 제목이란 추상을 통해 책의 내용을 유추할 수 있다. 그런데 책의 구조인 제목, 목차, 내용 순이 아닌 단순 서류 뭉치로 이루어진 책 하나가 있다.이상하지 않는가? 서류 뭉치를 본 순간에 책인지, 그냥 단순 서류 뭉치인지 알 수 없다. 저 서류 뭉치의 존재에 대해 의문이 들고 이해하는데 비용이 발생한다.void method(){ ....... T t = extracted(p); .... } T extracted(P p){ ...something.... } 우리는 메서드 구현부를 확인할 때, 위처럼 extracted 메서드와 같이 추상화된 내부 메서드를 본다. 추상화된 메서드명의 주제에 대해 더 궁금하면 해당 메서드 내부에 들어가 확인할 것이다.이는 외부 세계와 내부 세계로 나뉠 수 있으며 method 내부에 메서드 선언부로만 표현된 extracted(p)가 경계가 된다.이는 외부 세계는 추상화 레벨이 높고, 내부 세계는 추상화 레벨이 낮음을 의미한다. 당연히 메서드 구현부에는 메서드 선언부에 대한 구체적인 내용이 있기 때문이다.하나의 세계에서는 추상화 레벨이 동등해야 한다.우리는 진열된 책을 예시로 봤다. 코드로 직접 봐보자.public static void main(String[] args) { showGameStart(); 10 initializeGame(); 10 showBoard(); 10 .... if (gameStatus == 1) { 5 System.out.println("지뢰를 모두 찾았습니다. GAME CLEAR!"); break; } ..... 10 } 10, 5는 예시로 추상화 단계를 수로 표현했다. 10이라는 추상화 단계를 가다가 ‘gameStatus == 1’ 이라는 구체에 가까운 5라는 추상화 단계를 만나며 코드를 이해하는데 혼란을 줄 수 있으며 추가적인 논리가 더 필요로 하다.그렇기에 추상화 단계를 맞춰주는 것은 중요하다.   진행할 점강의의 양이 매우 방대하여 깔끔하게 정리하기 쉽지 않은 거 같다. 추상과 구체의 관계에 대해 학습했으니적절한 추상화를 통해 내용을 정리해서 상대방에게 이해하기 쉬운 글을 제공할 수 있게 연습해봐야겠다.    

백엔드읽기좋은코드클린코드

유진

4주차 발자국 | 인프런 워밍업 클럽 2기 - 백엔드

인프런 워밍업 클럽 2기입문자를 위한 Spring Boot with Kotlin(https://inf.run/bXQQQ) 강의를 듣고 작성하였습니다Spring SecuritySpring Security는 Spring Boot 애플리케이션에 보안 기능을 손쉽게 통합할 수 있는 프레임워크입니다. 인증(Authentication)과 권한(Authorization) 관리 기능을 제공하여 애플리케이션을 보호하는 데 사용되며, OAuth2, JWT 같은 다양한 보안 프로토콜도 지원합니다.dependencies { implementation("org.springframework.boot:spring-boot-starter-security") }@Configuration class SecurityConfiguration { @Bean fun filterChain(httpSecurity: HttpSecurity): SecurityFilterChain? { return httpSecurity .authorizeHttpRequests { authorizeHttpRequests -> authorizeHttpRequests .requestMatchers(AntPathRequestMatcher("/**")).authenticated() .anyRequest().permitAll() }.csrf { csrf -> csrf.disable() }.headers { headers -> headers.addHeaderWriter(XFrameOptionsHeaderWriter(XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)) }.formLogin { formLogin -> formLogin.defaultSuccessUrl("/") }.logout { logout -> logout.logoutRequestMatcher(AntPathRequestMatcher("/logout")) .logoutSuccessUrl("/") }.build() } } /* * NestJS에서는 strategy, Guard 를 사용하여 인증/인가처리를 구현합니다. * strategy 에서는 JWT, oauth 등의 보안 프로토콜을 정의하고 적용할 수 있습니다. * Guard를 정의하고 개별 request 위에 데코레이터로 적용할 수 있습니다 (global 적용도 가능) */ password encodehttps://velog.io/@glencode/Spring-Security-Crypto를-사용한-비밀번호-암호화시큐리티에 관한 강의를 아직 듣지 않았을 때, 패스워드 암호화를 해야하는 요구사항이 있어서 적용해보았던 내용을 기록합니다.dependencies { implementation("org.springframework.security:spring-security-crypto") }@Configuration class AuthConfig { @Bean fun passwordEncoder(): PasswordEncoder { return BCryptPasswordEncoder() } }의존성을 추가하고 config 를 정의합니다@Service class UserService( private val userRepository: UserRepository, private val passwordEncoder: PasswordEncoder, // and so on.. ) {...}/* * dto.password는 사용자로부터 입력받은 password * user.password는 DB에 encode 하여 저장된 password */ // encode val encodedPassword = passwordEncoder.encode(dto.password) // compare if(!passwordEncoder.matches(dto.password, user.password)) { throw BadRequestException("비밀번호가 틀렸습니다.") }passwordEncoder 를 주입하고 위 매서드를 사용하여 활용할 수 있습니다. /* * nodejs 에서는 bcrypt를 사용하여 구현할 수 있습니다. * const bcrypt = require('bcrypt'); */ // encode const encodedPassword = await bcrypt.hash(password, 10); // salt = 10 // compare if (!(await bcrypt.compare(password, user.password))) { throw new BadRequestException("비밀번호가 틀렸습니다.") } ControllerAdvice@RestControllerAdvice는 @ControllerAdvice와 @ResponseBody의 기능을 결합한 어노테이션으로, REST API에서 예외를 처리할 때 주로 사용됩니다.모든 컨트롤러에 대해 JSON이나 XML과 같은 형태로 일관된 응답을 반환할 수 있습니다.@ControllerAdvice는 Spring MVC에서 예외 처리, 데이터 바인딩, 모델 객체의 변환 등을 전역적으로 관리할 수 있게 도와주는 어노테이션입니다.서버 내부에서 입력 오류에 대한 경우를 전부 BadRequestException 으로 사용하고 message 를 다양하게 주고 있었는데, 실제로 에러가 발생했을 때 message가 오지 않아서 몹시 불편함을 느꼈습니다.직접 입력한 메시지를 응답에 뿌려주기 위해 검색 후에 아래와 같은 GlobalExceptionHandler 를 구현하였습니다.@RestControllerAdvice class GlobalExceptionHandler { @ExceptionHandler(BadRequestException::class) fun handleBadRequest(ex: BadRequestException): ResponseEntity<Map<String, String>> { val errorResponse = mapOf( "timestamp" to LocalDateTime.now().toString(), "status" to HttpStatus.BAD_REQUEST.value().toString(), "error" to "Bad Request", "message" to (ex.message ?: "잘못된 요청입니다."), ) return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse) } }# 에러 응답 예시 { "timestamp": "2024-10-23T04:22:59.959124", "status": "400", "error": "Bad Request", "message": "먼저 입실해주세요" } /* NestJS ExceptionFilter * 위와 동일한 작업을 하는 코드 */ @Catch(BadRequestException) export class GlobalExceptionFilter implements ExceptionFilter { catch(exception: BadRequestException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const message = exception.getResponse() as string; const errorResponse = { timestamp: new Date().toISOString(), status: HttpStatus.BAD_REQUEST, error: 'Bad Request', message: message || '잘못된 요청입니다.', }; response.status(HttpStatus.BAD_REQUEST).json(errorResponse); } } 작은 회고처음의 결심과 다르게, 강의 일정을 따라가는게 쉽지않았습니다. (게으르기 때문일까요~,,) 그래도 계속 하다보니 아주 조금...? 스프링에 대해 알아가는 느낌이 들어 정말 재밌었습니다. 꾸준히 노력해서, 프레임워크 상관없이 능숙하게 작업할 수 있는 백엔드 개발자가 되겠습니다.프로젝트에 아직 @TODO 가 많은데, 시작한 프로젝트는 돌아오는 주까지 열심히 해서 잘 마무리하고 싶습니다. 학부생 때가 HTML의 마지막이라, 타임리프 작업이 가장 오래걸렸습니다... (사실 아직도 끝나지 않았습니다)원래 쓰던 프레임워크와 비교해가며 이해하고 공부하는데, 이게 도움이 되었는지 아니었는지는 아직 잘 모르겠습니다. 아마 더 많이 사용해보는 시간이 필요할 거 같습니다. 공부법이든, 코딩 습관이든, 나만의 Best Practice 를 찾아가는 과정은 참 어려운거 같아요. 이번에 워밍업클럽을 통해 (!오랜만에!) 완강도 하고, 프로젝트도 하고, 강사님과 컨택할 수 있는 시간도 가지게 되어 정말 좋았습니다. + 온라인 세션에서 받은 이력서 피드백이 정말 많은 도움이 되었습니다. 이 자리를 빌려 감사의 마음을 전해드립니다 🙂다음번에도 이런 기회가 온다면 다른 강의로 신청해보려고 합니다. 이 과정을 기획하고 관리하신 인프런 운영자님과, a-z까지 한 과정에 깔끔하게 담아내신 강사님 정말 고생많으셨습니다! 수료식에서 보아요 ( •͈૦•͈ )

backend

인프런 워밍업 클럽 백엔드 4주차 후기

후기 드디어 인프런 워밍업 클럽의 마지막 발자국을 쓰게 되었다.길다면 길고 짧다면 짧은 4주동안 두 개의 강의를 들으면서 많은 것을 느낄 수 있던 시간이었다.Readable Code에서는 추상을 통해서 메시지를 잘 만들고, 책임과 역할을 잘 분배하여 읽기 좋은 코드를 만들 수 있도록 노력해야 겠음을 느꼈고, Practical Test에서는 좋은 테스트 코드를 만들어서 사람이 노가다 하는 비중을 최대한 줄이면서, 안정적인 코드를만들어야겠음을 느꼈다.개발이라는 것은 나혼자만이 아닌 팀 단위로 움직이는 것이기 때문에 이를 팀 레벨로 이끌고 싶은 욕구가 생겼다. 그러기 위해선 내가 그를 증명할 수 있는 탄탄하고 좋은 실력을 갖추도록 노력해야겠다. 이번 인프러너의 좋은 강의들을 재반복하면서 나의 지식으로 습득하고, 이를 다른 사람들에게 전수할 수 있는 그런 사람이 되도록 해야겠다.즐거운 기회를 마련해준 우빈님과 인프런에게 감사를 표하며 글을 마치겠다! ㅎㅎ 내용 정리Presentation Layer 테스트(1)외부 세계의 요청을 가장 먼저 받는 계층파라미터에 대한 최소한의 검증을 수행한다Mock (가짜, 대역) 객체테스트 시 의존관계 주입이 방해될 때, 가짜를 집어넣어 처리함MockMvcMock(가짜) 객체를 사용해 스프링 MVC 동작을 재현할 수 있는 테스트 프레임워크 @Transactional(readOnly = true)CRUD에서 CUD가 동작 안 함오직 R만 작동JPA는 기본적으로 1차 캐시에 스냅샷을 저장하고, 트랜잭션 커밋 플러시 하는 시점에 변경 감지가 작동하여 업데이트 쿼리를 발생시킴근데 reaOnly = true를 열면 CUD 스냅샷 저장, 변경감지를 안 하여 성능 향상 효과가 있음CQRSCommand / QueryRead 작업이 거의 80%정도 주를 이룰 때가 많음그래서 command와 query 작업을 분리하여 서로 연관되지 않게끔 분리하는 CQRS 패턴을 활용조회 하는 서비스만 만들면 @Transactional(readOnly=true)를 써서 관리할 수 있음DB 엔드포인트도 구분하여 쓰기는 마스터, 읽기는 슬레이브로 보내버릴 수 있음어노테이션 보고 마스터나 슬레이브로 구분해줄 수 잇다함클래스 단위에은 readOnly를, 변경 메서드엔 @Transactional을 달자아님 객체 단위로 나눠서 관리Presentation Layer 테스트(2)Validation을 활욜String@NotBlank: 전부 다 허용 안됨@NotNull: “”, “ “는 통과됨@NotEmpty: “ “ 공백은 통과 “” 빈문자열 실패정책을 validation에서 거르는게 맞을까? 라는 고민을 해야함EX) 상품 이름은 20자 → 이런건 서비스 레이어나 프러턱트 코드 같은 안쪽에서 해도 됨상위 레이어는 하위 레이어를 알아도 되지만하위 레이어는 상위 레이어를 모르게 하는게 좋음Layered Architecture 단점DB랑 연결하기 위해 JPA 도메인 객체를 만들었는데, 너무 강결합 되는 구조가 됨깊어질 수록 바꾸기가 어려워짐 Hexagonal Architecture포트와 어댑터 형태포트를 통해 외부와 통신을 함도메인 정책이 가장 안 쪽에 있음모노레포라면 레이어드 아키텍쳐도 괜찮지만점점 커질 거 같으면 헥사고날을 고려해보자QueryDSLJPA랑 함께 많이 쓰이는 동적 쿼리 빌더타입체크를 지원해주서 컴파일단에서 체크해 안전해짐 섹션7: Mock을 마주하는 자세Mockito로 Stubbing 하기이메일 등 외부 네트워크에 전송하는 서비스 로직에는 Transactional 안 걸어두는게 좋음트랜잭션으로 DB 조회할때 커넥션을 가지고 있는데, 외부 소통 하면서 갖고 있으면 다른데서 못 가져감Test DoubleStunt Double: 스턴트 배우를 쓰는 것을 차용 → 대역을 사용Dummy: 아무 것도 하지 않는 깡통 객체Fake: 단순한 형태로 동일한 기능을 수행하나, 프로덕션에서 쓰기에는 부족한 객체 (ex. FakeRepository - 메모리 Map)Stub: 테스트에서 요청한 것에 대해 미리 준비한 결과를 제공하는 객체. 그 외에는 응답하지 안흠Spy: Stub 이면서 호출된 내용을 기록하여 보여줄 수 있는 객체. 일부는 실제 객체처럼 동작시키고 일부만 Stubbing할 수 있다.Mock: 행위에 대한 기대를 명세하고, 그에 따라 동작하도록 만들어진 객체Stub과 Mock이 좀 헷갈림Stub은 상태 검증 (State Verfication)Mock은 행위 검증(Behavior Verication): 메서드가 무엇을 했을 때(행위) 어떤 값을 돌려주는 느낌~Mocks Aren't Stubspublic interface MailService { void send(Message msg); } public class MailServiceStub implements MailService { private List<Message> messages = new ArrayList<>(); public void send(Message msg) { messages.add(msg); } public int numberSent() { return messages.size(); } } // Stub 검증 class OrderStateTest { @Test void testOrderSendsMailIfUnfilled() { Order order = new Order(TALISKER, 51); MailServiceStub mailer = new MailServiceStub(); order.setMailer(mailer); order.fill(wraehouse); assertEquals(1, mailer.numberSent()); // 상태에 대한 검증 } } // Mock 검증 class OrderInteractionTester { @Test 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, @Spy, @InjectMocks순수 mockito로 검증하기BDDMockitoClassicist VS MockistMockist: 모든 걸 Mocking 처리해서 하자Classicist: 모킹만 해서 실 프로덕션 환경을 다 커버하긴 어렵다필요한 경우엔 실제를 쓰다가 필요하면 Mockito를 쓰자강사님 (Classcisit)Presentaion은 외부에서 오는 값만 검증하고, 하위 레벨은 Mocking외부 시스템은 우리에게 제어권이 없기에 이런 경우 (외부 계를 나누자)비용이 조금 더 들더라도 실제 객체를 갖고와 테스트를 하는게 안전하지 않을까~키워드 정리Bean이 들어가면 스프링 환경에서이니, 단위 테스트로만 Mocking하고 싶으면 Bean이 안 들어간걸 쓰자섹션8: 더 나은 테스트를 작성하기 위한 구체적 조언한 문단에 한 주제!글쓰기에서 요지는 한 문단엔 한 주제만을 넣음테스트도 문서로써의 기능을 함테스트 코드를 글 쓰기의 관점에서 하나의 테스트도 하나의 문단으로 보고, 그에 맞게 처리하자void containsStockTypeEx() { // given ProductType[] productTypes = ProductType.values(); for (ProductType productType: productTypes) { if (productType == ProductType.HANNDMADE) { // when boolean result = ProductType.containsStockType(productType); // then assertThat(result).isFalse(); } if (productType == ProductType.BAKERY || productType == ProductType.BOTTLE) { // when boolean result = ProductType.containsStockType(productType); // then assertThat(result).isTrue(); } } 위와 같이 if 같은 분기문이 있으면 두 가지 이상의 케이스를 구분하겠단 의미반복문의 경우에도 테스트 코드를 읽는 사람이 생각을 해야함DisplayName을 한 문장으로 만들게끔 해서 하나의 케이스만 처리하게끔 하자완벽하게 제어하기아래와 같이 시간, 랜덤값 같이 내가 제어할 수 없는 값은 외부에서 주입할 수 있도록 해야함public Order createOrder() { LocalDateTime currentDateTime = LocalDateTime.now(); final LocalTime currentTime = currentDateTime.toLocalTime(); if (currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)) { throw new IllegalArgumentException("주문 시간이 아닙니다. 관리자에게 문의하세요."); } return new Order(LocalDateTime.now(), beverages); } // 외부에서 주입 public Order createOrder(LocalDateTime currentDateTime) { final LocalTime currentTime = currentDateTime.toLocalTime(); if (currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)) { throw new IllegalArgumentException("주문 시간이 아닙니다. 관리자에게 문의하세요."); } return new Order(LocalDateTime.now(), beverages); } 그렇다고 LocalDateTime registeredDateTime = LocalDateTime.now() 테스트 코드에서 현재 시각을 주는 메서드를 막 쓰지는 말자 (환경마다 다를 수 있으니)시각을 LocalDateTime.of() 같은 시각을 지정해줄 수 있도록 원칙을 만드는 것도 좋음테스트 환경의 독립성을 보장하자@DisplayName("재고가 부족한 상품으로 주문을 생성하려는 경우 예외가 발생한다.") @Test void createOrderWithNoStock() { // given final LocalDateTime registeredDateTime = LocalDateTime.now(); Product product1 = createProduct(BOTTLE, "001", 1000); Product product2 = createProduct(BAKERY, "002", 3000); Product product3 = createProduct(HANDMADE, "003", 5000); productRepository.saveAll(List.of(product1, product2, product3)); Stock stock1 = Stock.create("001", 2); Stock stock2 = Stock.create("002", 2); stock1.deductQuantity(1); // todo stockRepository.saveAll(List.of(stock1, stock2)); OrderCreateRequest request = OrderCreateRequest.builder() .productNumbers(List.of("001", "001", "002", "003")) .build(); // when // then assertThatThrownBy(() -> orderService.createOrder(request.toServiceRequest(), registeredDateTime)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("재고가 부족한 상품이 있습니다."); } deductQuantity 부분으로 인해 논리적으로 한 번 더 생각해서 테스트를 짜게 된다테스트는 when, then에 집중해야는데, given도 보면서 혼란스러워짐given은 순수 생성자 기반의 값을 주는게 좋음 or Builder팩토리 메서드도 지양하는게 좋음 (팩토리 메서드 내에 의도를 가지고 뭔가 로직이 있을 수 있기에)테스트 간 독립성을 보장하자class StockTest { private static final Stock stock = Stock.create("001", 1); @DisplayName("재고의 수량이 제공된 수량보다 작은지 확인한다.") @Test void isQuantityLessThanEx() { // given int quantity = 2; // when boolean result = stock.isQuantityLessThan(quantity); // then assertThat(result).isTrue(); } @DisplayName("재고를 주어진 개수만큼 차감할 수 있다.") @Test void deductQuantityEx() { // given int quantity = 1; // when stock.deductQuanaity(quantity); // then assertThat(stock.getQuanaity()).isZero(); } } 두 가지 테스트가 static 변수 같은 공유 자원을 활용하고 있음공유 자원의 값이 변경되면 다른 테스트 결과에 영향이 생김테스트 수행 순서는 랜덤하기에 독립적으로 항상 올바를 테스트가 되도록 해줘야함DynamicTest를 쓰면 지정해줄 수 있나봄한 눈에 들어오는 Test Fixture 구성하기Fixture: 고정물, 고정되어 있는 물체테스트를 위해 원하는 상태로 고정시킨 일련의 객체활용 어노테이션@BeforeAll@BeforeEach@AfterAll@AfterEach픽스처를 통해 공통의 테스트 객체를 만들면 하나의 테스트를 바꿀 때 모두 영향이 끼치기에 지양하는 것이 좋음테스트클래스가 엄청 길어진 경우 given 절을 보는데 문맥을 기억하기 어려워짐 (→ 문서 파악이 어려워짐)사용해도 괜찮을 때각 테스트 입장에서 봤을 때 : 아예 몰라도 테스트 내용을 이해하는 데에 문제가 없는 경우에수정해도 모든 테스트에 영향을 주지 않는가data.sql에서 given 데이터를 만들 수 있겠지만, 데이터 파편화가 될 가능성이 큼 (비추)테이블도 많아지고, 사이즈가 커질 수록 관리가 어려워짐생성 메서드를 만들 때 테스트에 필요한 파라미터만 받아서 쓰자name 필드가 필요없으면 파라미터에서 빼버림테스트 패키지 전체에서 사용하는 추상 클래스를 만들어 픽스처 빌더들을 모아 쓸 순 있겠지만 NO 추천파라미터가 엄청 많아질 때 마다 내가 필요한 빌더들이 생기고, 관리가 더 안 될 수도 있음코틀린을 사용하면 롬복도 필요없고, 빌더도 필요없음. 기본값을 지정해줄 수 있음Text Fixture 클렌징deleteAll: select 해서 건건이 where id 조회해서 지움연관 관계 걸려있는 것도 찾아서 같이 지어줌전체 찾아서 순회해서 지워주고 있음삭제도 일종의 비용이기 때문에 테스트에서 오래걸리면 좀 그럴 수 있음@Transactional public void deleteAll() { Iterator var2 = this.findAll().iterator(); while(var2.hasNext()) { T element = (Object)var2.next(); this.delete(element); } } deleteAllInBatch전체 삭제 쿼리를 만들어서 지어줌 → 벌크성으로 지워줌@Transactional public void deleteAllInBatch() { Query query = this.entityManager.createQuery(this.getDeleteAllQueryString()); this.applyQueryHints(query); query.executeUpdate(); } private String getDeleteAllQueryString() { return QueryUtils.getQueryString("delete from %s x", this.entityInformation.getEntityName()); } @Transactional을 쓰면 되는데, 사이드 이펙트를 잘 고려해서 진행해야함@ParameterizedTest반복되는 given에 대해서 위 어노테이션을 활용해서 반복해서 할당할 수 있다.@DisplayName("상품 타입이 재고 관련 타입인지를 체크한다.") @CsvSource({ "HANDMADE, false", "BOTTLE, true", "BAKERY, true" }) @ParameterizedTest void containsStockType4(ProductType productType, boolean expected) { // when boolean result = ProductType.containsStockType(productType); // then assertThat(result).isEqualTo(expected); } private static Stream<Arguments> provideProductTypesForCheckingStockType() { return Stream.of( Arguments.of(ProductType.HANDMADE, false), Arguments.of(ProductType.BOTTLE, true), Arguments.of(ProductType.BAKERY, true) ); } @DisplayName("상품 타입이 재고 관련 타입인지를 체크한다.") @MethodSource("provideProductTypesForCheckingStockType") @ParameterizedTest void containsStockType5(ProductType productType, boolean expected) { // when boolean result = ProductType.containsStockType(productType); // then assertThat(result).isEqualTo(expected); } @DynamicTest공통의 값으로 단계 별로 테스트를 돌리고 싶으면 사용한다@DisplayName("") @TestFactory Collection<DynamicTest> dynamicTest() { return List.of( DynamicTest.dynamicTest("", () -> {}), DynamicTest.dynamicTest("", () -> {}) ); } @DisplayName("재고 차감 시나리오") @TestFactory Collection<DynamicTest> stockDeductionDynamicTest() { // given Stock stock = Stock.create("001", 1); return List.of( DynamicTest.dynamicTest("재고를 주어진 개수만큼 차감할 수 있다.", () -> { // given int quantity = 1; // when stock.deductQuantity(quantity); // then assertThat(stock.getQuantity()).isZero(); }), DynamicTest.dynamicTest("재고보다 많은 수의 수량으로 차감 시도하는 경우 예외가 발생한다", () -> { // given int quantity = 1; // when // then assertThatThrownBy(() -> stock.deductQuantity(quantity)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("차감할 재고 수량이 없습니다."); }) ); } 테스트 수행도 비용이다. 환경 통합하기테스트를 작성하는 이유는 사람이 수동으로 돌리는 비용보다 기계에 맡겨서 피드백을 빨리 받게 하기 위함테스트의 속도가 빨라야 유의미하기에 비용관리를 해야함서버 띄우는 경우가 많아지면 서버가 오래 걸리게 됨이런 비용을 관리해야함@MockBean 의 경우에도 서버를 새로 띄어야됨@WebMvcTest(controllers = { OrderController.class, ProductController.class }) public abstract class ControllerTestSupport { @Autowired protected MockMvc mockMvc; @Autowired protected ObjectMapper objectMapper; @MockBean protected OrderService orderService; @MockBean // Mock 객체 만들어줌 protected ProductService productService; } @ActiveProfiles("test") @SpringBootTest public abstract class IntegrationTestSupport { @MockBean protected MailSendClient mailSendClient; } Q. private 메서드의 테스트는 어떻게 하나요?하려고 해서도 안 되고 할 필요가 없다.클라이언트(외부)는 공개된 API만 알면 되기에, private을 알 필요 없다만약 private 메서드를 단독으로 빼서 테스트하고 싶다면, 객체를 분리할 시점인가를 고민해봐야 한다필요하다면 새로운 객체를 만들어 책임을 위임하면 된다Q. 테스트에서만 필요한 메서드가 생겼는데 프로덕션 코드에서는 필요 없다면?만들어도 됨. 하지만 보수적으로 접근하기테스트에서 조회성을 위해 만드는 거 정돈 괜찮지만, 테스트에서만 사용되는 메서드를 막 만드는 것은 지양해야함getter, 생성자, 생성자 빌더, 사이즈 등 객체가 마땅히 가져도 되는 행위라 생각되고 미래에 충분히 사용되는 것들은 만들어도 괜찮다~ 학습 테스트잘 모르는 기능, 라이브러리, 프레임워크를 학습하기 위해 작성하는 테스트여러 테스트 케이스를 스스로 정의하고 검증하는 과정을 통해 보다 구체적인 동작과 기능을 학습할 수 있다.관련 문서만 읽는 것보다 훨씬 재미있게 학습할 수 있다.Spring REST Docs테스트 코드를 통한 API 문서 자동화 도구API 명세를 문서로 만들고 외부에 제공함으로써 협업을 원할하게 한다기본적으로 AsciiDoc을 사용하여 문서를 작성한다REST Docs VS SWAGGERREST DOCS장점테스트를 통과해야 문서가 만들어진다 (신뢰도가 높음)프로덕션 코드에 비침투적임단점코드 양이 많다.설정이 어렵다SWAGGER장점적용이 쉬움문서에서 바로 API 호출을 수행해볼 수 있다.단점프로덕션 코드에 침투적이다테스트와 무관하기 때문에 신뢰도가 떨어질 수 있음

백엔드

임원기

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

4주라는 시간이 엄청 길줄 알았는데 순식간에 지나갔다.  여태 학습한 내용을 정리하면서 마무리해보자.Mock 다루기3주차에 흐름이 끊기는 것 같아 Presentation Layer 테스트진도를 나갔기 때문에 Mock부터 시작했다.MockMvc부터 시작해서 테스트 더블까지 다뤘는데,자세한 내용은 블로그에 따로 작성해봤다. Mockito 파헤치기 with Mock, SpyMock을 사용한 테스트는 테스트가 성공했다고해서 실제 운영 환경에서도 정상적으로 기능이 동작하리라 확신할 수는 없다는 생각이 있어 Mock 사용을 일부러 피했는데, 글을 작성하면서 효율적인 테스트는 어떻게 짤 수 있는가, 테스트 더블을 어떻게 사용하면 테스트 격리가 가능할지 고민할 수 있는 좋은 시간이 되었다.미션과 피드백Day18 미션은 테스트 더블 애노테이션의 각 용법과 차이점, BDD 스타일로 코드 배치하기였다.피드백에서 머리를 한대 맞은 느낌이었는데, 핵심은 중복 제거가 아니고 도메인에 집중해야한다는 것이다.사용자, 게시물은 간접적이고, 댓글 작성은 직접적이기 때문에 given절에 배치해야 한다는 것이다.중복 제거에 초점을 너무 둔 나머지 미처 생각하지 못한 부분이었다. 항상 나무가 아닌 숲을 볼 것!맺음한 달동안 초 집중할 수 있을 줄 알았는데, 막상 진행하니까 많이 신경쓰지 못한 것 같아 아쉬움이 남는다.그래도 깔끔한 코드를 다루는 법, 테스트 코드를 어떻게 작성해야 하는지 얻어가는 것이 훨씬 많았다. 이제 프로젝트에 적용해서 내 것으로 만드는 시간을 가질 예정이다.다른 러너들도 고생많았습니다! 

안지수

[워밍업 클럽] BE 클린코드&테스트 4주차 발자국

강의: Practical Testing: 실용적인 테스트 가이드 학습 내용Spring & JPA 기반 테스트Layered Architecture에 따른 스프링 & JPA 기반 테스트 방법에 대해서 학습했다.Persistence Layer데이터베이스에 접근하는 영역(Data Access) Data에 대한 CRUD에만 집중한 레이어비즈니스 가공 로직 포함 XBusiness Layer비즈니스 로직을 구현하는 영역Persistence Layer와 통합하여 테스트 작성 -> @SpringBootTest 사용트랜잭션 보장주로 클래스 전체에 @Transactional(readOnly = true) 적용하고,커맨드 메서드(CUD) 위에 @Transactional 적용 Presentation Layer외부 요청을 가장 먼저 받는 영역파라미터 등 넘겨 받은 값들에 대한 최소한의 검증 수행Business Layer, Persistence Layer와 같은 하위 Layer 들을 Mocking 처리-> @MockMvc , @WebMvcTest, @MockBean 사용 Mock을 마주하는 자세더 나은 테스트를 작성하기 위한 구체적 조언 회고Liked 지식공유자 우빈님의 깜짝 세션Lacked 첫 강의를 수강할 때는 강의 진도표에 잘 맞춰 듣었는데 두번째 테스트 코드 강의 때는 강의 진도가 뒤쳐져서 마지막 과제는 제출하지 못했다. 좀 더 많은 것을 알고 공부한 상태에서 들었으면 훨씬 빨리, 많이 배울 수 있었을 것 같아서 아쉽다.Learned 단순히 테스트 코드를 작성하는 방법 뿐만 아니라 Layered Architecture에서 어떻게 테스트 코드를 작성에 유리한 설계를 할 수 있는지 배웠다. 이런 설계는 각 Layer의 책임 분리를 유도하고, 궁극적으로 좋은 설계로 이어짐을 배울 수 있었다.Longed for 배운 내용을 충분히 소화해서 내 코드에 자연스럽게 녹여내는 것이 목표!

백엔드

정지훈

인프런 워밍업 클럽(클린코드, 테스트) - 4주차 발자국

 4주차의 핵심 내용은 아래와 같았다Presentation Layer 테스트mokito 사용하기테스트를 위한 여러가지 조언들네이밍, 픽스처 분리, private 메서드의 책임분리 등..테스트와 관련한 여러가지 조언부분이 굉장히 인상깊었다.실제로 곧바로 적용해볼수 있는 부분과, 더 깊게 고민해볼 부분들이 있어서 좋았던 것 같다. 과제는 1주차에 2회 진행하였다.[Day15 과제]스프링의 레이어드 아키텍처 환경에서 각 계층 테스트에 대해 자신만의 용어로 정리하는 시간을 가졌다.정확히 어떤 부분을 계층별로 테스트해야하나 고민이 있었는데, 강의를 들으면서 많은 부분들이 해소되었고 도움을 받았다. [Day18 과제]mokito에서 사용할 수 있는 여러 개념들을 정리해 보았다.모호했던 개념들어있지만 한번 알아두니까 생각보다 별거(?)없었고 용도를 잘 알 고 사용하면 좋을 것 같다고 느꼈다.테 마무리열심히 강의를 듣다보니 금세 1달이라는 시간이 지났다.강의를 듣고 정리한것으로 마무리 지었지만, 복습과 체화시키는 과정이 필요할 것 같다.그리고 이번 과정을 거치면서 코드를 작성하거나 테스트를 작성할 때에 조금 더 생각하고 신중하게 작성할 수 있게 된 것 같아서 뿌듯하다.

백엔드인프런워밍업클럽

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

이전내용 : 클린코드[인프런 워밍업 클럽 백엔드 스터디 2기] 1주차 발자국[인프런 워밍업 클럽 백엔드 스터디 2기] 2주차 발자국테스트 코드[인프런 워밍업 클럽 백엔드 스터디 2기] 3주차 발자국Mock을 마주하는 자세Test Doubl,e StubbingDummy : 아무 것도 하지 않은 깡통 객체Fake : 단순한 형태로 동일한 기능은 수행하나 , 프로덕션에서 쓰기에는 부족한 객체 Stub : 테스트에서 요청한 것에 대해 미리 준비한 결과를 제공하는 객체Spy : Stub이면서 호출된 내용을 기록하여 보여줄 수 있는 객체. 일부는 실제 객체처럼 동작시키고 일부만 Stubbing할 수 있다.mock : 행위에 대한 기대를 명세하고, 그에 따라 동작하도록 만들어진 객체Stub : 상태 검증Mock : 행위 검증@Mock, @MockBean, @Spy, @SpyBean @InJectMocks[설명링크]  더 나은 테스트를 작성하기 위한 구체적 조언한 문단에 한 주제!완벽하게 제어하기객체 내부에서 랜던값을 테스트 코드로 비교할 수 없다면 DI를 통해서 외부에서 주입받아서 테스트 코드를 작성하자테스트 환경의 독립성을 보장하자순수한 생성자로만 테스트 기본절을 구성하는 게 더 좋다. (.SQL 파일로 데이터를 인입받는 것을 지양하자)두 가지 이상의 테스가 하나의 자원을 공유한다. 공유자원을 사용하지말고 독립적으로 사용하자.Text Fixture 클렌징DeleteAllInBatch를 사용하는 것을 추천 (DeleteAll은 많은 쿼리가 실행이됨.)기본적으로 클래스 위에 @Transaction을 사용하나 배치 같은 트랜잭션이 혼용되는 곳에서 DeleteAllInBatch을 많이 사용함.테스트 수행도 비용이다. 환경 통합하기❗❗IntegrationTestSupport 추상클래스를 상속 받아서 테스트 코드를 만듦.이를통해서 Spring Boot 띄우는 횟수를 줄이고 최적화를 진행할 수 있음.@ActiveProfiles("test") @SpringBootTest public abstract class IntegrationTestSupport { } @WebMvcTest(controller = { OrderController.class, ProductController.class }) public abstract class ControllerTestSuport { } 테스트를 작성하는 마음가짐테스트를 왜 해야 되는가? 테스트 코드는 귀찮은 작업이다. 하지만 왜 해야 되는지 마음속에 명확히 인지하고 있어야지 귀찮음을 이기고 테스트를 작성할 수 있다.최적의 상황을 판별하고 맞게 도구를 빠르게 사용을 할 수 있어야 진짜 프로다. 지속적으로 도구를 사용하는 방법을 연습하고, 타협하지 않는 마음으로 테스트를 작성해 나가면 미래의 수많은 시간을 아낄 수 있다. 4주차 회고클린코드와 테스트코드 학습의 마지막 날입니다.올해 1월부터 시작한 프로젝트의 오픈 날짜가 10월 28일과 겹치면서 야근도 많았지만, 끝까지 완주해낸 제 자신이 뿌듯합니다. 클린코드와 테스트코드는 실무에서 정말 필수적인 요소라고 생각합니다. 이번 기회에 배운 것들을 혼자만 실천하는 것이 아니라, 동료들과 공유하고 함께 좋은 코딩 습관을 만들어가가도록 하겠습니다. 2024년이 얼마 남지 않았지만, 올해 가장 잘한 일 중 하나라고 자부하고 앞으로도 이러한 원칙들을 항상 인지하면서 코드를 작성하도록 하겠습니다.감사합니다.  

백엔드워밍업클럽스터디

whffkaos007

워밍업 클럽 2기 BE 클린코드&테스트 : 미션 - Day 18

워밍업 클럽 2기 BE 클린코드&테스트 : 미션 - Day 18  이 글은 박우빈님의 강의를 참조하여 작성한 글입니다. 미션 - Day 18 미션 내용1. @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이를 한번 정리해 봅시다.  2. 아래 3개의 테스트가 있습니다. 내용을 살펴보고, 각 항목을 @BeforeEach, given절, when절에 배치한다면 어떻게 배치하고 싶으신가요? (@BeforeEach에 올라간 내용은 공통 항목으로 합칠 수 있습니다. ex. 1-1과 2-1을 하나로 합쳐서 @BeforeEach에 배치) ✔ 게시판 게시물에 달리는 댓글을 담당하는 Service Test✔ 댓글을 달기 위해서는 게시물과 사용자가 필요하다. ✔ 게시물을 올리기 위해서는 사용자가 필요하다. 미션1테스트를 통해 검증하는 과정에서 여러 객체(모듈)이 상호 작용하고 객체 간 의존성이 클수록 필요한 데이터를 준비하는데만 오랜 시간을 걸린다. 배보다 배꼽이 큰 경우를 막기 위해 검증하지 않는 객체(모듈)은 Mock 처리하여 검증하고자 하는 것에만 더 집중할 수 있다. 우리는 아래와 같은 어노테이션을 통해 이러한 이점을 얻을 수 있다. @Mock : 실제 객체 대신에 가짜(Mock) 객체를 생성한다.@MockBean : Spring 테스트 환경에서 스프링 컨텍스트에 등록된 Bean을 Mock 객체로 대체한다.@Spy : 실제 객체를 생성하되, 특정 메서드에 대해 행위와 결과를 지정할 수 있다.@SpyBean : Spring 테스트 환경에서 스프링 컨텍스트에 등록된 Bean을 Spy 객체로 대체한다.@InjectMocks : 의존성 주입을 자동으로 수행한다. 테스트 대상 클래스의 인스턴스를 생성하고, 해당 클래스가 의존하는 객체들을 @Mock 또는 @Spy로 주입합니다. Bean의 유무 차이(@Mock, @MockBean / @Spy, @SpyBean)기능에 대한 차이는 없다. 다만 스프링 컨텍스트를 사용한다면 싱글톤 방식으로 각 인스턴스가 관리된다. 대게 단위 테스트 환경에서는 의존성 주입을 위해 @Mock, @Spy를 사용하고 스프링 환경에서 테스트하고 싶을 때는 @MockBean, @SpyBean을 사용하면 된다. @injectMocks vs @Autowired여기서도 Mock, Spy를 통해 의존성을 주입할 때는 @injectMocks을 사용하면 된다. @Mock UserRepository userRepository; @InjectMocks UserService userService; // userRepository가 주입됨스프링 컨테이너에서 빈을 가져올 때는 다음과 같이 사용하면 된다.@MockBean UserRepository userRepository; @Autowired UserService userService; // userRepository가 주입됨 미션2✔ 게시판 게시물에 달리는 댓글을 담당하는 Service Test✔ 댓글을 달기 위해서는 게시물과 사용자가 필요하다. ✔ 게시물을 올리기 위해서는 사용자가 필요하다.  @BeforeEach void setUp() { 사용자 생성에 필요한 내용 준비 사용자 생성 게시물 생성에 필요한 내용 준비 게시물 생성 } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given 1-5. 댓글 생성에 필요한 내용 준비 // when 1-6. 댓글 생성 // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given 2-5. 댓글 생성에 필요한 내용 준비 2-6. 댓글 생성 // when 2-7. 댓글 수정 // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given 3-7. 사용자1의 댓글 생성에 필요한 내용 준비 3-8. 사용자1의 댓글 생성 3-3. 사용자2 생성에 필요한 내용 준비 3-4. 사용자2 생성 // when 3-9. 사용자2가 사용자1의 댓글 수정 시도 // then 검증 }[작성 이유]@BeforeEach 구조 이유해당 테스트에 핵심 도메인은 댓글이다. 댓글을 달기 위한 사용자와 게시물을 부수적인 요소이며 댓글을 생성하기 위해서는 사용자와 사용자가 생성한 게시물이 필수라는 조건이 있다. 해당 도메인 로직이 변경되더라도 이는 전제 조건이므로 setUp 메서드에서 구현해도 의미를 담기 충분하다고 판단했고 덤으로 중복도 방지할 수 있다고 생각한다. 테스트 별 given, when 절 구조 이유각 테스트 별 when 절에는 하나의 행위만 있다. 테스트는 하나의 행위에 대한 검증을 해야 한다. 만약 테스트에 2개 이상의 행위를 검증하게 되는 경우 검증하고자 하는 것이 모호해지고 실제로 테스트에 성공하더라도 2개 이상의 행위가 복합적으로 동작했기에 정확한 결과를 예측하기 힘들다.   

백엔드테스트코드

[인프런 워밍업 스터디 클럽 2기_BE] 4주차 발자국

Mock을 마주하는 자세1. Test Double 개념마틴 파울러는 테스트에서 사용되는 가짜 객체를 Test Double이라는 용어로 부르며, 이는 영화에서 스턴트 더블을 사용하는 것과 유사하게 테스트를 위해 실제 객체 대신 사용되는 모든 종류의 "대역" 객체를 의미합니다. Test Double은 다양한 상황에서 실제 객체를 대체하여 테스트를 가능하게 합니다. Test Double에는 여러 종류가 있으며, 여기에는 Dummy, Fake, Stub, Spy, Mock이 포함됩니다.2. Test Double의 종류Dummy: 아무것도 하지 않는 깡통 객체입니다. 테스트에서 단순히 자리를 채우기 위해 사용됩니다.Fake: 실제 기능을 수행하지만, 단순한 형태로 동작하는 객체입니다. 프로덕션에서 사용하기에는 부족한 부분이 있습니다. 예를 들어 FakeRepository가 해당됩니다.Stub: 테스트에서 요청한 것에 대해 미리 준비된 결과를 제공하는 객체입니다. 그 외의 요청에는 응답하지 않습니다.Spy: Stub의 역할을 하면서 호출된 내용을 기록하여 보여줄 수 있는 객체입니다. 일부는 실제 객체처럼 동작시키고 일부는 Stubbing할 수 있습니다.Mock: 특정 행동에 대한 기대를 명시하고, 그 기대에 따라 동작하도록 만든 객체입니다.3. Stub과 Mock의 차이Stub과 Mock은 모두 테스트에서 특정 객체의 역할을 대신할 수 있지만, 검증 방식과 목적이 다릅니다.차이점 요약특징 Stub Mock 목적 특정 상황을 설정하기 위해 사용 행위 자체가 올바르게 수행되는지 검증 검증 방식 상태 검증 (State Verification) 행위 검증 (Behavior Verification) 용도 테스트의 결과가 예상한 대로인지 확인 특정 메서드가 예상대로 호출되었는지 확인 예시 메서드가 호출된 후 내부 상태를 확인 메서드가 기대한 횟수만큼 호출되었는지 확인4. Mockito란?Mockito는 테스트 더블을 쉽게 만들어주는 Java 라이브러리입니다. 테스트 더블이란, 테스트에서 실제 객체 대신 사용되는 가짜 객체를 말합니다. 예를 들어, 우리가 UserService를 테스트할 때, UserService가 실제 데이터베이스를 호출하지 않게 하려면 데이터베이스와 상호작용하는 UserRepository를 가짜로 만들어서 테스트하는 방식이 있습니다. 이때 Mockito를 사용하여 UserRepository의 Mock 객체를 생성하는 것입니다.5.Mockito에서 자주 사용하는 세 가지 애노테이션1. @Mock@Mock 애노테이션은 클래스의 가짜 객체(Mock Object)를 생성하는 데 사용됩니다. 이를 통해 실제 객체와 상호작용하는 대신, 특정 상황에서 원하는 결과를 반환하도록 미리 설정할 수 있습니다.용도주로 의존성을 격리하고 테스트하려는 대상 객체와 관련된 외부 객체의 실제 구현을 대체할 때 사용합니다. 예를 들어, 데이터베이스나 외부 API 호출 같은 의존성을 제거하고 독립적으로 테스트하려는 경우 유용합니다.사용 예시@Mock private OrderRepository orderRepository; 특징@Mock 객체는 기본적으로 모든 메서드 호출에 대해 아무 동작도 하지 않으며 null, 0, false 같은 기본값을 반환합니다. 필요한 경우 특정 메서드 호출에 대한 반환값을 설정할 수 있습니다:when(orderRepository.findById(1L)).thenReturn(Optional.of(order)); 2. @InjectMocks@InjectMocks 애노테이션은 의존성이 주입된 객체를 생성합니다. 이 애노테이션이 붙은 객체는 @Mock 또는 @Spy로 주입 가능한 필드에 대해 자동으로 주입됩니다.용도실제로 테스트하고자 하는 객체를 생성할 때 사용합니다. 테스트 대상 객체의 의존성으로 @Mock 또는 @Spy로 선언된 가짜 객체가 주입됩니다.사용 예시@InjectMocks private OrderService orderService; 특징:OrderService 내부에 있는 OrderRepository, MailService 등의 필드에 자동으로 @Mock 객체가 주입됩니다. @InjectMocks는 주로 테스트 대상 클래스에 붙이고, 해당 클래스의 의존성을 주입하기 위한 @Mock과 함께 사용됩니다.3. @Spy@Spy 애노테이션은 기존 클래스의 객체를 부분적으로 모킹할 수 있게 해줍니다. 즉, @Spy로 생성된 객체는 실제 객체처럼 동작하지만, 특정 메서드는 모킹할 수 있습니다.용도테스트 중에 실제 객체의 일부 기능은 사용하면서 특정 메서드만 모킹하고자 할 때 사용합니다.사용 예시@Spy private MailService mailService; 특징@Spy 객체는 실제 객체처럼 동작하지만, 특정 메서드에 대해서는 원하는 동작을 설정할 수 있습니다. 특정 메서드만 모킹하고 싶을 때 doReturn이나 doThrow 같은 메서드를 사용합니다:doReturn(true).when(mailService).sendEmail(anyString(), anyString(), anyString(), anyString()); 6. BDD MockitoBDD (Behavior-Driven Development) Mockito는 테스트 코드에서 행동에 기반한 검증을 가능하게 하는 Mockito의 기능입니다. BDD는 “애플리케이션이 어떻게 행동해야 하는지에 대한 공통된 이해를 구성하는 방법” 입니다.BDDMockito는 Behavior-Driven Development (행위 주도 개발) 스타일의 테스트 작성을 지원하는 Mockito의 확장 라이브러리입니다.BDDMockito의 주요 특징given-when-then 구조 BDDMockito는 테스트 코드를 given(준비)-when(실행)-then(검증) 구조로 작성하도록 돕습니다. 이 구조는 테스트의 가독성을 높이고 테스트의 의도를 명확히 합니다.스텁(Stubbing) 메서드given(): Mockito의 when() 대신 사용됩니다.willReturn(), willThrow() 등: thenReturn(), thenThrow() 대신 사용됩니다.검증 메서드then(): verify() 대신 사용됩니다.should(): 특정 동작이 수행되었는지 검증합니다.가독성 향상 BDDMockito를 사용하면 테스트 코드가 더 자연스러운 언어로 읽힙니다.7. Classicist VS. MockistClassicist (고전파) Mockist (모키스트) 상태 기반 테스트 (객체의 상태를 확인) 행동 기반 테스트 (메서드 호출을 검증) 협력 객체에 대한 신뢰를 바탕으로 테스트 협력 객체와의 상호작용을 검증 구체적인 결과를 중시 메시지 흐름과 동작의 정확성을 중시 변경에 덜 민감한 테스트 변경에 민감할 수 있음더 나은 테스트를 작성하기 위한 구체적 조언테스트 하나 당 목적은 하나테스트 하나당 목적은 하나분기문이나 반복문 이런 생각을 요하는 추가적인 고민을 필요로하는 로직들이 들어가서는 안됨완벽한 제어제어할 수 있는 값으로테스트 환경의 독립성, 테스트 간 독립성공유 변수 사용하는 것 , 한 테스트 다른 테스트 연관되는 것 피하자Test FixtureTest Fixture는 특정 항목, 장치 또는 소프트웨어를 일관되게 테스트하는 데 사용되는 장치https://velog.io/@langoustine/Test-FixturedeleteAll(), deleteAllInBatch()deleteAll()이 실행되면, findAll()의 결과로 얻은 리스트를 순회하며 데이터를 한 개씩 삭제deleteAllInBatch() 결국 테이블에 있는 데이터를 전부 지우는 DELETE 쿼리가 실행됩니다. 즉, 데이터 크기와 관계없이 한 번의 쿼리로도 Repository를 clear 할 수 있게 되므로, deleteAll() 보다는 deleteAllInBatch() 사용이 테스트 속도를 고려했을 때 우선적으로 사용하는 것이 좋음https://velog.io/@balparang/deleteAll-보다-deleteAllInBatch를-사용하자학습 테스트잘 모르는 기능, 라이브러리, 프레임워크를 학습하기 위해 작성하는 테스트여러 테스트 케이스를 스스로 정의하고 검증하는 과정을 통해 보다 구체적인 동작과 기능을 학습할 수 있다.관련 문서만 읽는 것보다 훨씬 재미있게 학습할 수 있다.Spring REST Docs란?Spring REST Docs는 테스트 코드에 기반하여 API 명세서를 자동으로 생성하는 문서화 도구입니다. 백엔드 개발자가 API를 개발하고 테스트하면서 동시에 신뢰할 수 있는 API 명세서를 만들 수 있도록 도와줍니다.Spring REST Docs의 주요 특징테스트 코드 기반 문서화: 테스트를 통과해야만 문서가 생성되므로 API 명세서의 신뢰도가 높습니다.AsciiDoc 문법을 사용한 문서화: 기본적으로 AsciiDoc 형식을 사용하여 문서를 생성합니다. AsciiDoc은 Markdown과 유사한 문법으로, 간결하고 가독성이 높은 문서를 작성할 수 있습니다.프로덕션 코드 비침투적: 문서화를 위한 설정이 프로덕션 코드와 분리되어, API 동작에는 영향을 주지 않습니다.Spring REST Docs의 장단점장점테스트와 문서가 함께 관리되므로 API 명세의 신뢰도가 높아집니다.프로덕션 코드와 분리되어 코드 유지보수와 품질에 영향을 주지 않습니다.단점테스트를 기반으로 문서가 생성되기 때문에 테스트 코드의 작성량이 많아질 수 있습니다.설정이 다소 복잡하여, 초기 세팅에 시간과 노력이 필요합니다.Spring REST Docs를 사용할 때의 유용성Spring REST Docs는 API 명세가 변경되는 경우에도 항상 최신 상태로 유지됩니다. 이는 테스트가 통과할 때만 문서가 생성되는 구조이기 때문에 가능한 것으로, 코드와 문서의 일관성을 보장합니다. 백엔드 개발자의 입장에서는 API의 품질과 신뢰도를 확보하면서, 협업을 위한 문서화도 자연스럽게 이루어지므로 유지보수가 용이해집니다.

sdsd988

[워밍업 클럽 스터디 2기 백엔드(클린코드, 테스트코드)] 4주차 발자국

4주차 강의 : Mock을 마주하는 자세, 더 나은 테스트를 작성하기 위한 구체적 조언, 부록(Spring Rest Docs)  요약1. 테스트 환경에서 모든 빈을 제어하기 힘들다. -> 테스트 더블(@Mock, @Spy 의 필요성)2. 테스트코드도 코드다. 따라서, 관리되어야 한다.3. 테스트 코드 작성으로 작성하기 귀찮은 개발 문서까지 이어진다면?(어렵고, 귀찮은 과정을 통해) 강의 내용 @Mock을 마주하는 자세테스트 코드를 작성하다 보면, 의존성 주입이 필요해진다.테스트 하고자 기능을 제외한 빈의 생성은 불필요하다는 전제에서, 테스트 더블(대역) 활용 필요성 테스트 더블 역할을 생성할 수 있는 어노테이션@Mock, @Spy, @InjectMocks 운영 환경에서 테스트, 특정 조건에서 효율적인 테스트 (Classicist VS Mockist)더 나은 테스트를 위한 구체적 조언프로그래밍 언어도 언어이기에, 한 문단에는 한 주제를 갖는 것이 좋다.테스트는 환경 그리고 테스트 간의 독립성이 보장되어야 한다.테스트 후 데이터는 클렌징 되어야 하고부록테스트를 작성했다면, 스프링을 활용하여 개발 문서를 작성할 수 있다.Spring Rest Docs 통해 개발 문서를 작성할 수 있다.미션 1. @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이를 정리2. 테스트 코드의 배치강의에서 학습한 테스트 더블 어노테이션의 차이에 대해 정리하였다.테스트 코드의 작성 순서를 정리해보면서, 어떻게 테스트를 효율적으로 작성해 나갈 것인지 생각해 볼 수 있었다. 4주차 회고 - 4주는 짧지만, 긴 시간이다. 완강은 힘들다. 인프런 워밍업 클럽이라는 , 강제성이 생기면서 4주간 2개의 강의를 완강할 수 있었다. 코드를 잘 작성하는 것 에 대해 이해할 수 있었다.클린코드, Reasonable code에 대해 구체적으로 생각을 갖게 되었다.강의를 듣고, 리팩토링을 진행하면서 이 정도까지 해야하나? 라는 생각이 들기도 했다.그렇지만, 완성한 후 다시 코드를 읽어 보니 일단 나는 이해하기가 쉽다.테스트 코드작성에 대해 자신감을 갖게 되었다.테스트코드가 중요하다, 필요하다는 이야기를 들어서 세뇌되어 있었지만 막막했다.어떻게 작성해야 하는 지 가늠이 되지 않았다. (강의 전)이제는 테스트코드 작성에 자신감이 생겼다. (강의 후)결국 코드는 실전이라는 생각을 워밍업 클럽 마지막 주차에 다시 생각하게 된다.자신감이 생겼을 때, 더 연습하고 성장해야겠다.

백엔드테스트코드인프런워밍업클럽백엔드클린코드

[워밍업 클럽 스터디 2기 - BE] (클린코드, 테스트코드) 4주차 발자국

해당 포스팅은 Practical Testing: 실용적인 테스트 가이드를 학습하면서 작성한 회고입니다.학습 회고Mock 사용 방법에 대해 다루었고, 구체적으로 Mocking 시점과 방법, Mocking을 적용할 적절한 시점에 대해 명확하게 이해할 수 있었고, Mock을 활용하여 더욱 효율적인 테스트 작성에 대한 설명을 통해 여러 고민을 해볼수 있는 좋은 시간이었습니다. Mock 활용과 Testing 전략Mock을 사용하는 시점에 대해 Classicist와 Mockist 관점을 접하며, 각 접근 방식의 장단점을 비교해 볼 수 있었습니다. Classicist는 실제 객체를 활용한 통합 테스트에 집중하는 반면, Mockist는 Mocking을 통해 테스트 효율을 높이고 빠르게 검증하는 데에 초점을 맞추는 방식입니다. 개인적으로는 Mockist 방식이 코드의 효율적인 테스트를 위해 더 적합하게 느껴졌습니다. Mock과 Stub, Spy를 포함한 다양한 테스트 Double의 기능을 익히면서 단위 테스트와 통합 테스트에 맞는 도구를 선택하는 방법을 명확히 알게 되었습니다. 개선된 테스트 작성에 대한 구체적인 팁더 나은 테스트를 위해 필요한 몇 가지 구체적인 조언을 배웠습니다. 먼저, 각 테스트는 하나의 주제에 집중하도록 구성하여 가독성을 높이는 것이 중요하다는 점을 강조하고 있습니다. 또한, LocalDateTime.now()와 같은 변동 가능한 값들은 제어 가능한 상태로 고정하고, 테스트 간 독립성을 보장하는 것이 좋다는 것을 확인할 수 있었습니다. 그리고 @ParameterizedTest와 @DynamicTest를 통해 반복적이거나 변동하는 시나리오를 효율적으로 테스트하는 방법도 배우게 되었습니다. Spring REST Docs와 API 문서화테스트를 기반으로 API 문서를 자동으로 생성하는 Spring REST Docs도 다루었습니다. 이는 개발 문서의 신뢰도를 높여주기 때문에 협업 시에도 큰 장점이 될 수 있지만, 설정이 다소 복잡하고 문서 작성 시 코드 양이 많아지는 단점도 존재합니다. 이번에 배운 내용을 바탕으로 Spring REST Docs 설정과 활용 방법을 추가로 학습해보고 싶습니다. 전체 회고4주간 진행된 스터디를 통해 클린 코드와 테스트 코드의 중요성을 재차 느끼게 되었으며, 이번 학습이 실제 업무와 개인 프로젝트에서 코드 품질을 높이는 데 큰 밑거름이 될 것이라고 생각합니다. 평소 테스트 코드와 관련해 갈망이 있었으나 실제로 깊이 있게 배워본 적은 없었는데, 이번 기회를 통해 실질적인 테스트 작성 방법과 Mocking 전략을 체계적으로 학습할 수 있어 좋았습니다.스터디가 끝난 후에도 배운 내용을 바탕으로 테스트 코드 학습을 꾸준히 이어나갈 계획입니다.

ykm8864

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

마지막 4주차 발자국을 작성하는 시간이다. Mockito로 Stubbing 하기mock에 대한 원하는 행위(return)을 정의한 다음 내가 테스트하고자하는 서비스에만 집중. ⇒ stubbing이라고 일컷는다.Mockito에서 Stubbing은 모의 객체(Mock)의 메서드가 호출될 때 반환할 값을 미리 설정해 두는 작업을 말한다. 테스트할 때 실제 객체 대신 Mockito로 생성한 모의 객체를 사용하는데, 이때 필요한 메서드의 동작을 미리 정의해 주는 것을 Stubbing이라고 한다. 이렇게 설정하면 실제 메서드가 호출되지 않고, 지정된 값이나 행동을 반환하므로 독립적인 테스트가 가능한 이점이 있다..Stubbing을 설정하려면 when과 thenReturn 또는 thenThrow 메서드를 사용하여 원하는 값을 정의한다. Test Double단위 테스트에서 사용되는 용어로, 테스트하려는 코드의 의존성이나 협력 객체를 대신하여 사용하는 객체를 말한다. 크게 아래 5가지가 있다.Dummy : 아무 것도 하지 않는 깡통 객체Fake : 단순한 형태로 동일한 기능은 수행하나, 프로덕션에서 쓰기에는 부족한 객체 ex) FakeRepository (메모리에 휘발성으로 관리)Stub : 텟트에서 요청한 것에 대해 미리 준비한 결과를 제공하는 객체.Spy : Stub이면서 호출된 내용을 기록하여 보여줄 수 있는 객체다. 일부는 실제 객체처럼 동작시키고 일부만 Stubbing 할 수 있다.Mock : 행위에 대한 기대를 명세하고, 그에 따라 동작하도록 만들어진 객체Mock ≠ Stub공통점은 가짜객체. 차이점 : Stub 은 상태에 대한 검증, Mock은 행위에 대한 검증이다. @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이테스트 객체를 생성해주는 다양한 어노테이션이 있다. 크게 Spring 기반 테스트인지 Mockito 기반 테스트인지 구분을 하고 spy 인지 아닌지로 나눌 수 있다.Mokito사용해서 mock만들려면 클래스 상단@ExtendWith(MockitoExtension.class) 를 붙여줘야한다.@Mock:Mockito에서 사용하는 애노테이션으로, 인터페이스나 클래스를 Mock 객체로 생성한다.해당 객체는 실제 동작 없이 사전 설정된 행동만 수행하도록 설정된다.주로 단위 테스트에서 사용@MockBean:Spring Boot에서 사용하는 애노테이션으로, 스프링 컨텍스트에 Mock 객체를 등록한다.스프링 빈으로 주입이 필요한 경우 사용하며, 실제 빈을 Mock 객체로 대체하여 통합 테스트에 적합.@Spy:Mockito에서 사용하는 애노테이션으로, 실제 객체를 기반으로 스파이 객체를 생성한다.원래 메서드를 호출하면서도, 필요한 경우 특정 메서드만 Mocking하여 동작을 변경할 수 있다.@SpyBean:Spring Boot에서 사용하는 애노테이션으로, 스프링 컨텍스트에 실제 객체를 스파이 객체로 등록한다.기존 스프링 빈의 동작을 유지하면서, 특정 메서드만 Stub 처리하여 통합 테스트에서 부분적으로 변경하고 싶을 때 유용하다.@InjectMocks:Mockito에서 사용하는 애노테이션으로, @Mock이나 @Spy로 생성된 객체를 주입해주는 역할을 한다. BDDMockito어? given 절인데 문법이 when이네?Mockito를 감싸고있는 BDD(GIVEN, WHEN, THEN)스타일로 작성만 할 수 있게 이름만 바꾼 상태!! 그래서 그냥 BDDMockito 사용하면 된다. 앞으로는 Classicist VS. Mockist테스트코드를 전적으로 믿냐 아니냐에 대한 견해 차이로 보인다.Mockist : 단위테스트할떄 이미 모든걸 mocking으로 테스트했으니까, 통합테스트할떄는 다 mocking처리해서 기능 보장된 애들은 다 쳐내고 해야하는 것만 짤라서 하자. vs.Classicist : mocking을 다 해버리면 실제 production에서 실 객체가 움직일때의 올바른 동작을 어떻게 보장할 수 있어? 개인적으로 나도 우빈님처럼 Classicist에 더 가까운 편인 거 같다. 테스트 코드는 아무리 완벽하게 하려고 해도 프로덕션 코드에 따라가지 못한다고 생각하고 결국 테스트를 작성하는 사람도 나 자신이기 때문에 허점은 존재할 거 같다. Clean 테스트 코드테스트 코드도 한 문단에 한 주제테스트는 문서다! 글쓰기란 관점에서 봤을때, 한 문단에 하나의 주제를 가지고 있는 게 좋다.완벽하게 제어하기테스트하기 위한 환경을 조성할 때, 모든 조건을 완벽하게 제어할 수 있어야한다.테스트 “환경”의 독립성을 보장하자따라서 given절은 일반적인 생성자만 기입되는게 좋다.테스트 “간”의 독립성을 보장하자두개 이상 테스트 간의 독립성을 보장하자.Test FixtureFixture : given절에 쓰이는 객체들(내가 원하는 결과를 얻기위해 고정해둔 조건들) 이떄 when절에 given절 내용이 들어가는 경우가 많은데 이런 실수를 범하지 말자. 99%의 when절은 한줄이다. (메서드실행) @ParameterizedTest하나의 테스트 메서드를 다양한 매개변수로 반복 실행할 수 있도록 하는 어노테이션사용 예: 여러 입력값에 대해 동일한 로직을 테스트할 때 활용주요 기능:@ValueSource: 간단한 값 배열을 전달(e.g., int, String).@CsvSource / @CsvFileSource: CSV 형식의 데이터나 파일을 통해 여러 매개변수를 전달@MethodSource: 메서드를 통해 테스트 데이터를 전달 @DynamicTest런타임에 테스트 케이스를 생성한다. 정적인 테스트 메서드로 정의되는 것이 아닌 Stream<DynamicTest> 형식으로 생성한다.사용 예: 테스트 시나리오가 복잡하고 실행할 테스트 케이스의 수나 조건이 실행 중 결정되는 경우에 유용하다.주요 기능:동적 테스트를 생성하려면 @TestFactory 어노테이션 필수!Stream<DynamicTest>를 반환한다.메서드 내부에서 테스트 케이스와 이름을 동적으로 정의할 수 있습니다. Spring REST DocsAPI의 구조, 기능, 요청 및 응답 형식 등을 문서화AsciiDoc의 도움을 받아 MarkDown문법으로 작성하고 라이브러리를 의존성 주입받아 html로의 변환도 쉽게 할 수 있다.Swagger vs. REST DocsSwagger적용이 쉽다.문서에서 바로 api호출을 수행할 수 있다.프로덕션 코드에 침투적이다.신뢰도가 떨어진다.REST Docs테스트를 통과해야 문서가 만들어지므로 신뢰도가 높다.프로덕션 코드에 비침투적이다.코드의 양이 많고 설정이 어렵다.강의에서 여러 설정을 잡고 최종적으로 REST Docs문서를 생성하여 브라우저를 통해 확인해보았다. 테스트 과정을 거치며 각각의 문서 조각들이 모여 하나의 문서가 되는 것이 재밌었다. 4주차 회고Keep (만족했고, 앞으로도 지속하고 싶은 부분)테스트 코드를 짤 때 어떤 점이 문서화를 할 때 좋을지 다시 한번 생각해볼 수 있으며, 각종 어노테이션을 습득할 수 있었다. 더불어 api를 많이 짤 일이 생길 텐데 이렇게 REST Docs를 통해 문서 규격을 자동화하여 관리할 수 있다는 점이 너무 유용하고 값진 경험이였다. 앞으로 토이프로젝트를 하면서도 많이 사용해볼 예정이다..Problem (아쉬웠던 점)전반적으로 이번 강의를 마치면서 강사님의 생각을 내가 완전히 따라가지는 못했다는 생각이 많이 든다. 정말 훌륭한 강의를 펼쳐주시지만 내가 모든 것을 내꺼로 체화시키는 과정은 별도로 필요해보인다. 남이 하는걸 봤을때 쉬워보이면 그 사람이 정말 잘하는 거라고 하던데 나도 그런 경지가 되고 싶다는 생각이 들었다. Try (다음에 시도해볼 점)전반적으로 배운 내용을 적용해본 토이프로젝트를 진행할 예정이다. 클린코드와 테스트코드 원칙을 기반으로 서비스를 런칭시켜볼 예정이다. 너무 많은 것을 배워 행복한 일주일이였다. 

백엔드워밍업클럽백엔드2기발자국몰입하는개발자테스트코드

dmstjd0214

[인프런 워밍업 스터디 클럽 2기_BE] 4주차 회고록 정리

Presentation Layer외부 요청을 가장 먼저 받는 계층에서는 파라미터에 대한 최소한의 검증을 진행한다.테스트 진행 방식비즈니스 로직과 영속성 계층은 스프링을 띄워 테스트하고, Presentation Layer는 Mocking 기술(MockMVC)을 활용한다.MockMVC란?의존성이 복잡해 실제 테스트가 어려운 경우, 스프링 MVC의 동작을 가짜 객체로 재현할 수 있는 테스트 프레임워크 요구사항관리자 페이지에서 상품 등록 가능상품명, 타입, 상태, 가격 등 입력받음 Native Query 작성 이유TDD를 염두에 두고 테스트 -> 구현 -> 리팩토링 습관화서비스, 레포지토리 코드가 얇으면 비슷하지만 코드가 많아질수록 차이가 생기므로 사소한 부분도 테스트 작성 권장.동시성 이슈가 있다면 UUID 등을 활용.@Transactional읽기 전용 설정 시, 조회만 가능하고 CUD는 제한됨. JPA는 1차 캐시에 스냅샷을 생성해 변경을 감지하지만, 읽기 전용 시 성능 이점이 있음.CQRS (Command/Read 분리)CUD와 R은 8:2 비율로 책임을 분리하고, 마스터(쓰기)/슬레이브(읽기) DB로 나누어 엔드포인트를 관리할 수 있다.@EnableJpaAuditing @Configuration public class JpaAuditingConfig { } 전역 설정을 클래스로 나누어 관리.기본 생성자를 이용해 역직렬화를 진행하고, 컨트롤러 테스트 코드는 다음과 같이 작성:mvc.perform( post("/api/v1/products/new") .content(objectMapper.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON) ) .andDo(print()) .andExpect(status().isOk()); 규격화된 응답을 위해 ApiResponse 클래스를 사용해 공통 응답을 반환. 기존의 ResponseEntity와 차이점을 확인.NotNull, NotEmpty, NotBlank 차이점성격에 따라 검증 위치를 분리하는 것이 중요하며, 하위 레이어가 상위 레이어를 알아야 하는 상황을 피하기 위해 서비스용 DTO를 따로 작성하고, 컨트롤러에서 toServiceDTO로 변환해 처리.다른 API의 의존성 문제를 피하기 위해 DTO를 통한 책임 분리 중요요구사항매출 통계에 대한 메일 전송을 하는 요구사항 // stubbing when(mailSendClient.sendEmail(any(String.class), any(String.class), any(String.class), any(String.class))) .thenReturn(true); String의 값의 어떤 것이든 좋다! 라고 해서 테스트를 진행하는 방법TestDoubleDummy : 아무 것도 하지 않는 깡통 객체Fake : 단순한 형태로 동일한 기능은 수행하나, 프로덕션에서 쓰기에는 부족한 객체Stub : 테스트에서 요청한 것에 대해 미리 준비한 결과를 제공하는 객체 그외에는 XSpy : Stub이면서 호출된 내용을 기록하여 보여줄 수 있는 객체 일부는 실제 객체처럼 동작시키고 일부만 StubbingMock : 행위에 대한 기대를 명세하고, 그에 따라 동작하도록 만들어진 객체Stub vs Mock가장 큰 차이는 Sutb은 상태 검증,Mock은 행위검증이다.꿀팁Mail같은 긴 네트워크를 탈 서비스 일때 트랜잭션을 걸지 않는것이 좋다.@Mock: 테스트에서 가짜 객체 만들어서 의존성 대체할 때 사용.@Spy: 실제 객체 쓰되, 필요한 메서드만 Mocking 가능.@InjectMocks: @Mock이나 @Spy로 만든 객체들 주입해서 테스트 대상 생성.BDDMockito TestBDDMockito도 있다.Mockito와 동일하지만 BDD스타일의 기능만 따랐기때문에 모든 동작이 동일하다.평소에 BDDMockito를 사용하는게 좋을거같다!1. 한 문단에 한 주제!케이스가 많아지면 복잡한 방법보단 간단히 나누는 게 좋고, @DisplayName은 추상화보다는 명확하게 써야 한다.2. 완벽하게 제어하기createOrder처럼 오버로딩된 건, 제어가 안 되면 상위 레벨로 올려서 테스트 환경을 만들어야 한다.3. 테스트 환경의 독립성 보장다른 API를 가져다 쓰면 의존성 올라가서 테스트 독립성이 떨어짐. 환경이 깨지지 않게 보장 필요.stock1.deductQuantity(1); 이렇게 수량 바꾸면 테스트 깨짐. 실패는 when이나 then에서만 나와야 하고, 길어지면 추적 어려워짐.생성자 기반이 좋고, 순수 빌더나 생성자 방식 추천. 팩토리 메서드는 지양.4. 테스트 간 독립성 보장테스트 간 자원 공유 안 하는 게 좋고, static으로 인스턴스 쓰지 않기. 필요하면 5번처럼 해라.5. 한눈에 보이는 Test Fixture 구성원하는 상태로 고정시키는 객체들로 beforeAll, BeforeEach, AfterEach가 대표적.Fixture 수정 시 모든 테스트에 영향 미치니 조심해서 사용.언제 쓰면 될까?테스트 이해에 문제 없거나, 수정해도 영향 없을 때.필요한 구성요소만 넣고, 최소한으로 관리.Test Fixture 클렌징DeleteAll vs DeleteAllInBatchdeleteAll은 N개 반복으로 속도 느려짐, DeleteAllInBatch가 더 빠름.deleteAll은 순서 상관없이 다 지워줘서 안전.Repository delete 순서 조정Product PK가 Order에 묶여 있어 참조 문제 발생. 순서 맞춰 삭제 필요.@Transaction은 평소에 쓰고, Batch 통합 테스트는 상황에 따라 사용.@ParameterizedTest케이스 확장 시 값이나 환경 바꾸고 싶을 때 사용. 변수 없이 어노테이션만으로 편리하게 사용 가능.@DynamicTest시나리오 테스트 진행 시 사용. 서버 여러 번 실행돼 속도 느려지지만, 테스트 환경 맞춰 설정하면 OK.public abstract class IntegrationTestSupport{}Mock 사용 시 새로운 서버 구성 필요하므로 상위 클래스로 올리거나 테스트 환경 두 개로 나눠서 사용.private 메서드는?테스트 안 해도 된다.객체를 분리하는걸 고려해야한다.!!RestDocsAPI 문서를 자동으로 생성하는 도구 테스트 코드와 연계해 실제 요청·응답을 기반으로 신뢰성 높은 문서를 만들어줌!과제를 진행하며 given then when을 어떻게 나누고 Test Fixture을 어떤 방식으로 나누는지 고민을 많이했다.강의에서 보던 코드를 나누는건 할만했지만 직접 나누려고 생각을 하다보니 조금 힘들었던 부분이 많았다. 또한 Mocking에 대한 정확한 이해가 되지 않았는데 개념들을 정리하다보니 글을 정리하는 순간순간 이렇게 쓰면되겠다! 라고 생각하게 됐다.마지막 주차고 많이 밀렸기도 했고 힘든 부분도 많았지만 또 뒤돌아본다면, 나에게 돌아오는 실력들이 근육처럼 붙을꺼라 생각한다!관련 코드 : https://silvercastle.notion.site/18-12a1dc39fd1e80c3914ac646daf52996?pvs=4 

한나

✏️[인프런 워밍업 클럽 2기] 프로덕트 디자인 4주차 🐾

4주간의 프로덕트 디자이너 스터디를 마무리하며, 매주 새로운 도전과 배움의 기회를 마주했던 값진 시간이었습니다. 🥳여러 서비스 디자인을 직접 만들어보면서 실제 프로젝트에 적용할 수 있는 실용적인 팁들을 알 수 있었고, 페이지들을 구현하는 과정에서 컴포넌트를 활용해 효율적으로 서비스를 관리하는 방법에 대해서도 이해할 수 있었습니다. 아직 부족한 점이 많지만 이번 경험을 토대로 계속해서 성장해나가고 싶습니다.특히 마지막 주차까지 완주하면서, 처음에는 낯설게만 느껴졌던 개념들이 조금씩 제 것이 되어가는 과정을 경험할 수 있었습니다.💪📌배움배리어블 다크모드와 멀티 브랜드 모드 등록 후 활용법 B2C 이러닝 페이지 / 모바일 OTT 서비스 프로세스각 페이지 다크모드 버전 / 반응형 모드 제작 프로세스 타이포그래피 베리어블 등록하는 법배리어블을 활용한 프로토타입 만들기 📌미션다양한 서비스 화면들을 직접 구현해보면서 컴포넌트의 실질적인 활용법을 익힐 수 있었습니다. 특히 각 서비스의 핵심 기능에 맞는 UI 설계와 베리어블을 활용해 다크모드와 브랜드모드 전환, 반응형 모드를 구축해보면서 디자인 시스템의 확장성과 일관성을 유지하는 방법을 배울 수 있었습니다. 아직은 베리어블 활용이 완벽하지 않아 더 많은 연습이 필요하지만, 이번 미션을 통해 실무에서 활용할 수 있는 귀중한 경험들을 쌓을 수 있었습니다.❗이건 꼭 알아두자! ꙳꒰ ੭⑅•͈ ·̮ •͈꒱੭컴포넌트를 활용한 효율적인 서비스 구축 및 관리다크모드 및 브랜드모드 적용 및 활용법반응형 모드 적용하는 방법타이포그래피 배리어블 등록하기베리어블을 활용한 프로토타이핑 방법📌회고스스로 칭찬하고 싶은 점미션을 미리 완료하고 특강 전에 실무에서 적용할 부분들을 직접 적용해보았습니다.완전히 이해가 안된 부분은 반복해서 다시 시청하고 회사 디자인시스템 구축 시 놓친 부분들을 다시 수정 적용해보았습니다.사이드프로젝트에도 배리어블을 적극 활용해보기 위해 계속 연습해보았습니다.아쉬웠던 점특강 때 내용을 통해 배리어블을 활용한 프로토타이핑은 한 번에 이해가 되지 않아 미션4부터 어려움이 있어 아쉬움이 있었습니다.강의를 완강하고 디자이너가 왜 꼼꼼해야하는지 체감이 되었고 실무에서 그만큼 놓친 부분이 많았던 것 같아 아쉬움이 남았습니다.보완하고 싶은 점프로토타이핑은 평소에도 재밌어하던 주제였는데 배리어블을 활용한 프로토타이핑은 많이 어려움이 있었다보니 반복 학습을 통해 완전히 내것으로 만들어가고 있습니다. 강의와 미션을 하면서 깨달은 점 중 하나는 반복을 하면 결국 이해가 되고 내 것이 될 수 있구나 였기 때문에 다시 반복해보는 중이고 꼭 실무에 적용하려고 합니다🔥저번 특강 때도 알려주신 여러 디자인시스템을 분석해봐야할 필요성도 많이 느꼈습니다.앞으로의 계획완주를 해서 다음주 계획의 주제를 앞으로의 계획으로 변경 후 작성해보았습니다. 이번 강의들을 통해 배운 디자인 시스템을 기반으로 실무에서 활용할 수 있는 체계적인 컴포넌트 설계와 배리어블 구축 및 문서화 작업을 이어나가려 합니다. 특히 Figma의 다양한 기능들을 더 깊이 학습하여 효율적인 디자인 워크플로우를 구축하고자 합니다. 또한 프로덕트 디자이너로서 개발자와의 원활한 협업과 더 나은 사용자 경험 구현을 위해 HTML, CSS, JavaScript 등 프론트엔드 기술에 대한 학습을 시작하려 합니다. 이를 통해 기술적 제약사항을 이해하고 실현 가능한 디자인을 제안할 수 있는 디자이너로 성장하고 싶습니다.볼드님의 강의를 통해 디자인 시스템의 중요성과 실무에서 활용할 수 있는 다양한 인사이트를 얻을 수 있었습니다. 특히 베리어블 활용법과 같은 실질적인 스킬을 꼼꼼하게 알려주신 덕분에, 한 단계 성장할 수 있는 값진 시간이었습니다.🐾 진심으로 감사드립니다.😊

UX/UIUXUI피그마프로덕트디자인볼드UX

백엔드 프로젝트 2기 코틀린 - 4주차

스프링 시큐리티스프링 시큐리티를 사용하기 위한 의존성 추가implementation("org.springframework.boot:spring-boot-starter-security")스프링 시큐리티를 이용하여 로그인 을 구현하기 위해선 컨피그클레스를 추가해야 합니다. Configuration class AdminSecurityConfiguration { @Bean fun passwordEncoder(): PasswordEncoder { return BCryptPasswordEncoder() } @Bean fun filterChain(httpSecurity: HttpSecurity): SecurityFilterChain { return httpSecurity .authorizeHttpRequests { authorizeHttpRequests -> authorizeHttpRequests .requestMatchers(AntPathRequestMatcher("/admin/**")).authenticated() .anyRequest().permitAll() }.csrf { csrf -> csrf.disable() }.headers { headers -> headers.addHeaderWriter(XFrameOptionsHeaderWriter(XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)) }.formLogin { formLogin -> formLogin.defaultSuccessUrl("/admin") }.logout { logout -> logout.logoutRequestMatcher(AntPathRequestMatcher("/admin/logout")) .logoutSuccessUrl("/") }.build() } }스프링 시큐리티를 이용하기 위한 빈들을 생성합니다 BCryptPasswordEncoder 는 해시 함수를 이용하여 암호화된 비밀번호를 생성해줍니다.SecurityFilterChain 프로젝트의 보안 필터를 체인 형태로 구성하여 csrf설정을 허용하지 않을 수 있고로그인과 로그아웃의 설정을 추가할 수 있습니다. 프로젝트 배포Docker Compose 파일 설정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도커 컴포즈 파일을 통해 도커에서의 포트와 로컬에서의 포트를 설정하여 DB를 연결할 수 있습니다.  DockerfileFROM openjdk:17 LABEL maintainer="infomuscle10@gmail.com" VOLUME /tmp EXPOSE 8080 ARG JAR_FILE=build/libs/portfolio-0.0.1-SNAPSHOT.jar ADD ${JAR_FILE} portfolio-yongback.jar ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom", "-jar", "/portfolio-yongback.jar"build/libs/portfolio-0.0.1-SNAPSHOT.jar 의 자르 파일을 이용하여 빌드를 실행합니다. 그 실행된 파일은 도커에서 portfolio-yongback.jar 로 표시 됩니다. Google Clud Platform에서 Compute Engine 인스턴스 생성하기AWS, Azure와 같은 클라우드 컴퓨팅 서비스로서 컴퓨터의 일부를 금액을 지불하여 사용하는 기술 입니다. GCP의 컴퓨트 엔진을 이용하여 인스턴스를 생성, 프로젝트를 배포 합니다. 도메인 연결프로젝트가 빌드된 ip를 DNS를 이용하여 접속할 수 있게 도메인 이름을 구입하여 사용할 수 있습니다. HTTPS 연결하기http로 연결되는 프로젝트를 https를 이용하여 보다 안전하게 연결하여 사용할 수 있게 합니다.

조혜림

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

열심히 따라가다 보니 어느 새 스터디 마지막 주가 다가왔다. 이번 주에는 B2C 이러닝 페이지와 모바일 OTT 서비스 페이지를 구현해보는 과제를 수행하였고 토요일 특강 시간에는 프로토타입을 활용하여 다양한 인터렉션을 구현하는 방법을 배우는 시간을 가졌다. 금주에 배운 강의 내용은 다음과 같다.  B2C 이러닝 페이지 제작지금까지 제작한 다양한 컴포넌트를 활용하여 이러닝 페이지 제작모드를 활용하여 브랜드 및 다크모드 적용배리어블을 활용하여 반응형 버전 제작엑셀 시트 연동 플러그인을 활용하여 데이터 연결지금까지 강의를 통해 제작한 다양한 요소를 조합하여 페이지를 제작하였다. 제작된 컴포넌트를 조합하면 쉽고 빠르게 페이지 디자인을 구현할 수 있고 여기에 설정해둔 모드를 적용하기만 하면 단숨에 지정된 색상 톤에 맞는 색상으로 변경이 가능하다. 모드를 통해 일일히 디자인 요소 하나하나를 수정할 필요 없이 클릭 몇 번만으로 자동으로 다른 스타일의 디자인을 구현할 수 있는 매우 높은 생산성을 경험할 수 있었다. 다만 페이지를 제작하다보면 자동으로 지정되는 #000이나 #FFF 값들을 확인하지 않고 다크 모드로 넘어가서 일부 컴포넌트를 일일히 수정하기도 했는데 이런 시행착오를 교훈 삼아 앞으로 실무에서 적용하거나 혼자 토이 프로젝트를 진행할 때에는 다크 모드로 변환하기 전에 확인하는 과정을 거쳐야겠다.  모바일 OTT 서비스 페이지 제작지금까지 제작한 다양한 컴포넌트를 활용하여 모바일 OTT 메인페이지와 컨텐츠 페이지 제작엑셀 시트 연동 플러그인을 활용하여 데이터 연결Movies & TV Shows 플러그인을 활용하여 이미지 데이터를 제작하고 컴포넌트로 활용모바일 OTT 서비스는 주로 다크모드만 제작되기 때문에 해당 페이지를 제작할 때에는 브랜드 모드만 활용하였다. 또한 모바일이 메인 디바이스인 만큼 반응형도 모바일과 태블릿만 제작하는 시간을 가졌다. 실제로 페이지를 구현하면서 컴포넌트 구조를 유연하지 않게 제작하여 수정을 거치거나 혹은 컴포넌트 자체를 새로 만드는 경우도 있었는데 이러한 시행착오를 겪으면서 페이지에 배치할 때 보다 유연하게 작동할 수 있는 컴포넌트 구조에 대해 고민할 수 있었다. 온라인 특강 : 배리어블 프로토타이핑 강의이 특강은 진짜 거짓말이 아니라 '이걸 내가 무료로 청강 해도 되는건가?' 라는 생각이 진지하게 들 정도로 너무나도 유용하고 어디서도 찾아보거나 듣기 어려운 강의였다. 선생님께서 사전에 구현해보고 싶은 프로토타이핑 인터렉션을 모집하셨고 그 중 3가지 예제를 구현해보는 시간을 가졌다. 다양한 인터렉션을 실제로 구현하는 방법을 배울 수 있는 점에서 굉장히 유익하고 도움이 많이 되는 시간이었다. 3가지 예제 중 앞의 1번째와 2번째는 기존 강의를 토대로 추가적인 프로토타이핑을 적용하는 과정이었다면 마지막 3번째 예제는 if와 else를 활용하여 보다 복합적이고 다양한 경우를 반영한 인터렉션을 구현하는 방법을 배울 수 있었다. 실무에서 다른 팀 뿐만 아니라 고객과 커뮤니케이션 할 때에도 보다 구체적인 구현 방향을 시각적으로 보여줄 수 있다는 점에서 많이 연습해서 내 능력으로 체득하고 싶은 욕심이 생기는 그런 기능이었다.  4주간의 스터디를 돌아보며그간 피그마를 배워야지 생각하면서도 차일피일 미뤄왔던 건 물론 개인적인 여러 일이 있었기 때문이기도 하지만 가장 큰 건 이미 경력자로써 현실에 안주했기 때문이 아니었나 하는 생각이 든다. 이제 피그마가 현업에서 표준화 되고 있는 지금에서야 배움을 시작하였지만 그럼에도 안주하지 않기 위해 노력했고 처음 다루는 툴에 버벅거리기 일쑤였지만 포기하지 않고 끝까지 완주했다는 점에서 정말 뿌듯하고 기쁜 순간이다. 그리고 금주부터는 근무 중인 회사에서도 내부 회의를 거쳐 피그마를 우선 적용해보기로 하여 업무에서도 피그마를 활용할 수 있게 되면서 디자이너로써 하나의 스킬을 익히고 활용해볼 수 있는 계기가 된 것 같아 매우 좋았다. 또한 선생님의 강의를 보면서 따뜻한 경력 속에 파묻혀 안주해왔던 나 자신을 많이 돌아보게 되었다. 선생님도 실무에서 많이 바쁘실텐데 과제 하나하나 꼼꼼하게 확인해주시고 거의 매주 특강까지 준비해주시는 열정을 보면서 나 역시 업무에서든 인생에서든 쉽게 안주하지 않고 발전하는 길을 찾아나가는 힘을 가진 사람이 되어야겠다는 생각을 많이 하게 되었다.  스터디는 이제 마무되지만 공부와 실습은 이제부터 본격적으로 시작이라고 생각한다. 아직 익숙해질 기능이 많이 남아 있고 모르는 기능도 많이 있어 어려웠던 부분은 강의도 다시 듣고 복습해보면서 더욱 발전해나가야겠다. 그리고 실무에서 피그마를 활용함과 동시에 퇴근 후 남는 시간이나 주말에는 토이 프로젝트도 진행해보면서 많은 경험을 해보고 거기서 실력을 늘려갈 수 있도록 노력해야겠다.

UX/UIUX/UIFigma프로덕트디자인디자인시스템워밍업스터디

taeminseo

워밍업 클럽 스터디 2기 [클린코드 & 테스트코드] - 4주차 회고

4주차 회고1. Mock을 마주하는 자세Test DoubleDummy : 아무 것도 하지 않은 깡통 객체Fake : 단순한 형태로 동일한 기능은 수행하나 , 프로덕션에서 쓰기에는 부족한 객체Stub : 테스트에서 요청한 것에 대해 미리 준비한 결과를 제공하는 객체. 그 외에는 응답하지 않는다Spy : Stub이면서 호출된 내용을 기록하여 보여줄 수 있는 객체. 일부는 실제 객체처럼 동작시키고 일부만 Stubbing할 수 있다. (상태 검증)Mock : 행위에 대한 기대를 명세하고 , 그에 따라 동작하도록 만들어진 객체 (행위 검증)2. 더 나은 테스트를 작성하기 위한 구체적 조언한 문단에 한 주제하나의 테스트가 한 문단이라고 생각한다면 하나의 테스트는 하나의 주제만을 가져야 한다.테스트 환경의 독립성을 보장하자다른 테스트에 영향을 주지 않아야함테스트 간 독립성을 보장하자두개 이상의 테스트에 대해 서로 영향을 주지 않아야함Test Fixture 구성하기fixture : 고정물 , 고정되어 있는 물체테스트를 위해 원하는 상태로 고정시킨 일련의 객체Test Fixture 클렌징deleteAll vs deleteAllInBatch , Text Fixture를 정리하고 초기화@ParameterizedTestif문 같은 논리 구조가 들어가는 경우 지양하기하나의 테스트에 값이 바뀌는 경우 사용@DynamicTest인스턴스의 여러 시나리오 테스트할 경우 사용3. Appendix학습 테스트잘 모르는 기능 , 라이브러리 , 프레임워크를 학습하기 위해 작성하는 테스트여러 테스트 케이스를 스스로 정의하고 검증하는 과정을 통해 보다 구체적인 동작과 기능을 학습할 수 있다.관련 문서만 읽는 것보다 훨씬 재밌게 학습할 수 있다.Spring Rest Docs테스트 코드를 통한 API 문서 자동화 도구API 명세를 문서로 만들고 외부에 제공함으로써 협업을 원활하게 한다.기본적으로 AsciiDoc을 사용하여 문서를 작성한다Rest Docs vs SwaggerRestDocs장점테스트를 통과해야 문서가 만들어진다. (신뢰도가 높다)프로덕션 코드에 비침투적이다단점코드 양이 많다설정이 어렵다Swagger장점적용이 쉽다문서에서 바로 API 호출을 수행해볼 수 있다.단점프로덕션 코드에 침투적이다.테스트와 무관하기 때문에 신뢰도가 떨어질 수 있다.회고워밍업 클럽 2기의 모든 일정이 끝낫다. 한달이 어떻게 지나갔는지 모르도록 많이 바뻐서 따라가기 힘들었지만 일정을 따라가면서 소화한것이 뿌듯하다. 읽기 좋은 코드와 테스트 코드에 지식이 한줄 추가 된거같다. 시간이 될때 한번 부족한 부분에 대한 복습을 해야할 것 같다.

wisehero

[워밍업 클럽 BE 2기 - 클린코드, 테스트코드] 4주차 발자국

4주차 발자국 드디어 이번 워밍업 클럽의 마지막 날입니다. 0기와 2기에 참여했었고 서로 다른 주제였는데 다음 워밍업 클럽 주제가 또 기다려지네요. 개인적으로는 코틀린을 기다리고 있습니다. 이번 주차에 포함된 진도는 아래와 같습니다.Presentation LayerMock을 마주하는 자세더 나은 테스트를 작성하기 위한 구체적 조언학습 테스트 | RestDocs가장 의미있다고 여겨졌고 개인적으로 많이 배운 주제는 Day 17의 더 나은 테스트를 작성하기 위한 구체적 조언 파트였는데요. 제가 개인적으로 실수가 많은 부분이 테스트 환경의 독립성을 보장하는 것과 테스트 간의 독립성을 보장하는 것을 놓쳤었는데요. 우빈님의 조언을 얻고 이 부분을 더 신경써서 테스트 코드를 작성할 수 있겠다는 자신감을 얻었습니다. 또 몰랐던 꿀팁을 하나 얻었었는데요! 저는 테스트 픽스처 클린징을 할 때 그냥 deleteAll을 사용했는데 deleteAllInBatch를 사용해야 하는 이유에 대해서 알 수 있어서 좋았습니다. 평소 놓쳤던 부분이었는데 좀 더 시간 효율적인 테스트를 작성할 수 있게 되었고 앞으로 사내에서 JPA 프로젝트에서 운영 소스를 작성할 때도 조금 더 신경을 쓸 수 있을 것 같습니다. 이외에도 테스트를 좀 더 효율적으로 수행할 수 있는 여러 꿀팁들을 얻을 수 있어서 좋았고요. 테스트 코드가 아직까지는 누군가에겐 귀찮은 문제, 누군가에겐 팀원들을 설득해야하는 문제로서 남아있는데 더 많은 사람들이 우빈님의 강의를 듣고 테스트 코드 작성의 필요성을 느끼고 공감했으면 좋겠습니다.   

백엔드클린코드테스트코드워밍업클럽

spacebar

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

4주차 학습내용Practical Testing : 실용적인 테스트 가이드강의를 학습하며 작성한 내용입니다.MOCK을 마주하는 자세Mockito로 Stubbing하기테스트에 불필요한 과정을 줄이기 위해 mock을 사용해서 테스트를 한다.메일전송같이 긴 작업 (트랜잭션에는 참여하지 않아도 되는)에는 @Transactional을 걸지 않는 게 좋다.stubbing : mock 객체에 원하는 행위를 정의하는 것Test DoubleStub - 상태 검증 (State Verification)Mock - 행위 검증 (Behavior Verification)순수 Mockito로 검증해보기@Mock : @ExtendWith(MockitoExtension.class) 를 함께 사용해야 함.verify() : mock객체에 대해 원하는 메서드가 특정 조건으로 실행되었는지 검증@Spy : 한 객체에서 일부는 실제 객체를 쓰고 싶고 나머지 일부만 stubbing을 하고 싶을 때 사용Mockito의 @Spy는 실제 객체를 기반으로 만들어지기 때문에 when절을 쓸 수 없다. 더 나은 테스트를 작성하기 위한 구체적 조언테스트 환경의 독립성을 보장하자@DisplayName("재고와 관련된 상품이 포함되어 있는 주문번호 리스트를 받아 주문을 생성한다.") @Test void createOrderWithStock() { // given Stock stock1 = Stock.create("001", 2); Stock stock2 = Stock.create("002", 2); stock1.deductQuantity(3); ... //when //then assertThatThrownBy(() -> orderService.createOrder(request, registeredDateTime)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("재고가 부족한 상품이 있습니다."); } 재고가 2개인데 3개를 차감하려고 하면 차감할 수량이 없다고 테스트에 오류가 발생한다. 테스트하고자 하는 행위는 createOrder인데 재고 차감이라는 다른 행위를 끌어다쓰면서 두 가지 케이스가 혼합이 되어있기 때문에 주문 생성 자체에 대한 테스트 실패가 아니라 given절을 생성하다가 테스트를 실패하게 된다. 이런 경우 테스트가 더 복잡해지면 실패 원인을 유추하는 데 어려움이 생긴다.테스트에 독립성이 보장되지 않으면 복잡해졌을 때 실패 원인을 유추하는 데 어려움이 생긴다.테스트에서는 팩토리 메서드를 지양하는 게 좋다.팩토리 메서드도 어떤 의도를 갖기 때문에 순수한 생성자나 빌드로 given절을 구성하는 게 더 좋다.최대한 독립성을 보장해서 테스트 환경을 구성하는 것이 좋다.테스트 독립성을 보장하자기본적으로 테스트는 순서에 무관해야 한다. a테스트가 수행된 이후에 b테스트가 수행되어야 성공한다는 개념은 없어야 한다. 독립적으로 언제 수행되던지 같은 결과를 내야 한다.Test Fixture테스트를 위해 원하는 상태로 고정시킨 일련의 객체공통의 fixture는 테스트의 결합도를 높여 모든 테스트에 영향을 주기 때문에 지양하는 것이 좋다.builder의 parameter에는 클래스 내에서 필요한 것들만 남겨놓는 것이 좋다. 미션레이어 아키텍처에 대해 나만의 언어로 풀어 쓰는 미션이었는데, 강의를 듣고 공부도 했지만 막상 설명한다고 생각하고 쓰려니 헷갈리는 부분들이 생겨서 다시 공부해보면서 작성했다.Mockito 관련 애노테이션의 차이점을 정리하고, 테스트코드를 적절히 배치하는 미션이었는데 배운 걸 적용한다는 느낌이 잘 드는 미션이어서 수월하게 수행한 것 같다. 💬 회고하루에 들어야 하는 강의 양이 생각보다 많아서 매일매일 진도표대로 듣지는 못했지만 워밍업클럽을 통해 약간의 강제성이 생겨 들어보고 싶었던 로드맵을 빠르게 훑어볼 수 있었다. 리팩토링이나 제대로 된 테스트코드 작성은 멀게만 느껴지고 막막했는데 강의를 듣고, 미션을 수행하다보니 어떤 방향으로 공부하고 적용시켜 나가야할지 어느 정도 감이 잡힌(?) 느낌이다. 계속 공부하면서 내것으로 만들기 위해 적용하고 발저시키는 연습을 해야겠다.

백엔드

taeminseo

워밍업 클럽 스터디 2기 [클린코드 & 테스트코드] - 3주차 회고

3주차 회고1. 단위 테스트테스트하기 어려운 영역을 구분하고 분리하기외부로 분리할수록 테스트 가능한 코드는 많아진다.테스트가 어려운 부분을 분리해서 테스트 가능한 영역을 많이 만들자테스트하기 어려운 영역관측할 때마다 다른값에 의존하는 코드현재 날짜/시간 , 랜덤값 , 전역 변수 / 함수 , 사용자 입력외부세계에 영향을 주는 코드표준 출력 . 메세지 발송 , db에 기록하기 등순수함수 (테스트하기 쉬운 함수)같은 입력에는 항상 같은 결과외부 세상과 단절된 형태테스트하기 쉬운 코드 lombok@Data , @Setter , @AllArgsConstructor 사용 지양양방향 연관관계 시 @ToString 순환 참조 문제2. TDD프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 방법론테스트를 먼저 작성하고 기능 구현red - 실패하는 테스트 작성green - 테스트 통과 최소한의 코딩refactor(blue) - 구현 코드 개선 , 테스트 통과 유지선 기능 구현 , 후 테스트 작성테스트 자체의 누락 가능성특정 테스트 케이스(해피 케이스)만 검증할 가능성잘못된 구현을 다소 늦게 발견할 가능성선 테스트 작성 , 후 기능 구현복잡도가 낮은 (유연하고 유지보수가 쉬운) 테스트 가능한 코드를 구현할 수 있다쉽게 발견하기 어려운 엣지 케이스를 놓치지 않게 해준다구현에 대한 빠른 피드백을 받을 수 있다과감한 리팩토링이 가능해진다 회고시험이 있어서 진도를 많이 따라가지 못해서 돌아오는 주에는 진도를 열심히 따라가야겠다.단위 테스트 작성 과제를 제출하지 못했지만 추후에 강의를 다듣고 한번 진행해보는게 좋을거 같다. 

채널톡 아이콘