묻고 답해요
169만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결Do It! 장고+부트스트랩: 파이썬 웹개발의 정석
docker-compose down 안되는 현상
안녕하세요.docker-compose down시 계속 permission denined가 뜹니다.우선 저는 가상머신 os:ubuntu에서 프로젝트를 진행중이며, sudo 붙여서 종료 시도, sudo docker kill 로도 강제 종료를 시도했지만 permission denined이라 다 실패하였습니다.최후의 수단으로 docker설치 삭제 후, 재설치도 해보았지만 안타깝게도 실패하였습니다.하여 container를 종료하려고 할 때마다 가상머신 종료하고 다시 들어가야하는 매우 불편하고도 불운한 상황에 처해있습니다. groups를 통해 docker가 있는 것도 확인하였습니다. 도저히 왜 안되는지 모르겠습니다.도움이 절실합니다... 감사합니다.ㅠ
-
해결됨Spring Boot TDD - 입문부터 실전까지 정확하게
테스트 격리에서 테스트 랜덤 실패 이유
69강 테스트 격리에서 테스트가 랜덤으로 실패하는 이유가 뭔가요?싱글톤때문에 발생하는 문제인건 알겠는데 조회할 때 Exception이 생기는 이유를 모르겠습니다. ㅠㅠ
-
미해결Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트
빌드 안 되시는 분들 참고
Maven resources compiler 어쩌구자바 버전 문제제 기준 pom.xml에서 java.version 21로 올리고 프로젝트 설정에서 SDK/모듈도 동일하게 설정했더니 해소 됐습니다NoSuchFieldError 어쩌구lombok 버전이 자바 버전과 안 맞음.pom.xml lombok dependency의 버전을 자바랑 맞추기 https://jinseobbae.github.io/java/2023/02/27/lombok-version-compatibility.htmlUnsupported class file major version 65스프링부트 버전 문제3.3.0으로 올리니까 해소됐습니다
-
미해결클린 코더스: 실전 객체 지향 프로그래밍과 TDD 마스터 클래스
13강, 책임을 나누는 기준
3분 43초경, 책임을 나누는 부류에서"누가 해당 메서드의 변경을 유발하는 사용자인가"가 기준이 된다고 하셨는데,메서드의 변경을 유발한다는 게해당 메서드를 누가 호출하느냐? 어떤 사용자가 이 메서드를 사용하냐? 이렇게 이해하면 되나요?메서의 변경을 유발한다는 의미가 해당 메서드를 이용한다는 의미인지? 정확히 메서드의 변경을 유발한다는 점이 무슨말인지 모르겠습니다.
-
미해결Spring Boot TDD - 입문부터 실전까지 정확하게
테스트 코드 작성 범위 고민
안녕하세요! TDD 평소에 관심이 많았지만 실무에서 막상 적용하려니 고민이 많았는데 좋은 강의 만들어 주셔서 감사합니다. 섹션5까지 수강했는데 벌써 만족스럽네요 수강평 5점 먼저 드렸고 평소에 실무에서 항상 고민이 되던 부분이 있어서 질문드려요! 테스트코드가 너무 많아지는 것이 부담스러워서 하는 고민인데요. 특정 Service 메서드의 테스트코드를 작성 할 때 해당 메서드가 사용하고 있는 Repository 메서드 에서 테스트한 내용을 Service 에서 중복적으로 테스트해야하는 가에 대한 고민입니다. 참고로 Service, Repository 메서드 둘다 응답에 영향을 미치는 동일한 파라미터를 인풋으로 받고 있다는 전제입니다. GPT 에게 물어보면 아래와 같이 대답을 하는데요. 서비스 메서드의 인풋값이 변경되면 응답에 영향을 미치는데 Repository에서 테스트했다고 안하는 것이 맞는가? 라는 고민이 계속 되네요. 고견 주시면 감사하겠습니다![GPT 답변]Service 테스트는 Repository 동작이 "올바르다고 믿는다는 전제 하에" 작성하는 것이 일반적인 전략입니다.즉, Repository의 구현 자체를 Service 테스트에서 다시 검증할 필요는 없습니다. 질문 카테고리가 "수업질문"인데 전체질문으로 변경하는 버튼이 없어서 일단 그대로 둡니다 ㅜㅜ
-
해결됨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 응답으로 변환되어 반환되는 전체 과정 중 일부를 시뮬레이션하고 검증하는 관점에서 통합테스트다라고 주장하셨습니다. 정확한 구분은 어떻게 해야할까요? 사실 이러한 구분이 중요한가 고민도 됩니다. 어차피 테스트에 대한 목적과 경계 설정을 구분하는 것이 중요하지 않은가 싶긴 한데 우빈님 의견이 궁금합니다.