묻고 답해요
158만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결Spring Boot TDD - 입문부터 실전까지 정확하게
질문드립니다.
안녕하세요! 강의 절반을 본 수강생입니다.뒷내용에 나올법한데 질문드려봅니다. 강의에서 거짓음성에 대한 경험때문에 테스트 코드에 @Transactional 를 안붙이신다고 하셨습니다. 그럼 예를 들어서 개발 디비기준으로 CRUD 테스트를 작성하시는건가요? 또 다른 질문으론TDD 기법이 아닌 , 실무 회사 코드에서 기존 코드에 인터페이스 테스트를 작성하려고 합니다 TDD가 아닌 유닛테스트로 이해가 되는데, 이런 방법또한 상관 없을까요?
-
미해결Spring Boot TDD - 입문부터 실전까지 정확하게
거짓 양성, 거짓 음성 질문
안녕하세요 규원님. 강의 잘 보고 있습니다.거짓 양성과 거짓 음성이 의미하는 바가 잘 이해가 되지 않아서 질문드립니다.수업 중 제공해주신 표에서는:거짓 양성: 테스트가 실패하지만 운영 코드가 테스트 시나리오를 만족하는 경우거짓 음성: 테스트가 성공하지만 운영 코드가 테스트 시나리오를 만족하지 않는 경우로 나와 있습니다.하지만 제가 이해하기로는 정의가 반대인 것 같습니다:거짓 양성: 테스트는 통과하지만 실제로는 코드에 문제가 있는 상황거짓 음성: 테스트는 실패하지만 코드가 올바르게 동작하는 상황예시로 제시해주신 코로나 검사 사례를 TDD에 적용해서 생각해보면, "코로나에 감염되었는가?"라는 테스트 시나리오가 있을 때:거짓 양성: 테스트는 통과했지만(양성 결과) 실제로는 감염되지 않은 경우이를 TDD로 연결하면: 테스트는 통과했지만 실제 코드는 요구사항을 만족하지 않는 경우TDD에서 이 용어들의 정의가 명확히 정해져 있는 건지, 그리고 어느 쪽이 올바른 정의인지 궁금합니다.감사합니다.
-
미해결Spring Boot TDD - 입문부터 실전까지 정확하게
cqrs 명령 아키텍처 개선 질문
안녕하세요 선생님 제가 이해가 잘 안된걸 수 있어서 확인차 질문 드립니다!var executor = new RegisterProductCommandExecutor(productRepository::save); executor.execute(command, uuid, sellerId);명령 조회를 개선하면 RegisterProductCommandExecutor 라는 실행기를 만들어서 execute 로 실행하는 동작으로 이 구조를 pdf 자료에서 확인하면궁금한점이명령모델이 new RegisterProductCommandExecutor(productRepository::save); 로 실행기를 만들면 명령모델이 실행기를 만들면서 productRepository 라는 기반구조에 의존하고 있는거 아닌가요? 구조자체에서 보면 명령실행기는 execute 를 통해서 실행기가 참조하는 기반구조 기능을 실행하는 건 직접 의존하지 않아서 자유도가 높아보인다? 라는 생각이드는데요, 실행기를 만들때 명령모델은ProductRepository 를 알아야할 것같아서 의미가 없어보입니다.실행기를 만들때 실행기 자체가 bean으로 등록이 되고 실행기 bean 안에서 ProductRepository 를 스프링에서 주입받고, 실행기를 만드는 명령 모델은 실행기만 주입받고 파라미터로 repository 를 넘겨도 되지 않으니 이게 정말 의존하지않는 구조같은데요.(제가 말한 (2) 방식이라면) 이이렇게 되면 그냥 돌고돌아서 결국엔 처음 방식인 ProductRepository 인터페이스를 (추상화 되어있으니) 그냥 바로 빈을 주입받는 것이랑 별차이 없다고 생각합니다조회도 역시 이런생각이 들었습니다. 명령과 조회를 분리한다는 개념은 이해가 갈거같은데 각 명령과 조회 모델이 기반구조에 의존? 한다는 개념을 잘 이해가 가지 않습니다!
-
미해결따라하며 배우는 리액트 A-Z[19버전 반영]
런타임 에러 ㅠㅠ
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요.import axios from "../api/axios"; import React, { useEffect, useState } from 'react' import requests from '../api/requests'; import "./Banner.css" import styled from "styled-components"; export default function Banner() { const [movie, setMovie] = useState([]); const [isClicked, setIsClicked] = useState(false); useEffect(() => { fetchData(); }, []); const fetchData = async() => { //현재 상영중인 영화 정보를 가져오기 (여러 영화) const request = await axios.get(requests.fetchNowPlaying); //여러 영화 중 영화 하나의 ID를 가져오기 const movieId = request.data.results[ Math.floor(Math.random() * request.data.results.length) ].id; //특정 영화의 더 상세한 정보를 가져오기 (비디오 정보도 포함) const {data: movieDetail} = await axios.get(`movie/${movieId}`, { params: {append_to_response: "videos"}, }); setMovie(movieDetail); } const truncate = (str, n) => { return str?.length > n ? str.substr(0, n - 1) + "..." : str; } console.log('movie', movie); if(!isClicked) { return ( <header className="banner" style={{ backgroundImage: `url("https://image.tmdb.org/t/p/original/${movie.backdrop_path}")`, backgroundPosition: "top center", backgroundSize: "cover", }}> <div className="banner__contents"> <h1 className="banner__title"> {movie.title || movie.name || movie.original_name} </h1> <div className="banner__buttons"> <button className="banner__button play" onClick={() => setIsClicked(true)} > Play</button> <button className="banner__button info">More Information</button> </div> <h1 className="banner_description"> {truncate(movie.overview, 100)} </h1> </div> <div className="banner--fadeBottom"></div> </header> ); } else{ return ( <Container> <HomeContainer> <Iframe width="640" height="360" src={`https://www.youtube.com/embed/${movie.videos.results[0].key}?controls=0&autoplay=1&loop=1&mute=1&playlist=${movie.videos.results[0].key}`} title="YouTube video player" allow="autoplay; fullscreen" ></Iframe> </HomeContainer> </Container> ); } } const Iframe = styled.iframe` width: 100%; height: 100%; opacity: 0.65; border: none; &::after { content:""; position: absolute; top: 0; left: 0; width: 100%; height: 100%; }` const Container = styled.div` display: flex; justify-content: center; align-items: center; flex-direction: column; width: 100%; height: 100vh; ` const HomeContainer = styled.div` width: 100vw; height: 100vh;`
-
미해결따라하며 배우는 리액트 A-Z[19버전 반영]
강의대로 따라갔는데 에러보다 api키로 들어간 넷플릭스? 그런게 렌더링 되지 않습니다 ㅠ
강의대로 따라갔는데 강의처럼 렌더링이 되고 있지 않습니다 ㅠㅠ 어디가 문제일까요chatgpt는 서버가 없다 뭐 이렇다는데 ㅠㅠ 출력 화면 Nav.jsimport React, { useEffect, useState } from 'react' import"./Nav.css" export default function Nav() { const [show, setShow] = useState(false); useEffect(() => { window.addEventListener("scroll", ()=> { console.log('window.scrollY', window.scrollY); if(window.scrollY > 50) { setShow(true); } else { setShow(false); } }) return () => { window.removeEventListener("scroll", ()=> {}); }; }, []); return ( <nav className={`nav ${show && "nav__black"}`}> <img alt = 'Netflix logo' src = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Netflix_2015_logo.svg/960px-Netflix_2015_logo.svg.png" decoding="async" width="799" height="216" srcset="//upload.wikimedia.org/wikipedia/commons/thumb/0/08/Netflix_2015_logo.svg/1198px-Netflix_2015_logo.svg.png 1.5x, //upload.wikimedia.org/wikipedia/commons/thumb/0/08/Netflix_2015_logo.svg/1597px-Netflix_2015_logo.svg.png" className='nav__logo' onClick={() => window.location.reload()} /> <img alt = "User logged" src = "https://occ-0-3077-988.1.nflxso.net/dnm/api/v6/vN7bi_My87NPKvsBoib006Llxzg/AAAABTZ2zlLdBVC05fsd2YQAR43J6vB1NAUBOOrxt7oaFATxMhtdzlNZ846H3D8TZzooe2-FT853YVYs8p001KVFYopWi4D4NXM.png?r=229" className="nav__avatar" /> </nav> ); } Nav.css.nav{ position: fixed; top: 0; width: 100%; height: 30px; z-index: 1; padding: 20px; display: flex; justify-content: space-between; align-items: center; transition-timing-function: ease-in; transition: all 0.5s; } .nav__black { background-color: #111; } .nav__logo{ position: fixed; left: 40px; width: 80px; object-fit: contain; } .nav__avatar { position: fixed; right: 40px; width: 30px; object-fit: contain; }Banner.jsimport axios from "../api/axios"; import React, { useEffect, useState } from 'react' import requests from '../api/requests'; import "./Banner.css" export default function Banner() { const [movie, setMovie] = useState([]); useEffect(() => { fetchData(); }, []); const fetchData = async() => { //현재 상영중인 영화 정보를 가져오기 (여러 영화) const request = await axios.get(requests.fetchNowPlaying); //여러 영화 중 영화 하나의 ID를 가져오기 const movieId = request.data.results[ Math.floor(Math.random() * request.data.results.length) ].id; //특정 영화의 더 상세한 정보를 가져오기 (비디오 정보도 포함) const {data: movieDetail} = await axios.get(`movie/${movieId}`, { params: {append_to_reponse: "videos"} }); setMovie(movieDetail); } const truncate = (str, n) => { return str?. length > n ? str.substr(0, n - 1) + "..." : str; } return ( <header className="banner" style={{ backgroundImage: `url("https://image.tmdb.org/t/p/original/${movie.backdrop_path}")`, backgroundPosition: "top center", backgroundSize: "cover" }}> <div className="banner__contents"> <h1 className="banner__title"> {movie.title || movie.name || movie.original_name} </h1> <div className="banner__buttons"> <button className="banner__button play">Play</button> <button className="banner__button info">More Information</button> </div> <h1 className="banner_description"> {truncate(movie.overview, 100)} </h1> </div> <div className="banner--fadeBottom"></div> </header> ) } Banner.css.banner{ color: white; object-fit: contain; height: 448px; } @media (min-width : 1500px) { .banner{ position: relative; height: 600px; } .banner--fadeBottom{ position: absolute; bottom: 0; width: 100%; height: 40rem; } } @media (max-width : 768px) { .banner__contents{ width: min-content !important; padding-left: 2.3rem; margin-left: 0px !important; } .banner--description{ font-size: 0.8rem !important; width: auto !important; } .info{ text-align: start; padding-right: 1.2rem; } .space{ margin-left: 6px; } }axios.jsimport axios from "axios"; const instance = axios.create({ baseURL: "https://api.themoviedb.org", params: { api_key: "eea00451962aefe6185011d467944242", language: "ko-KR", }, }); export default instance;requests.jsconst requests = { fetchNowPlaying: "movie/now_playing", fetchNetFlixOriginals: "/discover/tv?with_networks=213", fetchTrending: "/trending/all/week", fetchTopRated: "/movie/top_rated", fetchActionMovies: "/discover/movie?with_genres=28", fetchComedyMovies: "/discover/movie?with_genres=35", fetchHorrorMovies: "/discover/movie?with_genres=27", fetchRomanceMovies: "/discover/movie?with_genres=10749", fetchDocumentaries: "/discover/movie?with_genres=99", } export default requests;
-
미해결Spring Boot TDD - 입문부터 실전까지 정확하게
거짓 양성 감지 노하우 질문입니다
안녕하세요 규원님. 강의 잘 듣고 있습니다. 제가 실습 코드를 따라하다가 오탈자가 생겨 이를 해결하는 과정에서 의문점이 생겨 질문 남깁니다. shopper 토큰 발급 엔드포인트 구현 과정에서 @RequestBody 어노테이션을 누락했습니다@PostMapping("/shopper/issueToken") ResponseEntity<?> issueToken(IssueShopperToken query) { return repository.findByEmail(query.email()) .map(shopper -> composeToken()) .map(AccessTokenCarrier::new) .map(ResponseEntity::ok) .orElseGet(() -> ResponseEntity.badRequest().build()); }문제는 이런 상황에서 400이 발생하며 실패해야할 테스트 코드가 통과하게 됩니다.@Test void 잘못된_비밀번호가_사용되면_400_Bad_Request_상태코드를_반환한다( @Autowired TestRestTemplate client ){ // Arrange var email = generateEmail(); var wrongPassword = generatePassword(); var password = generatePassword(); client.postForEntity( "/shopper/signUp", new CreateShopperCommand(email, generateUsername(), password), Void.class ); // Act ResponseEntity<AccessTokenCarrier> response = client.postForEntity( "/shopper/issueToken", new IssueShopperToken(email, wrongPassword), AccessTokenCarrier.class ); // Assert assertThat(response.getStatusCode().value()).isEqualTo(400); }이유를 고민해보고 다음과같은 결론을 짓게 되었습니다.@RequestBody가 없는 경우 컨트롤러 메서드의 매개변수는 기본적으로 form data로 인식하게 된다.따라서 IssueShopperToken이 form data로 인식되게 된다.현재 테스트에서는 상태코드가 400인지만을 확인한다.하지만 form data가 없는 경우에도 400이 발생한다.테스트가 통과한다. 거짓 양성의 사례라고 보여집니다. 이경우에는 Assert절을 강화해 상태코드 말고도 검증되어야할 항목들을 추가해 거짓 양성을 방지할 수 있을거라고 생각됩니다. 이렇듯 테스트코드도 사람이 작성하다 보니 Assert절을 어느정도 수준까지 구체적으로 작성해야 할지를 TDD가 익숙하지 않다면 빠르게 식별하기 어렵다고 생각합니다.실무에서는 이와같은 상황은 매우 치명적일 수 있을거 같고요.그래서 실무에서 필요한 Assert절을 구체화하는 명확한 기준이나 노하우가 있으신지가 궁금합니다!
-
해결됨클린 코더스: 실전 객체 지향 프로그래밍과 TDD 마스터 클래스
org.fitness 라이브러리를 어떻게 받을 수 있나요
안녕하세요클린 코더스 강의보면서 예제를 따라해보고 있는데 초반 function 강의 예제에 필요한 org.fitness 라이브러리가 받아지지 않는거 같습니다. 클래스 파일이 없으니 컴파일 에러가 터지는데, 라이브러리 파일 구할 수 있는 방법 없을까요?maven repository:https://mvnrepository.com/artifact/org.fitness/fitness저장소:https://github.com/msbaek/fitness-example/tree/master
-
미해결클린 코더스: 실전 객체 지향 프로그래밍과 TDD 마스터 클래스
11. Null is not an error의 예제인 top 함수는 커맨드인가요 쿼리인가요??
안녕하세요. 좋은 강의 감사합니다. CQS에서 커맨드는 내부 구조를 변경하는데 반환값이 없고, 쿼리가 내부 구조를 변경하지 않고 반환값이 있는 것이라고 설명을 들었습니다. 그런데, 11. Null is not an error에서 예제를 든 스택의 top 메소드는 내부 구조를 변경함에는 커맨드 처럼 보입니다만, 값을 반환하므로 쿼리 처럼 행동합니다. 예제라서 그런것인지 아니면 쿼리나 커맨드 둘 중에 하나인지 궁금합니다.
-
미해결Spring Boot TDD - 입문부터 실전까지 정확하게
질문드립니다.
안녕하세요 TDD에 관심 있는 개발자입니다.좋은 강의 만들어주셔서 감사합니다. 강의에 대한 질문이 아닌 실무적인 질문드립니다.뒷 내용을 못봐서,, 지금까지 보면서 궁금했던 내용 질문드립니다. 상황마다 다르겠지만 형상관리하는 시점이 언제정도 될까요? 제 생각엔 Green 상태에 커밋을 할텐데 Green 상태는 단순히 api 상태값만 통과하는 기준일까요..?디비 먼저 설계가 아닌 인터페이스설계 테스트를 하고 필요시 디비 스키마를 작성한다라고 이해 하였습니다만 결국 해당 테스트 시나리오를 전부 만족 한다면, API 개발이 끝난 상태라고 할수 있는 건가요?리팩토링은 선택이라고 강의에서 이야기 해주셨는데 예를 들어서 @Valid 라는 애노테이션으로 외부 인터페이스의 값을 필터링 해서 상태값을 반환해주겠다라는 내용일까요?실무적으로 테스트 시나리오는 노션에 작성하신다고 하셨는데 이슈 트래킹 ( 지라 등등) 쪽을 작성하시는게 더 좋을까요?
-
해결됨Spring Boot TDD - 입문부터 실전까지 정확하게
프로젝트 규모가 큰 경우 @SpringBootTest 실행 속도 문제
안녕하세요, 강사님.저는 백엔드 1년 차 주니어 개발자입니다. 강사님 덕분에 TDD 학습을 차근차근 진행하고 있는데, 한 가지 궁금증이 생겨 질문드립니다. 현재 강의에서는 매번 API 기능을 구현할 때마다 @SpringBootTest 를 사용하여 테스트하고 계신데요. 만약 프로젝트 규모가 엄청 커져서 컨텍스트 로딩 시간이 5초 이상이 소요되는 경우,테스트 결과를 기다리느라 “개발 흐름이 끊기는” 문제가 발생할 것 같은데요. 현업에서는 이 부분을 어떻게 관리하시는지 궁금합니다. 매번 @SpringBootTest를 그대로 사용하며 시간을 감수하시는지특정 테스트만 빠르게 실행하는 슬라이스 테스트(@WebMvcTest 등)를 병행하시는지그 외 다른 최적화 기법이 있는지 조언 부탁드립니다. 😊
-
미해결Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트
update에서 Repository.save
dbEntity와 도메인으로 따로 분리하면 Jpa의 기능을 쓰지 못하기에 update부분에서 더티체킹을 사용하지 않고 Repository.save를 이용해 merge로 update한다는건데 이렇게 했을 때 merge는 하나하나 다른 부분을 수정하는게 아니고 전부 덮어 쓰는거라 이로 인해 발생할 수 있는 문제에 대해 신경 쓰지 않아도 되나요? 아니면 따로 처리를 해야하나요?
-
미해결Practical Testing: 실용적인 테스트 가이드
계층 관련 질문이 있습니다.
위 강의에서는 Response 부분을 service 레이어에 response 패키지 안에 관리를 하고 있습니다.개인적인 생각으로는 요청을 받는 부분은 최상단 Presentation Layer 이고 궁극적으로 어떠한 결과를 return 해주는 것 또한 Presentation Layer 이라고 생각합니다. 그렇기에 requestDto 는 controller 쪽에 있는게 맞고 responseDto 또한 controller 쪽에 있어야 하는게 맞지 않을까 하는 생각이 듭니다!혹시 위 부분 어떻게 생각하시는지 궁금합니다.
-
해결됨Practical Testing: 실용적인 테스트 가이드
'코틀린'에서는 빌더를 따로 쓰지 않는데, 이 때는 어떻게 test fixture를 만드시는지 궁금합니다
우빈님 안녕하세요! '코틀린'에서는 빌더를 따로 쓰지 않는데, 이 때는 어떻게 test fixture를 만드시는지 궁금합니다 ㅎㅎ저도 그동안 실무에서 자바를 쓰면서 테스트에서 given 절의 test fixture는 빌더로 만들어서 사용하고, 프로덕션에서는 팩토리 메서드로 객체를 생성해왔습니다. (물론 이 강의를 보고나서부터입니다 ㅎㅎ) 그런데 코틀린을 쓰다보니까 롬복을 거의 쓸 일이 없는 것 같더라고요.private constructor & 팩토리 메서드를 코틀린에서 쓰다 보니까 팩토리 메서드로만 객체를 생성해야해서 테스트에서 '맥락을 이해하는데 허들이 하나 더' 생기는 꼴이 되고 있어 조금 고민입니다.. 그렇다고 테스트 전용 팩토리 메서드 같은 것을 만들자니 프로덕션에 테스트 관련된 소스가 바로 생기는 것은 영 석연치 않더라고요.그래서 지금은 일단 생성자를 public으로 열어두고, 팩토리 메서드가 있는 도메인 객체를 만들 때는 무조건 팩토리 메서드만 쓴다. 그리고 테스트에서는 테스트 용이성을 위해 생성자로 객체를 생성하는 것을 허용하는 것으로 해보려고 합니다. 혹시 위 같은 상황에서 우빈님은 어떤 기준으로 쓰고 계신지 궁금합니다..!바쁘실텐데도 시간 내어 확인해주셔서 감사합니다 :)
-
미해결Practical Testing: 실용적인 테스트 가이드
혹시 update 로직은 어떻게 테스트하나요? (@Setter?)
강의에선 update 로직이 없어서 테스트할 필요는 없는 것 같은데, 필요한 경우에 엔티티에 @Setter 추가해놓고 테스트 하면 될까요?setter 사용을 지양하라는 글이 많은데, 현업에서는 어떤식으로 테스트하나요?
-
미해결Practical Testing: 실용적인 테스트 가이드
단위테스트와 통합테스트의 경계가 궁금합니다.
안녕하세요 우빈님 강의 너무나 잘 듣고 있습니다 🙂주니어에게 큰 힘이 되고 있습니다 감사합니다 공부를 하다보니 단위테스트와 통합테스트의 경계가 궁금하게 되었습니다.우빈님은 컨트롤러 / 서비스 / 리포지토리 각 계층에 대해 단위 / 통합 / 단위 테스트라고 구분하셨는데요 어떤 분은 컨트롤러 계층에서 작성한 테스트에 대해 통합테스트라고 주장하셨습니다@WebMvcTest 또는 @SpringBootTest를 사용해 스프링 컨텍스트의 일부(웹 계층) 또는 전체를 로드해야 한다.진정한 단위 테스트"는 스프링이나 다른 컨테이너 없이 new 연산자를 사용하여 객체를 인스턴스화하고 테스트하는 것을 의미한다고 하셨습니다. 테스트 대상 범위: 또한 테스트는 특정 컨트롤러 메서드의 로직만을 격리하여 테스트하는 것이 아니라, 특정 URL에 대한 HTTP 요청이 스프링 웹 계층을 통과하여 컨트롤러에 도달하고, 컨트롤러의 응답이 HTTP 응답으로 변환되어 반환되는 전체 과정 중 일부를 시뮬레이션하고 검증하는 관점에서 통합테스트다라고 주장하셨습니다. 정확한 구분은 어떻게 해야할까요? 사실 이러한 구분이 중요한가 고민도 됩니다. 어차피 테스트에 대한 목적과 경계 설정을 구분하는 것이 중요하지 않은가 싶긴 한데 우빈님 의견이 궁금합니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
Service+Repository 통합테스트 관련 질문입니다.
요즘 서비스 계층 단위테스트를 위해 모킹과 fake 객체 구현을 공부하고 있습니다. 하지만 레포지토리를 일일이 모킹하는 코드를 작성하는 것이 빠른 피드백이 장점인 단위테스트로 의미가 있는지 의문이 들 정도로 시간이 많이 들더라고요. 그래서, 좀 더 효율적인 강사님의 방식을 따라가고 싶어 강의를 듣던 중 의문이 생겨서 질문드립니다.1. 계층별 테스트 분리 기준에 대한 질문입니다.컨트롤러, 레포지토리는 단위테스트, 서비스 계층은 레포지토리 부분과 통합테스트 이렇게 분리해서 진행하셨던 이유를 여쭤봐도 될까요? 서비스, 컨트롤러, 레포지토리 계층 각각 단위테스트를 작성하고 컨트롤러에서 레포지토리까지 한번 통합테스트를 작성하는 방법도 있을 것 같고, 묶어볼 방법은 몇 가지 더 있는 것 같습니다.그런데 강의에서처럼 분리했던 게 가장 효율적이라고 생각하는 기준과 이유…. 이 분리 방식의 발견 과정이 너무 궁금하네요2. 통합테스트 DB 관련 질문입니다.서비스계층과 레포지토리 계층을 묶은 상태로 H2 같은 임베디드 데이터베이스를 사용하면 테스트 속도가 상당히 느리게 나오긴 합니다. 이런 부분은 어쩔 수 없이 안고 가는 것인가요? 그리고 운영이나 개발 DB를 postgress같이 H2말고 다른 걸 사용한다고 해도, H2로 통합테스트 테스트하는게 이점이 있을까요?3. 서비스 계층 단위테스트 관련 질문입니다.혹시, 부담이 안 된다면, 서비스 계층의 단위테스트가 중요도가 많이 떨어진다고 생각하시는 이유가 Fake든 Mockito를 사용한 Stub이든 데이터베이스를 흉내만 내는 테스트가 의미가 없다고 여기셔서 그런 것일까요?부족한 질문 읽어주셔서 감사합니다!
-
미해결따라하며 배우는 리액트 A-Z[19버전 반영]
안녕하세요 개발과 상관없는 질문입니다만
안녕하세요 강사님 좋은 강의 감사드립니다vscode 테마 정보좀 알수있을까요?
-
미해결Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트
최종 완성된 코드를 받아 볼 수 있을까요?
안녕하세요. 강의 잘 듣고 있습니다^^최종완성된 코드를 받아서 확인해보고 싶은게 있는데 공유 가능할까요?
-
미해결Practical Testing: 실용적인 테스트 가이드
OrderControllerDocsTest 작성 해봤는데요. 날짜 형식이 이상하게 나와요
OrderControllerDocsTest.java@DisplayName("주문 생성 API") @Test void createOrder() throws Exception { OrderCreateRequest request = OrderCreateRequest.builder() .productNumbers(List.of("001")) .build(); LocalDateTime now = LocalDateTime.now(); given(orderService.createOrder(any(OrderCreateServiceRequest.class), any(LocalDateTime.class))) .willReturn(OrderResponse.builder() .id(1L) .totalPrice(4000) .registeredDateTime(now) .products(List.of(ProductResponse.builder() .id(1L) .productNumber("001") .type(ProductType.HANDMADE) .sellingStatus(ProductSellingStatus.SELLING) .name("아메리카노") .price(4000) .build())) .build()); mockMvc.perform(post("/api/v1/orders/new") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("200")) .andExpect(jsonPath("$.message").value("OK")) .andExpect(jsonPath("$.status").value("OK")) .andDo(document("order-create", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestFields( fieldWithPath("productNumbers").type(JsonFieldType.ARRAY) .description("상품 번호") ), responseFields( fieldWithPath("code").type(JsonFieldType.NUMBER) .description("코드"), fieldWithPath("status").type(JsonFieldType.STRING) .description("상태"), fieldWithPath("message").type(JsonFieldType.STRING) .description("메시지"), fieldWithPath("data").type(JsonFieldType.OBJECT) .description("응답 데이터"), fieldWithPath("data.id").type(JsonFieldType.NUMBER) .description("주문 ID"), fieldWithPath("data.totalPrice").type(JsonFieldType.NUMBER) .description("주문 총 금액"), fieldWithPath("data.registeredDateTime").type(JsonFieldType.ARRAY) .description("주문 시각"), fieldWithPath("data.products").type(JsonFieldType.ARRAY) .description("주문 상품"), fieldWithPath("data.products[].id").type(JsonFieldType.NUMBER) .description("상품 ID"), fieldWithPath("data.products[].productNumber").type(JsonFieldType.STRING) .description("상품 번호"), fieldWithPath("data.products[].type").type(JsonFieldType.STRING) .description("상품 타입"), fieldWithPath("data.products[].sellingStatus").type(JsonFieldType.STRING) .description("상품 상태"), fieldWithPath("data.products[].name").type(JsonFieldType.STRING) .description("상품 이름"), fieldWithPath("data.products[].price").type(JsonFieldType.NUMBER) .description("상품 가격") ))); } docs/index.html 에서 확인한 registeredDateTime처음에 테스트 코드 작성시에 ieldWithPath("data.registeredDateTime").type(JsonFieldType.ARRAY) .description("주문 시각"),이 부분을 JsontFieldType.STRING 으로 했더니 테스트 실패 메시지에 해당 타입이 Array 라고 해서 바꿨는데... 문서에 저렇게 나옵니다. 이게 맞는건지 궁금합니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
test 용 .yml
안녕하세요 좋은 강의 감사합니다. 강의에서는 테스트에 사용되는 설정 파일을 main/resources/~.yml 파일을 사용하셨는데요.혹시 test 패키지에 별도의 .yml 파일을 둬서 사용하는 것은 어떻게 생각하시나요?보통 어떻게 하는 것이 올바르고? 장단이 있을지 궁금합니다. 강사님 의견도 궁금하구요! 감사합니다.