묻고 답해요
141만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결Practical Testing: 실용적인 테스트 가이드
@RestControllerAdvice 와 @Validation
안녕하세요 선생님 좋은 강의 잘 듣고 있습니다!다름이 아니라 Validation과 ControllerAdvice에대해 질문이 있습니다. 컨트롤러 테스트 할 때 빈 validation이 적용 된 모든 필드를 하나씩 모두 검증 하나요?컨트롤러에서 request 필드가 많아서 하나씩 검증 하기엔 너무 테스트가 길지 않을까 고민 됩니다.실무에서도 컨트롤러의 모든 필드를 검증 하나요? 공통 예외 처리는 어떻게 하는게 좋을까요? @RestControllerAdvice를 basePackages 설정 하여 처리 하고 있습니다. 예상치 못한 예외의 경우가 생길것을 대비 하여 basePackages가 없는 advice를 두었습니다. 이렇게 하니 우선 순위가 밀려서 GeneralAdvice가 먼저 선택되어 Order로 우선순위 설정 해두었습니다. 처리 못한 예외를 각 advice에 넣자니 다른 advice도 코드가 중복 될것 같아 이렇게 처리 했는데 실무에서는 어떻게 처리 하시는지 궁금 하여 질문 남깁니다.!
-
미해결Practical Testing: 실용적인 테스트 가이드
when then이 깔끔하게 나눠지지 않으면 주석을 어떻게 다는 편인가요?
강의 BDD 스타일로 작성하기 까지 보고 질문드립니다.정말 단순한 질문이긴 하지만 when에 해당하는 코드와 then에 해당하는 코드가 나눠지지 않으면 주석을 어떻게 다는 게 좋을지 궁금해졌습니다. 강의의 영업 시간 이전에 주문하면 예외를 던지는지 테스트하는 코드를 보면 when절과 then절이 하나로 뭉쳐 있다고 생각되는데, 아래처럼 그냥 한 줄에 when then을 모두 적는 게 좋을까요? @Test void createOrderBeforeOpenTime() { // given CafeKiosk cafeKiosk = new CafeKiosk(); Americano americano = new Americano(); cafeKiosk.add(americano); // when then assertThatThrownBy(() -> cafeKiosk.createOrder(LocalDateTime.of(2024, 11, 19, 9, 59))) .isInstanceOf(IllegalArgumentException.class) .hasMessage("주문 시간이 아닙니다. 관리자에게 문의하세요."); } 아니면 조금 이상하다고 생각되긴 하지만 아래처럼 나눠서 적는게 좋을까요? @Test void createOrderBeforeOpenTime() { // given CafeKiosk cafeKiosk = new CafeKiosk(); Americano americano = new Americano(); cafeKiosk.add(americano); // when assertThatThrownBy(() -> cafeKiosk.createOrder(LocalDateTime.of(2024, 11, 19, 9, 59))) // then .isInstanceOf(IllegalArgumentException.class) .hasMessage("주문 시간이 아닙니다. 관리자에게 문의하세요."); }
-
미해결Practical Testing: 실용적인 테스트 가이드
spring security @AuthenticationPrincipal rest docs 질문
안녕하세요 강사님강의를 굉장히 잘 들었던 수강생입니다. 다름이 아니라 최근 spring security 를 도입하고 난 뒤에 rest docs 에 대해 문제가 생겨 질문하게 되었습니다.'회원 정보 조회' 라는 API 가 있고, 이 API 는 로그인을 한 뒤에 securityContextholder 에 담겨져 있는 객체를 사용하고자 파라미터로 @AuthenticationPrincipal 이라는 어노테이션을 이용하여 받고 있습니다.그러나...restdocs 를 이용하여 해당 API 를 문서화 시키려고 할때 객체가 null 이 들어가서 테스트가 실패를 하게 되더라구요.docs 가 아닌 일반 controller test 에서는 @SpringBootTest 를 사용하여 @WithUserDetails 를 통해 테스트를 통과했습니다.그러나 @SpringBootTest 를 따로 사용하고 있지 않은 상황에서 어떻게 해결할 수 있을지 고민입니다 ㅠㅠ감사합니다.
-
미해결실무에 바로 적용하는 프런트엔드 테스트 - 2부. 테스트 심화: 시각적 회귀・E2E 테스트
브랜치 git clone 질문
질문 git clone https://github.com/practical-fe-testing/test-example-shopping-mall.git 를 했는데 폴더안에 gitignore 과 readme 파일만 있는데 해당 브랜치를 clone해서 사용하는게 아닌건가요? 참고사항 대체방안으로 zip파일로 다운받아서 사용하고 있긴합니다!
-
미해결테스트 with Jest: 제로초에게 제대로 배우기
비동기 함수 테스트 중 특정 케이스 에러
비동기 함수 테스트 강의를 따라했을 때 제 컴파일러에서는 오류가 발생합니다. 다른 테스트 케이스들 말고 아래 두 케이스에서만요.test('okPromise 테스트', () => { const okSpy = jest.fn(okPromise); return okSpy.then((result) => { expect(result).toBe('ok'); }) }) test('noPromise 테스트', () => { const noSpy = jest.fn(noPromise); return noSpy.catch((result) => { expect(result).toBe('ok'); }) })각 테스트 별 에러 메세지는 다음과 같습니다.'Mock<Promise<string>, [], any>' 형식에 'then' 속성이 없습니다.ts(2339)'Mock<Promise<never>, [], any>' 형식에 'catch' 속성이 없습니다.ts(2339)코드를 동일하게 작성했음에도 불구하고, 어떤 문제로 위 에러가 발생하는 걸까요?
-
미해결쉬운 모바일 테스트 자동화 시작하기 : Appium Studio
appium studio download 문의드립니다.
안녕하세요 강의에 appium studio download 방법이 자세히 나와있지 않아 문의드립니다.영상내에서 홈페이지에서 간단하게 신상정보 입력 후 이메일을 통해 다운로드 링크를 받으라고 안내해주셨는데 아래 링크가 맞는지 문의드립니다. https://digital.ai/products/continuous-testing/appium-studio/free-trial/ 위 링크에 정보 입력 후 등록한지 30분가량이 되었지만 메일로 링크는 못받고있는데 보통 얼마나 소요가 될까요?
-
미해결Practical Testing: 실용적인 테스트 가이드
정적 팩토리 메서드 사용과 toEntity 메서드에 대해 질문 있습니다.
우선 빌더를 활용해 내부에서 객체를 생성하고 외부에서 해당 객체를 생성하려면 정적 팩토리 메서드를 사용하거나 혹은 toEntity 같은 메서드를 만들어 사용하면 좋을거 같다 라는 생각이 들었습니다. 궁금한 점은 외부에서 객체를 생성할때 어떤 경우에는 정적 팩토리 메서드를 사용해서 생성하고 어떤 경우에는 toEntity 같은 메서드를 만들어서 사용하면 좋을지 궁금합니다.@Getter @NoArgsConstructor public class ProductCreateServiceRequest { private ProductType type; private ProductSellingStatus sellingStatus; private String name; private int price; @Builder private ProductCreateServiceRequest(ProductType type, ProductSellingStatus sellingStatus, String name, int price) { this.type = type; this.sellingStatus = sellingStatus; this.name = name; this.price = price; } public Product toEntity(String nextProductNumber) { return Product.builder() .productNumber(nextProductNumber) .type(type) .sellingStatus(sellingStatus) .name(name) .price(price) .build(); } }
-
미해결Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트
인프라계층 구현체와 테스트 구현체에 대한 질문
안녕하세요.UserRepository를 구현하는 구현체로는 UserJpaRepository를 사용하는 UserRepositoryImpl와 테스트용 구현체인 FakeUserRepository가 있는데요. TDD라는게 단순 객체지향적인 설계를 잘하는 방법론이라면 납득이 가는데, 실제 서비스 되어야 할 코드가 잘 동작하는지 검증하기 위함이라는 관점에서는 조금 궁금한점이 생겨 질문드립니다. FakeUserRepository는 UserRepository의 계약을 잘 이행하고 테스트도 성공하도록 잘 구현하였습니다.그런데 실제 서비스에 사용될 UserRepositoryImpl는 한번도 검증된 적 없는데 테스트를 통해 어떻게 소프트웨어가 변화함에 있어서 확신을 가질 수 있나요? UserRepositoryImpl 내부 구현이 변경되었을때 여전히 FakeUserRepository만 테스트를 실행할텐데 어떤식으로 개발 과정에서 확신을 얻을 수 있는지 궁금합니다. 추가적으로 TDD의 역할에는 어떤 구현 기술에 대해 잘 사용되었는지에 대한 검증은 포함되지 않나요? 예를 들어 스프링 데이터 JPA를 잘 사용하였는지..? 예를 들자면 저는 TDD를 해본적은 없지만 실제 실행되는 인프라 계층을 실행해볼 수 있는 샘플 테스트코드 개념으로 만들어서 사용하고 있는데요. 제가 개발하고 있는 기업용 솔루션이 버전이 높아짐에 따라 워낙 무거워져서 로컬 PC에서 WAS를 기동하는데에만 2분이 넘기 때문에 최소한의 인프라계층 혹은 인프라계층의 협력 클래스들정도는 테스트 코드로 직접 테스트해볼 수 있도록 테스트 코드를 작성해 두었습니다. 이렇게 해두면 어떤 이슈가 발생했을때도 곧바로 쉽게 테스트가 가능하니까요. 물론 DB 개발서버 장비에 연결하고 더미데이터를 가지고 실행하는 식으로요. 이번 강의에서 H2를 통한 인프라 테스트 강의까지가 딱 제가 하고 있는 테스트들이었는데요. 중간에 놓친것인지 테스트가 무거워진다는 설명만 하고 그뒤로는 리팩토링과 함께 인프라 레벨의 테스트는 언급을 안하셔서 제가 생각하는 구현 기술을 잘 사용하여 실제 서비스되는 인프라 모듈을 직접 테스트 하는 부분은 어떻게 해야할지 궁금합니다.
-
미해결테스트 with Jest: 제로초에게 제대로 배우기
TS로 변경 시 "type": "module" 제거 이유가 궁금합니다.
확장자를 TS로 변경했을 때 코드에는 ESM의 import, export를 사용했는데 왜 package.json파일의 ESM 관련 옵션인 "type": "module"을 제거하는지 궁금합니다. GPT의 답변에 의하면TS 파일은 컴파일 과정에서 CommonJS나 ESM이나 알맞게 변환을 하기 때문에 해당 옵션이 필요 없고, Jest와의 호환성을 위해 "type": "module"을 제거해주는 것이고,JS파일은 기본적으로 CommonJS모듈 시스템을 사용하기 때문에 import문을 사용했으면 ESM방식으로 해석하도록 명시하기 위해서 "type": "module"을 추가한다.라고 설명을 해주는데 그럼 JS 파일의 경우 Jest와의 호환성을 고려하지 않는건지.. 이해가 잘 되지 않습니다. 혹시 설명을 해주실 수 있을까요?
-
미해결테스트 with Jest: 제로초에게 제대로 배우기
jest.config.js 파일 내용 다른 분들 참고
환경 : mac os저의 경우에 npx ts-jest config:init 명령어를 통해 생성한 jest.config.js 파일의 내용이 다음과 같이 강의 영상과 달랐습니다./** @type {import('ts-jest').JestConfigWithTsJest} **/ module.exports = { testEnvironment: "node", transform: { "^.+.tsx?$": ["ts-jest",{}], }, };강의 영상 내용에 맞춰서 preset: 'ts-jest' 설정을 추가해주어 Jest가 TypeScript 파일을 컴파일하고 실행할 수 있도록 해주니 잘 동작합니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
Integration Test Truncate
안녕하세요! 좋은 강의 잘 들었습니다!!강의 듣고, 테스트 적용해보며 한가지 궁금한게 생겼는데요!테스트 데이터 삭제의 건 입니다!요구사항이 다음과 같다고 한다면, 어떤 방법을 내리실 지 궁금해서요! Test라는 별도의 환경 없음. 로컬 개발환경은 개발DB를 바라보는 상황JPA가 아닌 MyBatisUnit Test가 아닌 integration Test혹시 이런 상황이라면, 테스트를 진행하며 추가되거나 변경된 데이터들을 어떻게 삭제할 수 있을까요 ??더 나아가서는, 성능테스트를 진행한다면 별도의 Test 환경이 없을 때 성능테스트를 진행하며 생긴 대량의 데이터들을 어떻게 원래대로 원복 시킬 수 있을까요 ??감사합니다!
-
미해결Practical Testing: 실용적인 테스트 가이드
readOnly = true 시 jpa 동작관련
readOnly = true시에는 jpa 에서는 단순 cud 는 동작하지만,변경감지는 안된다는 말씀이신가요?
-
미해결Practical Testing: 실용적인 테스트 가이드
자바 익셉션 종류도 외우시나요??
안녕하세요.강의를 수강하다가 문득 의문이 들었는데, 테스트 코드를 작성하실때 어떤 exception을 던질지도 다 알고 계신것 같더라구요! 예를들어, 아메리카노를 0개 주문할때는 IllegalArgumentException을 던진다거나요.... 혹시 이런 익셉션의 종류들도 전부 시간을 들여 외워야 할까요??? 아니면 자연스레 체득되길 기다릴까요??
-
미해결테스트 with Jest: 제로초에게 제대로 배우기
강의 교안
안녕하세요 제로초님. 강의 교안이 어디있는지 모르겠습니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
private 메서드를 public으로 바꾸면 어떤가요?
안녕하세요, 우빈님 강의 정말 잘 듣고 있습니다! 한가지 의문점이 드는 것은 private 메서드는 테스트를 하지 않고 그냐 유지하되, 필요 시에 따로 클래스를 분리하여 객체를 만드신다고 했는데요, 그냥 간편히 private 메서드를 public 메서드로 변환해서 내부 호출하는 방법은 별로 고려되지 않은 것 같은데 따로 이유가 있을까요? 혹시 createNextProductNumber()가 ProductService 클래스의 성격과는 맞지 않아서 분리하는 경우인지, createNextProductNumber()가 결국 다른 곳에서 사용 될 수 있이므로 별도의 파일로 관리하는지 궁금합니다
-
미해결2시간으로 끝내는 프론트엔드 테스트 기본기
Cypress io가 유료인가요?
Cypress Colud Trial 13 days left라고 뜨는데 유료로 바뀌었나요?
-
미해결Practical Testing: 실용적인 테스트 가이드
서비스단 private 메서드의 책임을 새로운 객체로 분리 시, repository 의존성 이슈에 대해
학습 관련 질문을 남겨주세요. 어떤 부분이 고민인지, 무엇이 문제인지 상세히 작성하면 더 좋아요!먼저 유사한 질문이 있었는지 검색해 보세요.서로 예의를 지키며 존중하는 문화를 만들어가요. 안녕하세요 우빈님! 강의 너무 잘 듣고 있는 컴공과 대학생입니다!!이번 목차에서 말씀해주신 내용을 바탕으로 개발한 프로젝트에 테스트코드를 작성하기 위해 리펙토링을 하던 도중, 의문점이 생겨서 질문 드립니다.강의에서 말씀해 주신 것처럼, 서비스단의 private 메서드가 가지는 책임을 서비스단이 아닌 다른 객체로 책임을 위임하는 것이 더 객체지향적이라 생각해서 저도 똑같은 방식으로 리펙토링을 진행중입니다.그런데, Layered architecture 에서 서비스단이 레포지토리단에 의존하고 있고, 강의에서와 마찬가지로 레포지토리단에 의존하고 있는 private 메서드인 경우에도 이 메서드의 책임을 서비스가 아닌 다른 객체로 위임하는 것이 과연 괜찮은 건지에 대한 의문이 들었습니다.Layered architecture 를 따른다면, 서비스단만이 레포지토리에 대한 의존성을 가게끔 하는 것이 맞지않나 라는 생각이고, 또 레포지토리의 변경이 발생했을때, 변경이 확산되는 범위를 서비스단으로 국한시키는 것이 더 좋은 게 아닐까라고 생각합니다.강사님의 생각이 궁금합니다!! 답변 달아주시면 감사하겠습니다!
-
해결됨Practical Testing: 실용적인 테스트 가이드
안녕하세요 @Autowired, @Mockbean, @Mock, @InjectMocks에 대해 질문 있습니다.
각 어노테이션을 언제 주로 사용하는지에 대해 아래와 같이 정리를 했는데 맞게 정리 한 건지 궁금합니다.@Autowired를 사용하는 경우@ActiveProfiles("test") @SpringBootTest class OrderServiceTest { @Autowired private ProductRepository productRepository; @Autowired private OrderRepository orderRepository; @Autowired private StockRepository stockRepository; @Autowired private OrderService orderService; ... }스프링 컨텍스트에 실제 빈 객체를 등록하고 해당 빈을 사용합니다. 이렇게 실제 빈을 사용하는 테스트라 실제 동작 검증에 있어서 가장 정확성이 높은 테스트 입니다.@Mockbean과 @Autowired를 섞어 사용하는 경우@WebMvcTest(controllers = ProductController.class) class ProductControllerTest { @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @MockBean private ProductService productService; ... }스프링 컨텍스트에 실제 빈이 아닌 Mock 빈을 등록합니다.컨트롤러 레이어에 대해서만 단위 테스트를 하고 싶은데 컨트롤러가 서비스 레이어에 의존하고 있으니 이러한 의존을 끊기 위해 @MockBean을 사용하는 방식입니다.@Mock과 @InjectMocks@ExtendWith(MockitoExtension.class) class MailServiceTest { @Mock private MailSendClient mailSendClient; @Mock private MailSendHistoryRepository mailSendHistoryRepository; @InjectMocks private MailService mailService; ... }@Mock을 통해 스프링 컨테이너에 등록되지 않는 가짜 객체를 생성합니다. 그리고 @InjectMocks에 사용된 객체에 @Mock을 통해 생성한 가짜 객체를 주입하는 방식입니다.스프링 컨테이너가 필요없는 외부 시스템에 대해 테스트를 할때 진행하는 방식입니다.위 질문 중에 "@Mockbean과 @Autowired를 섞어 사용하는 경우" 코드에 대해 질문이 있습니다. 컨트롤러에 대한 단위 테스트를 하기 위해 @MockBean을 사용하여 서비스에 대한 의존성을 끊는 부분은 이해를 했습니다. 근데 결국 서비스 객체를 가져다 쓰니 서비스가 의존하고 있는 Repository에 대한 부분도 @MockBean을 사용하여 스프링 컨테이너에 Mock을 등록해야 하지 않나 라는 생각이 듭니다.아래 코드에 대해 질문이 있습니다. 실제 스프링 부트를 실행 하면 각 서비스 객체와 Repository 객체 모두 스프링 컨테이너에 등록되어 서비스 레이어쪽에서 Repository레이어에 의존하는 상황입니다. 저는 스프링 컨테이너에 빈들도 잘 등록되고 서로 잘 데이터를 주고 받는지도 테스트를 해야한다고 생각하는데 아래와 같이 @InjectMocks과 @Mock을 사용하면 스프링 컨테이너와 상관이 없어져서 아래와 같은 상황에서 @InjectMocks과 @Mock을 사용하여 테스트 코드를 작성해도 되는지 궁금합니다. @ExtendWith(MockitoExtension.class) class CustomServiceImplTest { @InjectMocks private CustomServiceImpl customServiceImpl; @Mock private BankRepository bankRepository; @Mock private ProductRepository productRepository; @Mock private OrderRepository orderRepository; ... }1번 질문과 같이 정리를 했지만 @Autowired와 @Mockbean 사용에 대해 헷갈리는점이 있어 아래 내용을 정리 했는데 맞게 이해를 한건지 궁금합니다.@Autowired를 테스트 코드에서 사용하는 핵심은 해당 기능을 스프링 컨테이너에 등록하는걸 넘어서 해당 객체의 기능을 실제 사용하겠다 라는 의미다. @Mockbean을 테스트 코드에서 사용하는 핵심 이유는 의존 관계를 끊기 위함이다. 예를 들어 컨트롤러 쪽에서 서비스쪽에 강한 의존관계를 가지고 있어 우선 @Mockbean을 통해 생성한 가짜 객체를 스프링 컨테이너에 등록하고 이 가짜 객체를 컨트롤러쪽에서 의존하고 있는 객체에 넣어줘서 의존관계를 끊는다. 주의할점은 @Mockbean을 통해 생성한 가짜 객체의 기능은 사용하지 않는다. 아래 코드에서 @MockBean을 통해 생성한 ProductService 객체는 가짜 객체이므로 ProductRepository 객체를 못 불러오나요?@WebMvcTest(controllers = ProductController.class) class ProductControllerTest { @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @MockBean private ProductService productService; ... }
-
미해결따라하며 배우는 리액트 A-Z[19버전 반영]
넷플릭스 오리지널 제외하고 슬라이드가 동작을 안합니다.
<Row title="Netflix ORIGINALS" id="NO" fetchUrl={requests.fetchNetflixOriginals} isLargeRow > </Row> <Row title="Trending Now" id="NO" fetchUrl={requests.fetchTrending} > </Row> return ( <section className="row"> <h2>{title}</h2> <div className="slider"> <div className="slider__arrow-left"> <span className="arrow" onClick={() => { document.getElementById(id).scrollLeft -= window.innerWidth - 80 }}>{"<"}</span> </div> <div id={id} className="row__posters"> {movies.map((movie) => ( <img key={movie.id} className ={`row__poster ${isLargeRow && "row__posterLarge"}`} src={`https://image.tmdb.org/t/p/original/${isLargeRow ? movie.poster_path : movie.backdrop_path}`} alt={movie.name} > </img> ))} </div> <div className="slider__arrow-right"> <span className="arrow" onClick={() => { document.getElementById(id).scrollLeft += window.innerWidth - 80 }}>{">"}</span> </div> </div> </section> ) 넷플릭스 오리지널에서는 슬라이드가 제대로 동작합니다. 그런데 아래 row들의 화살표를 클릭했을때 넷플릭스 오리지널 포스터의 슬라이드가 동작하는데 이유를 모르겠습니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
외부 라이브러리를 사용하는 코드의 테스트 코드에 대해 질문이 있습니다.
강의를 보고 기존 프로젝트에 작성하지 않았던 테스트 코드를 작성하면서 공부하고 있습니다.도메인 모듈같은 경우 어떠한 의존성도 가지고 있지 않은 POJO 형태로 코드를 작성해서 단위 테스트로 검증할 수 있었습니다.Crawling 모듈의 경우 Jsoup이라는 라이브러리를 사용하여 특정 URL의 HTML을 불러오고, 해당 HTML에서 필요한 정보를 추출하는 코드를 가지고 있습니다.이때, 전체적으로 Mock을 사용하여 테스트를 진행하고자 하였는데 라이브러리의 특성 때문에 stubbing을 작성하는데 불편함을 느꼈습니다.그래서 HTML을 임의로 만들고, HTML을 불러오는 메서드만 stubbing을 적용하려고 하는게 더 효율적이지 않을까 라는 생각이 들었습니다.외부 라이브러리를 사용하는 코드에서 테스트 작성이 미숙한 것 같아서 어떤 방향이 더 좋을지 질문드리고 싶습니다. 아래 두 코드는 동일한 테스트를 진행하고 있는 코드가 아닌점 참고바랍니다.@Mock private lateinit var webPageLoader: WebPageLoader<Document> @InjectMocks private lateinit var dietParser: DietParser fun parse() { // given val mockDocument = mock(Document::class.java) val mockRows = Elements() val row1 = mock(Element::class.java) val row2 = mock(Element::class.java) val row3 = mock(Element::class.java) mockRows.add(row1) mockRows.add(row2) mockRows.add(row3) val columns1 = Elements( mock(Element::class.java).apply { given(text()).willReturn("2024.10.23") }, mock(Element::class.java), mock(Element::class.java), mock(Element::class.java).apply { given(text()).willReturn("Menu 1, Menu 2") } ) val columns2 = Elements( mock(Element::class.java).apply { given(text()).willReturn("교직원식당") } ) val columns3 = Elements( mock(Element::class.java).apply { given(text()).willReturn("2024.10.24") }, mock(Element::class.java), mock(Element::class.java), mock(Element::class.java).apply { given(text()).willReturn("Menu 3, Menu 4") } ) given(webPageLoader.getHTML(anyString())).willReturn(mockDocument) given(mockDocument.select(anyString())).willReturn(mockRows) given(row1.select(anyString())).willReturn(columns1) given(row2.select(anyString())).willReturn(columns2) given(row3.select(anyString())).willReturn(columns3) // when val result = dietParser.parse() // then assertThat(result).hasSize(2) .extracting("date", "menus") .containsExactly( tuple(LocalDate.of(2024, 10, 23), listOf("Menu 1", "Menu 2")), tuple(LocalDate.of(2024, 10, 24), listOf("Menu 3", "Menu 4")), ) } @Mock private lateinit var webPageLoader: WebPageLoader<Document> @InjectMocks private lateinit var parser: DepartmentNoticeParser fun parse() { // given val html = """ <table class="board-table"> <tbody> <tr> <td class="td-num">2</td> <td class="td-subject"><a href="javascript:fnView('2','url','title','2')">공지 제목 2</a></td> <td class="td-write">작성자 2</td> <td class="td-date">2024.10.02</td> </tr> <tr> <td class="td-num">1</td> <td class="td-subject"><a href="javascript:fnView('1','url','title','1')">공지 제목 1</a></td> <td class="td-write">작성자 1</td> <td class="td-date">2024.10.01</td> </tr> </tbody> </table> """.trimIndent() val document: Document = Jsoup.parse(html) given(webPageLoader.getHTML(anyString())).willReturn(document) parser.initialize(Major.COMPUTER_SOFTWARE_ENGINEERING) // when val notices = parser.parse() // then assertThat(notices).hasSize(2) .extracting("number", "title", "author", "date", "url") .containsExactlyInAnyOrder( tuple( 2, "공지 제목 2", "작성자 2", LocalDate.of(2024, 10, 2), "https://www.dongyang.ac.kr/combBbs/2/url/2/view.do?layout=unknown" ), tuple( 1, "공지 제목 1", "작성자 1", LocalDate.of(2024, 10, 1), "https://www.dongyang.ac.kr/combBbs/1/url/1/view.do?layout=unknown" ), ) }