Table: Person

+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| id          | int     |
| email       | varchar |
+-------------+---------+
id is the primary key (column with unique values) for this table.
Each row of this table contains an email. The emails will not contain uppercase letters.

 

Write a solution to delete all duplicate emails, keeping only one unique email with the smallest id.

For SQL users, please note that you are supposed to write a DELETE statement and not a SELECT one.

For Pandas users, please note that you are supposed to modify Person in place.

After running your script, the answer shown is the Person table. The driver will first compile and run your piece of code and then show the Person table. The final order of the Person table does not matter.

The result format is in the following example.

Example 1:

Input: 
Person table:
+----+------------------+
| id | email            |
+----+------------------+
| 1  | john@example.com |
| 2  | bob@example.com  |
| 3  | john@example.com |
+----+------------------+
Output: 
+----+------------------+
| id | email            |
+----+------------------+
| 1  | john@example.com |
| 2  | bob@example.com  |
+----+------------------+
Explanation: john@example.com is repeated two times. We keep the row with the smallest Id = 1.

 

해당 문제를 이제 풀려고 하는데 
일단 첫번째로

SELECT MIN(ID),EMAIL
FROM PERSON
GROUP BY EMAIL

이걸 적었는데 전혀 먹히지 않더라...
그래서 
DELETE문을 사용하려 했다.

DELETE FROM PERSON
WHERE ID = (SELECT MAX(ID) FROM PERSON GROUP BY EMAIL);

 

그런데 계속 RUNTIME ERROR가 나면
You can't specify target table 'PERSON' for update in FROM clause
이 오류 메시지를 보여주더라.

그래서 왜 그럴까??라고 알아본 결과

같은 테이블(Person)을 "삭제하려고 하면서 동시에 SELECT 서브쿼리에서도 읽어오는 것"을 금지한다고 한다.

DELETE를 하려는 대상을 FROM  절 안에서 직접 SELECT하고 있기 때문에 충돌(위험)이 생긴다고 본다고 한다.

즉 업데이트하려는 테이블과 읽으려는 테이블을 동일하게 사용하면 안된다는 규칙이 있다.

 

그럼 어떻게 해야할까?

DELETE p1
FROM PERSON p1
JOIN PERSON p2
ON p1.Email = p2.Email AND p1.Id > p2.Id;


다시 Person을 조인해서 p1과 p2 사이에서 같은 이메일을 찾고 p1.Id와 p2.Id를 비교해 p2.Id보다 큰 p1.Id를 찾아서

삭제 한다.

코딩을 공부하다보면 수많은 코드들에서 Import, export가 정말 많이 나온다. 이것들이 무엇이고 어떻게 활용할 수 있는지 정리해보자

 

Import

한 마디로 가져온다는 거다. 무엇을? export 한 것을.

 

Export

한 마디로 내보낸다는 거다. 무엇을? 내가 내보내고 싶은 것을.

 

코드로 간단하게 보자

 

//Math.js
export default function add(a, b) {
  return a + b;
}


Math.js에서 내가 add라는 함수를 다른 곳에서 사용하고 싶을 때 이렇게 export를 사용하면 된다. 여기서 default가 붙어 있는데 이것은 이 파일에서 기본으로 export하고 싶은 값을 사용하고 싶을 때 사용한다. export default는 파일 당 하나의 값만 보낼 수 있다. 

 

자 그러면 이 내보낸 것을 다른 파일에서 써야겠지?

 

//main.js
import sum from './math.js';

console.log(sum(4, 5)); // 9

 

export한 값을 Import를 사용해서 가져와서 사용하면 된다. 

 

export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}

Export는 여러개 할 수 있다.

 

import { add, subtract } from './math.js';

console.log(add(10, 5));     // 15
console.log(subtract(10, 5)); // 5

Import도 중괄호를 활용해서 여러 개를 한 번에 받을 수 있다.

만약 이름을 바꾸고 싶다면??

 

import { add as plus, subtract as minus } from './math.js';

console.log(plus(10, 5));  // 15
console.log(minus(10, 5)); // 5

이런 식으로 바꾸면 된다. 

 

또 Import를 활용해서 모든 내보낸 값을 한 번에 가져오는 방법도 있다. 

import * as math from './math.js';

console.log(math.add(10, 5));     // 15
console.log(math.subtract(10, 5)); // 5

 

모든 값을 가져와서 math라고 이름을 붙인디 객체를 사용하듯이 math.함수( ) 이런 식으로 활용이 가능하다.

 

React를 공부하다보면

import React from 'react';

export default function Button({ label }) {
  return <button>{label}</button>;
}
import React from 'react';
import Button from './components/Button';

function App() {
  return <Button label="Click me!" />;
}

export default App;

 

이런 식의 코드를 정말 많이 보게 된다. React 뿐만 아니라 Java, Spring 등 많이 언어와 프레임워크에서도 그냥 숨쉬듯 볼 수 있다.

 

이렇게 파일을 모듈화하면 코드가 더 깔끔해지고 유지보수가 쉬워짐으로 그냥 무조건 필수적으로 쓰고 알고 있어야한다.

'IT 지식' 카테고리의 다른 글

명령형 프로그래밍?? 선언형 프로그래밍??이 뭘까?  (0) 2025.04.03

어제 리액트를 공부하면서 이런 말이 나왔다. 리액트는 선언형으로 작성해야하고 자바스크립트는 명령형으로 작성하라고 한다. ???? 이게 도대체 무슨 말인가? 싶어서 이제 검색을 해봤고 그에 관한 것을 정리하고자 한다.

 

명령형 프로그래밍(Imperative)

  • 어떻게(How) 할 것인가에 집중하여야 한다.
  • 개발자가 단계별로 명령을 내려서 원하는 결과를 얻는다.
  • 보통 반복문, 조건문, 직접적인 DOM 조작 등이 포함된다.
const button = document.createElement("button");
button.textContent = "클릭!";
button.addEventListener("click", () => {
  alert("버튼 클릭됨!");
});
document.body.appendChild(button);

 

즉 개발자가 직접 순차적으로 이렇게 요렇게 할 것이다라고 명령을 내려서 프로그래밍 하는 것이다.

위의 코드 같은 경우도 개발자가 직접 DOM을 조작해서 순차적으로 명령을 내려서 UI를 업데이트 한 것.

 

선언형 프로그래밍(Declarative)

  • 무엇을(What) 할 것인가에 집중
  • 결과를 선언하고, 내부적으로 알아서 처리하도록 함
  • 코드가 더 직관적이고 유지보수가 쉬움
function App() {
  return <button onClick={() => alert("버튼 클릭됨!")}>클릭!</button>;
}

 

직접적인 DOM을 조작하지 않고 이런 기능을 할 거야라고 선언만 한다.

이후 React가 이 선언을 바탕으로 내부적으로 최적화된 방식으로 UI를 그려준다.

 

그럼 어떤 게 더 좋다라는 게 있을까?

 

한 번 장단점을 비교해보자

 

명령형(Imperative)

장점

  • 흐름을 세밀하게 제어가능하다
  • CPU 연산, 알고리즘 최적화에 유리하다

단점

  • 코드가 길고 복잡해짐
  • 유지보수가 어려움

- 그래서 보통 성능이 중요한 저수준 작업 (Low-Level)에헛 구현, 디버깅이 중요한 경우.

 

선언형(Declarative)

장점

  • 코드가 간결하고 가독성이 높다.
  • 유지보수가 쉽다. (구현보다 로직에 집중)
  • 병렬 처리 최적화 용이

단점

  • 내부 동작을 완전히 제어하기 어려움
  • 디버깅이 어려울 수 있다.

- UI 개발(React, HTML, CSS)이나 데이터 변환(SQL, Java Streams, Functional Programming), 프레임워크(Spring, ORM, DI)에서 많이 쓰일 수 있다.

'IT 지식' 카테고리의 다른 글

Import??? export?? 가 뭘까?  (0) 2025.04.03

프로그래머스 코딩테스트 연습 - SQL - String, Date

SELECT
    A.USER_ID,
    A.NICKNAME,
    CONCAT(A.CITY, " ", A.STREET_ADDRESS1, " ", A.STREET_ADDRESS2) AS '전체주소',
    CONCAT(SUBSTR(A.TLNO, 1, 3), '-', SUBSTR(A.TLNO, 4, 4), '-', SUBSTR(A.TLNO, 8, 4)) AS '전화번호'
FROM
    USED_GOODS_USER A
LEFT OUTER JOIN USED_GOODS_BOARD B
    ON B.WRITER_ID = A.USER_ID
GROUP BY A.USER_ID
HAVING COUNT(B.BOARD_ID) >= 3
ORDER BY A.USER_ID DESC

 

여기서 SUBSTR을 쓰니 계속 틀렸다고 한다. 그래서 막 검색을 해보니 SUBSTRING을 써보니 또 맞다고 한다.

그렇다면 SUBSTR과 SUBSTRING의 차이는 무엇일까?

 

사실 큰 차이는 없다. SUBSTR은 SUBTRING의 ALIAS 같은 존재이기 때문이다.

SUBSTRING의 경우는 구분자로 문자열을 자를 수 있다는 기능이 좀 더 있다고 하지만 사용방법에 있어서는

큰차이가 없다고 한다.

 

그럼 왜 SUBSTR은 안되고 SUBSTRING은 맞다고 했을까?

이것도 검색해보고 나름 생각해본 결과 

일단 SUBSTRING( )의 경우는 ANSI SQL의 표준이다. 그렇기 때문에 다른 데이터베이스에서도 많이 지원한다. 그러나 SUBSTR( )의 경우는 일부 DB에서는 지원하지 않을 수 있다고 한다. 

 

하지만 이건 정답의 유무를 가를 순 없다. 젤 큰 문제는 다음과 같다

 

SUBSTR( )의 경우 일부 MYSQL 버전에서 GROUP BY 절이 있을 때 비정상적으로 동작하는 경우 가 있다고 한다.

SUBSTRING( ) 경우에는 ANSI SQL을 따르므로 정상적으로 작동한다고 한다. 

그래서 내생각에는 아마 MYSQL 버전에 따른 문제일 수도 있고 아니면 GROUP BY를 하고 SUBSTR( ) 사용하면 비정상적으로 작동 하는 경우가 있기에 오답 처리가 됐을거라고 생각한다.

 

※ ANSI SQL이란?

ANSI SQL(American National Standards Institute SQL)의 약자로 SQL의 표준 규격을 의미한다.

즉, 모든 데이터베이스(DBMS)에서 공통적으로 사용할 수 있도록 정해진 SQL 문법과 기능을 정의한 것이다.

각각의 DBMS는 SQL을 조금씩 다르게 구현하는데 ANSI SQL 표준을 따르면 여러 DBMS 간의 호환성을 높일 수 있어서 유지보수와 이식성이 좋아진다.

const isAdmin = admins.includes(user.uid);

공부를 하다가 이런 코드를 만났다.

includes 이게 뭐하는 함수인지 전혀 몰랐다. 그리곤 아..뭐 admins에 user.uid를 추가하는 함수인가 보다 하고 넘어갔는데

다음날 다시 이제 공부를 하는데 이 부분을 잘못하고 이해하고 있어서 살짝 헤맸다.

그래서 한 번 정리하고자 한다.

 

includes는 자바스크립트에서 데이터형태가 배열일 때 사용할 수 있는 함수이다.

includes( )는 배열(Array)에서 특정 값이 존재하는지 확인하는 메서드이다. 

const arr = ['apple', 'banana', 'cherry'];
console.log(arr.includes('banana')); // true
console.log(arr.includes('grape')); // false

 

그런데 이걸 알고 있다가 데이터가 Object(객체) 면 사용할 수 없다.

그러면 Object일 때는 그러면 어떻게 해야하는가?

 

뭐 여러가지 방법이 있겠지만 일단 먼저 객체의 데이터를 배열로 변환해야한다.

이때 객체의 key의 데이터가 필요한지 value의 데이터가 필요한지에 따라 나뉜다.

key가 필요하다면 Object.key(data).includes(findValue)로 하면 될것이고

value가 필요하다면 Object.values(data).includes(findValue)로 하면 될 것이다.

 

정리하자면

Array.includes( )는 배열에 특정 값이 있는지 확인하는 함수이다. Object에서도 이를 활용하고 싶다면

Object.keys( ) -> 객체의 키들을 배열로 변환
Object.values( ) -> 객체의 값들을 배열로 변환

해서 활용하면 된다.

 

 

<Button text={'Login'} onClick={login} />
<Button text={'Logout'} onClick={logout} />

<Button text={'Login'} onClick={login()} />
<Button text={'Logout'} onClick={logout()} />

 

리액트를 공부하다보면 항상이 이 부분이 헷갈렸고 이번에 제대로 한 번 집고 넘어가보고자 한다.

보면 어떨때는 onClick={login} 이렇게 참조값만 전달하는 경우가 있고 어떤 경우는 onClick={login()}이렇게 함수를 전달해 놓은 경우가 있다. 이 둘의 차이점이 뭘까? 그리고 어느 경우 어떨 때 구분해서 사용해야할까?

 

  • onClick={login}의 경우
    login은 함수의 참조를 전달하는 거다. 
    React는 이 login 함수를 이벤트 핸들러로 등록해두고, 버튼이 클릭될 때 login 함수를 실행한다
    즉, 버튼을 클릭할 때만 login 함수가 실행된다.
  • onClick={login()}의 경우
    login()은 함수를 실행한 결과값을 전달한다.
    만약 login 함수가 비동기 함수라서 Promise를 반환한다면 onClick에는 Promise가 전달된다
    이러면 버튼이 랜더링될 때 login()이 실행되어 버리고, 버튼을 누르지 않아도 로그인 요청이 되는 문제가 생긴다.

자 그렇다면 이제 상황을 구분지어보자.

예를 들어 Button을 눌렀을 때나, 뭐 특정 Event가 발생해야될 때만 함수가 실행되게 하고 싶다?

그러면 onClick={참조값}만 전달하면 된다.

 

하지만 랜더링이 될 때 값을 전달 받아서 처리하고 싶거나 실행되게끔 하고 싶다?

그러면 onClick={함수} 이렇게 하면 된다.

분명 이렇게 딱 구분지을 수 없는 것이기 하지만 일단은 이렇게 이해하도록 하자.

 

자 그런데 만약에 

 

<Button text="Login" onClick={loginWithParam("google")} />

이런식으로 parameter를 받아야하는 경우에는 어떻게 해야할까?

이 경우는 parameter를 받아야하는데 이렇게 작성해 버리면 랜더링 될 때 실행이 됨으로 내가 원하는 이벤트가 아니다

그러면 어떻게 해야하는 가?

<Button text="Login" onClick={() => loginWithParam("google")} />

익명 함수로 감싸는 걸로 해결하면 된다.

'React' 카테고리의 다른 글

Redux에 대해서  (0) 2024.06.15
ContextAPI 이용해서 다크모드 사용하기(React)  (2) 2024.01.07
Component 재사용하기 (React)  (0) 2023.12.31
React 상태관리 - UseImmer  (0) 2023.12.27
React 상태관리 - useReducer  (0) 2023.12.26

테스트를 위해서 임시적인 데이터와 SEQ_NO를 만들어서 테스트해야하는 경우가 있었다. 

그래서 임시 테이블과 임시 SEQ_NO를 생성하는 방법을 알아보자. 

 

물론 여러가지 방법이 있겠지만 내가 주로 사용했던 방법을 기록하고자 한다. 

CREATE TEMPORARY TABLE temp_table (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    created_at DATETIME
);

INSERT INTO temp_table (id, name, created_at)
VALUES
    (3, '뚱이', NOW()),
    (4, '스폰지밥', NOW()),
    (5, '징징이', NOW()),
    (6, '다람이', NOW()),
    (7, '집게사장', NOW()),
    (8, '플랑크톤', NOW()),
    (9, '메롱시티', NOW());

 

 

위의 방법으로 임시테이블과 임시데이터를 생성할 수 있었다. 

이제 임시 SEQ_NO를 생성하려고 하는데 검색해서 알아본 결과 ROW_NUMBER를 활용해서 만들라고 하더라.

 

SELECT id,
    name,
    created_at,
    ROW_NUMBER() OVER (ORDER BY created_at) AS seqNo
FROM temp_table;

 

그래서 이용만들려고 하니

 

'1064. You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '(ORDER BY created_at) AS seqNo, name, created_at FROM temp_table' at line 2 0.000 sec'

 

이 오류가 생기네???

알아보니 ROW_NUMBER는 MySQL version 8.0 이상부터 가능하다고 하더라.

내 version을 확인해보니

5.7대네...그럼 어떻게 해야하지....

하다가

SET을 활용하라고 하더라.

 

SET @seqNO = 0;

SELECT 
	id,
    name,
    created_at,
    (@seqNO := @seqNO + 2) AS SEQ_NO
FROM temp_table
ORDER BY name;

 

 

이런 식으로 내가 원하는 증가량 만큼 임시 SEQ_NO를 만들 수 있다. 

SpringBoot를 이용해서 JSP를 사용해서 이제 프로젝트를 하나 해보려고 한다....

굳이 JSP를 이용하는 이유는 생각보다 JSP가 많이 사용하는 것 같아서이다...(SI에 있어서 그럴 수도)

그래서 이제 해보려고 하는데 CSS 연결이 생각보다 안되서 찾고 찾아서 드디어 해결해서 나중에 또 헤매고 시간낭비하기 싫어서 이렇게 글을 쓴다. 

 

분명 다양하게 하는 방법이 있겠지만 이 방법으로 해결했기 적는다.

 

1. 먼저 폴더의 위치를 파악한다.

 

일단 나 같은 경우는 이런 식으로 폴더 위치가 되어있다. WEB-INF 같은 경우는 JSP를 사용하게 되면 써야하는 폴더이다. 

원래 맨 처음에는 저 resources 폴더를 WEB-INF 안에 넣었었다. /WEB-INF/resources 이렇게 말이다.

그런데 이걸 못찾는 거 같더라...그래서 계속 css파일이 404error가 떴었다. 그래서 첫번째로 resources 폴더를 밖으로 뺏다.

 

2. yml 파일 설정하기.

 

 

yml파일을 해당 사진처럼 설정했다. static 즉 정적인 자원들은 해당 위치에서 찾을 수 있도록 설정한 것이다.

 

3. 해당 JSP 파일에서 CSS 경로 확인하기.

 

<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/resources/css/login/login.css" />

필자는 이렇게 경로를 썼고 여기서 ${pageContext.request.contextPath}는 JSP 내에서 현재 애플리케이션의 컨텍스트 경로를 자동으로 가져오는 표현식이라고 한다. 이 경로는 애플리케이션이 배포된 위치에 따라 동적으로 설정되므로, 별도로 지정할 필요 없이 JSP 페이지 내에서 사용할 수 있다고 한다.

 

4. 정적 리소스 핸들러 설정하기.

package com.study.lms.common.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer{

	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
	
		registry.addResourceHandler("/resources/**")
				.addResourceLocations("/resources/");
		
	}
	
}

 

common 패키지 하나 만들어서 안에 config 패키지 만들고 WebConfig 클래스 만들어서 이렇게 정적 리소스 핸들러를 설정해서 등록해줬다.

 

그러니 

 

아주 잘 찾아오는 것을 볼 수 있다~

이번에 Jnit5를 이용해서 Test를 짜는데 findAll 함수를 이제 Test해보려고 했다. 그런데 계속 

 

SQL: SELECT id, title, author FROM book; Cause: java.sql.SQLSyntaxErrorException: ORA-00933: SQL 명령어가 올바르게 종료되지 않았습니다 ; bad SQL grammar []

 

이 오류가 뜨는 것이다. 분명히 틀린 게 없다. 그래서 Mapper도 확인해봤다.

    <select id="getAllBook" resultType="com.study.database.domain.Book">
        SELECT 
        	id, 
        	title,
        	author 
        FROM book;
    </select>

 

이상하다...전혀 틀린게 없는데 왜 이렇지...를 계속 고민하고 찾고 하던 중에 문제를 발견했다.

바로 book뒤에 있는 ; 이녀석 세미콜론 때문이었다.

 

이 녀석을 지우고 돌리니 아주 잘된다.

 

그러면 도대체 이유가 뭘까?? 아니 SQL 짤때 ; 세미콜론 이 녀석은 SQL문을 끝마친다는 표시 아닌가? 그래서 저렇게 해줬는데 뭐가 잘 못 됐다는 걸까?

 

알아보니 JDBC나 MyBatis와 같은 ORM 도구에서는 세미콜론은 구문 오류를 발생시킬 수 있다고 한다.

JDBC 드라이버는 쿼리의 끝을 세미콜론 없이 인식한다고 하기에 세미콜론을 포함시키면 불필요한 문자가

포함된 것으로 간주되어 간혹 구문 오류가 발생할 수 있다고 한다.

 

※ 세미콜론 삭제 후 잘 돌아가서 이 글을 쓰기위해 다시 세미콜론을 붙이곤 돌리는데 이상하게 또 잘 돌아간다....간혹 그럴 때가 있나 보다...하지만 모른다면 계속 하루죙일 뭐가 틀렸지 하고 다른 곳에서 오류를 찾느라 정상적인 곳을 이상하게 만들 수 있으니 알고가자

코딩을 할 때 Test하는 것은 매우 중요하다고 요새 생각이 든다. 그래서 이 Test 하는 방법을 잘 알아둬야 잘하는 개발자로 성장할 수 있다고 본다. 솔직히 전에는 테스트를 잘 안했다. 안했다기 보다는 이런 Junit과 같은 Test 도구를 사용하지 않았다. 그냥 console.log로 찍어보거나 log.info로 데이터가 잘 나오나 안나오나 혹은 system.out으로 확인하곤 했다. 혹은 PostMan을 이용해서 요청을 하고 잘 응답이 오나 안오나를 확인했다.

하지만 실무에서 계속 PostMan을 이용해서 Test할 수도 없는 노릇이고 디버깅을 잘하거나 해야한다고 한다. 여담으로 실무가서 sysout 쓰면 바로 초짜티 난다고하느데..... 아무튼 이제 알아보자.

 

일단 여러가지 방법이 있겠지만 Controller를 Junit5에서 어떻게 테스트 할 수 있는지에 대해서 알아도록 하자

사실 service나 repository 같은 경우는 그 로직에 대해서만 test하면 되기 때문에 assertThat을 이용해서 잘 수행이 됐는지 안됐는지만 확인하면 된다.

 

하지만 controller는 조금 다르다. 왜냐하면 요청이 들어오고 그에 대한 응답을 Test 해야하기 때문에 요청도 해줘야하고 그에 맞는 검증을 또 해줘야 하기 때문이다.

 

먼저 코드로 바로 보자

 

package com.study.database.controller;

import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.study.database.domain.Book;
import com.study.database.service.BookService;

import lombok.extern.slf4j.Slf4j;

// 단위 테스트(Controller 관련 로직만 띄우기) Filter, ControllerAdvice
@Slf4j
@WebMvcTest(BookController.class)
public class BookControllerUnitTest {
	
	@Autowired
	private MockMvc mockMvc;
	
	@MockBean // IoC 환경에 bean 등록됨.
	private BookService bookService;
	
	// BDDMockito 패턴
	@Test
	public void save() throws Exception {
		// given (테스트를 하기 위한 준비)
		Book book = new Book(null, "스프링 따라하기", "mollani");
		String content = new ObjectMapper().writeValueAsString(book);
		log.info("===== content 확인 : {}", content);
		when(bookService.saveBook(book)).thenReturn(1);
		
		//when(테스트실행)
		ResultActions resultAction = mockMvc.perform(post("/book")
				.contentType(MediaType.APPLICATION_JSON)
				.content(content)
				.accept(MediaType.APPLICATION_JSON));
		
		// then(검증)
		resultAction
			.andExpect(status().isCreated())
			.andExpect(content().string("1"))
			.andDo(MockMvcResultHandlers.print());
		
	}

 

일단 여기서 Mock이라는 개념을 좀 알고 가야할 거 같다.

Mock 이란

- 실체 객체를 만들어서 테스트하기 어려운 경우에, 가짜 객체를 만들어서 테스트하는 기술

 

MockMvc

- 웹 어플리케이션을 어플리케이션 서버에 배포하지 않고, 테스트용 MVC 환경을 만들어서 요청 및 전송, 응답 기능을 해주는 객체

- 스프링 MVC 테스트의 주요 구성요소로, HTTP 요청을 모킹하여 컨트롤러의 동작을 테스트할 수 있게 해줌.

 

위의 코드에서 중요한 건 두개라 생각한다. @WebMvcTest와 @MockBean

 

@WebMvCTest

- Controller를 테스트하는 데 중점

- Service, Repository 등의 Bean은 로드되지 않음

- 테스트할 Controller 클래스를 지정할 수 있음.

 

@MockBean

- 이것을 사용하면 서비스 빈을 Mocking하여 Controller 동작을 테스트

 

한 번 정리하자면 @WebMvcTest는 Controller를 테스트하는데 중점을 두기에 Service, Repository를 Bean에 등록되지 않는다. 어??? 그러면 Controller에서 Service 로직이 있는데 어떻게 동작하지??라고 의문이 드는데 이때 바로 이용하는 것이 @MockBean이다. 가짜 Service 객체를 넣어주는 거다.

 

그래서 BDDMockito는 Behavior-Driven Development (BDD) 스타일의 테스트 기법에 맞게 Given - When - Then 순으로 Test코드를 짜면 된다.

 

※ ObjectMapper

- Jackson 라이브러리의 주요 클래서이며 Json 처리를 위한 다양한 기능을 제공한다.

 

.writeValueAsString - 주어진 자바 객체를 Json 문자열로 변환

+ Recent posts