반응형

 

해시 - 솔트치기

이번 카카오페이 사건을 통해 대충만 알고있던 솔트에 대해 정리하는 시간을 가져봤다.

암호화를 했는데 왜 문제인가?

보도자료(상세) | 보도자료 | 보도·알림 |

카카오페이에서 외부 api(알리페이)와 통신할 때 사용자의 개인정보 값에 대한 암호화 처리를 제대로 하지 않았다.

보도자료의 랜덤값 없이 단순하게 해시처리(암호화) 라는 말은 암호화 작업을 하긴 했지만 암호화 작업의 기본인 랜덤값(솔트)을 넣지 않았다고 하는 것인데, 이 작업을 진행하지 않고 단순 해시처리를 한다면 레인보우 테이블 공격에 취약하다.

이번 주제에서 설명하는 해시(암호화)는 단방향 암호화인 MD5, SHA256 등의 암호화 즉 디코딩 할 수 없는 값을 기준으로 설명 할 것이다.

암호화 테스트 링크 : https://emn178.github.io/online-tools/sha256.html

해시

우리가 hello 를 회원가입 할 때 입력받아 암호화 하여 데이터베이스에 저장한다고 가정해보자.

회원가입 할 때 받은 비밀번호를 단순 해시화 하여 값을 저장했을 때

SHA256(hello)2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 의 값으로 단방향 해시되어 데이터베이스에는 암호화 된 값을 저장할 것이다. 그래야 데이터베이스를 탈취당해도 유저의 실제 암호는 알 수 없기 때문인데

사실은 값을 찾아낼 수 있다.

레인보우 테이블

복호화를 하지 못하는데 값을 어떻게 찾을 수 있는지 의문이 들지만 해시된 값이 그저 단순 문자열로 이루어진 고정 값이라면 레인보우테이블 기법을 통해 해시된 값을 알아낼 수 있다.

레인보우 테이블이란 기존 문자들을 미리 암호화 처리하여 사전처럼 해시값 기준으로 원래의 문자열을 알아낼 수 있는 형태의 테이블이다.

레인보우 테이블이란 암호화된 해시값을 키로 두고, 키에 대한 원조 값을 찾아낼 수 있는 값을 value로 두는 사전이라고 볼 수 있는데 예시 테이블을 참고해서 보자면

key value
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 hello
8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4 hi
5d5b09f6dcb2d53a5fffc60c4ac0d55fabdf556069d6631545f42aa6e3500f2e sha256
3ebff31b62c0637c54d4ffa990d5c100ea359994b35f4b342ff49797542148cd md5

만약 이러한 테이블을 저장한 레인보우 테이블이 있을 때 해커는 hello의 값을 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 해시 검색을 통해 알아낼 수 있을 것이다.

코드 예시

import java.util.*;

public class main
{
    public static void main(String[] args)
    {

                // 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824의 값이 저장됨
        String passwordSave = sha256("hello");

        //만들어져 있는 레인보우 테이블이라고 가정
        Map<String, String> rainbowTable = new HashMap<>();
        rainbowTable.put(sha256("hello"), "hello");

        //얻은 해시값
        System.out.println(rainbowTable.get("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"));
    }

    public static String sha256(String needEncodingString){
            //해싱처리
        //hello -> sha256 -> 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
        return "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824";
    }
}

---------
출력 값 : hello

솔트 치기

솔트를 치는 이유는 보안 강화이며, 단순한 해시화는 앞에서 보았던 것 처럼 쉽게 예측이 가능한 것을 보았다.
해시작업을 할 때 기존의 문자열만으로 해시하는 것이 아니라 기존 문자열과 함께 서버에서만 알 수 있는 문자열을 부여해서 두 문자를 합친 해시값을 만드는 것이다.
예를 들면 sha256(hello + "qwerafaad" ) 와 같이 암호와 관련이 없는 문자열을 해시하기 전 추가해서 전혀 다른 해시값을 만드는 것이다. 이 때 암호와 관련이 없는 문자열을 솔트(소금)이라고 부른다.

또 솔트를 칠 때 주의해야 할 점이 있는데 이에 대해 알아보자

솔트값

소금 값은 짧지 않은 값으로 생성되어야 한다.
평범한 비밀번호로 구성되어있는 룩업 테이블(레인보우 테이블) 등의 경우 837G 만으로 전체 룩업 테이블을 구성할 수 있다. 이 837GB에서 “asd” 라는 문자가 추가되는 테이블을 만들었다고 했을 때 해커 입장에서는 부담스럽지 않은 값으로 해당 테이블의 조합을 만들어 낼 수 있을 것이다.

때문에 소금 값은 보통 해시 알고리즘을 사용해서 완성되는 해시길이로 생성한다.
ex ) SHA256 알고리즘은 32바이트 이기에 소금 값도 랜덤한 32바이트

유저별로 랜덤한 문자열을 생성

랜덤한 문자열을 만들어놓고 그 값을 그대로 재사용 해서는 안된다. 소금 값의 의미가 없어지는데, 예를 들어 하나의 유저의 비밀번호가 뚫렸다고 생각해보자

  • “hello”(비밀번호) + “adad”(소금)

해커는 hello라는 비밀번호에 대해 adad의 소금 값을 알아냈다.

  • 모든 유저의 비밀번호를 하나의 소금값으로 사용
  • 비밀번호가 유출된 유저가 비밀번호 재설정을 했을 때

위의 두가지 상황에서 해커는 소금값을 알기 때문에 쉽게 찾아낼 수 있을 것이다.
위와 같은 이유로 소금을 칠 때는 반드시 두가지 규칙(짧지 않은 소금 값, 재사용 금지)을 따라야 한다.

정리하면 다음과 같다.

  • 소금값(Salt) 생성
    • 사용자 계정을 생성하거나 비밀번호를 변경할 때, 새로운 임의의 랜덤 소금값을 생성합니다. 이 소금값은 절대 재사용하지 않으며, 충분히 길고 다양한 값을 가지도록 해야 한다. 일반적으로 소금값의 길이는 해시 함수의 출력 길이와 동일하게 해야한다.
  • 비밀번호 해싱
    • 사용자가 입력한 비밀번호에 생성된 소금값을 추가(일반적으로 결합)한 후, 그 결합된 값을 해시 함수에 전달하여 해시된 비밀번호를 생성한다.
  • 저장
    • 생성된 소금값과 해시된 비밀번호를 사용자 계정 테이블에 저장합니다. 이때 소금값은 별도로 저장되며, 해시된 비밀번호와 함께 보관.

참조

https://starplatina.tistory.com/entry/비밀번호-해시에-소금치기-바르게-쓰기

레인보우 테이블 : https://en.wikipedia.org/wiki/Rainbow_table

 
반응형

'공부 > CS' 카테고리의 다른 글

언어별 연산 속도  (0) 2024.12.30
Context Switching(문맥 교환)  (0) 2024.09.19
CGI, FastCGI  (1) 2024.07.23
Web Server  (0) 2024.07.22
HTTP통신 과정  (0) 2024.07.01
반응형

자바 프로젝트를 실행할 때는 컴파일이 완료된 프로젝트 파일의 압축형태의 실행파일 .jar파일로 배포를 진행한다.

단일 파일 컴파일

코드로 작성한 자바파일을 실행시키기 위해선 컴파일 과정을 거쳐야한다.

이 때 자바 단일 파일은 컴파일 이후 즉시 실행시킬 수 있다.

javac HelloWorld.java

------ 컴파일 완료(Helloworld.class파일 생성) ------

java HelloWorld

위와 같은 명령어를 통해 실행이 가능

멀티 파일 컴파일

javac -d out src/com/example/*.java

--- 외부 라이브러리(jar) 참조
javac -d out -cp "libs/*" src/com/example/*.java

srt/com/example/ 내의 전체 파일을 탐색하여 컴파일

jar 파일 생성

jar cfe HelloWorld.jar HelloWorld HelloWorld.class

c : 새로운 JAR파일 생성
f : 출력 JAR 파일 이름을 지정
e : 실행 파일의 진입점(메인 클래스)

java -jar HelloWorld.jar

위와 같이 실제 컴파일 이후 실행(배포)까지의 과정이 상당히 번거롭다

때문에 이후 여러 컴파일, 빌드 도구가 나왔는데, 대표적으로 Maven, Gradle이 있다.


빌드 도구

Gradle은 빌드 도구라고 불리는데 다음과 같은 작업을 돕는다.

  • 컴파일
    • 개발된 프로젝트는 하나의 파일로 실행되지 않기 때문에 여러 파일을 모두 컴파일, 외부 라이브러리 등록+컴파일 등 여러 작업을 한번에 진행해야 한다.
      때문에 명령어로 관리하기 힘들어졌고, 빌드 도구가 나오게 되었다.
  • 의존성 관리
    • 프로젝트에서 필요한 라이브러리, 모듈등을 등록 후 간편하게 사용할 수 있다.(build.gradle 파일 의존성 설정)
  • 패키징
    • 프로젝트의 전체 파일을 컴파일 및 외부 라이브러리를 등록 후 패키징 작업을 진행한다.
      패키징 파일은 .jar or .war로 패키징 되고, 이 패키징 된 파일을 실행하면 배포가 가능하다.
  • 테스트 자동화
    • 별도로 작성된 테스트 코드를 실행하여 프로젝트의 런타임 환경의 실 테스트를 진행한다.
  • 빌드 파이프라인(CI/CD) 통합

.jar 파일이란

myFile.jar
│
├── META-INF/
│   └── MANIFEST.MF  # 메타데이터와 JAR 파일의 정보가 포함된 파일
│
├── com/
│   └── example/
│       ├── MyClass.class  # 패키지에 속한 컴파일된 클래스 파일들
│       └── AnotherClass.class
│
├── resources/
│   ├── config.properties  # 리소스 파일
│   └── image.png  # 이미지 파일
│
└── lib/
    ├── library1.jar  # 포함된 라이브러리 파일들
    └── library2.jar


+ 스프링 부트 사용시
│
└──BOOT-INF/
      ├── layers.idx  # 스프링 부트 메타정보
    └── classpath.idx

.jar파일은 프로젝트를 실행하기 위한 최종 단계인데, 아래와 같은 명령어를 통해 생성한 프로젝트 파일을 실행할 수 있다.

java -jar myFile.jar
  • jar파일은 자바 프로젝트를 실행할 수 있도록 만들어진 zip파일 형식의 압축 파일이다.
  • 클래스 파일의 묶음 jar파일은 실행할 수 있는 상태이기에 기본적으로 컴파일 작업이 완료된 .class 파일로 변환되어 있다.
  • jar파일에는 메타데이터가 포함된 META-INF/MANIFEST.MF 파일이 있다. 이 파일에는 jar파일의 메타정보가 들어있다(버전 정보, 메인클래스 등)
    • 메인 클래스를 반드시 설정해야 이후 jar파일이 실행 가능하다.
    • 메인 클래스를 설정하지 않는 경우도 있는데 이 경우는 외부 라이브러리 배포용도로 사용할 때는 실행할 필요가 없기에 설정하지 않는다.

BOOT-INF

META-INF/MANIFEST.MF

Manifest-Version: 1.0
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: org.itech.techsignapi.TechsignApiApplication
Spring-Boot-Version: 2.7.18
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Spring-Boot-Layers-Index: BOOT-INF/layers.idx

아래는 스프링 부트를 사용하면 그래들 빌드 시 같이 넘어오는 정보

BOOT-INF/layers.idx

- "dependencies":
  - "BOOT-INF/lib/"
- "spring-boot-loader":
  - "org/"
- "snapshot-dependencies":
- "application":
  - "BOOT-INF/classes/"
  - "BOOT-INF/classpath.idx"
  - "BOOT-INF/layers.idx"
  - "META-INF/"
  • 스프링 부트 애플리케이션의 레이어드 JAR 구조를 정의하는 파일로, 각 파일이 어떤 레이어에 속하는지를 기록

BOOT-INF/classpath.idx

- "BOOT-INF/lib/spring-cloud-starter-openfeign-3.1.7.jar"
- "BOOT-INF/lib/spring-cloud-openfeign-core-3.1.7.jar"
- "BOOT-INF/lib/spring-cloud-starter-3.1.6.jar"
- "BOOT-INF/lib/jjwt-0.9.1.jar"
- "BOOT-INF/lib/jaxb-api-2.3.1.jar"
- "BOOT-INF/lib/springdoc-openapi-ui-1.6.15.jar"
- "BOOT-INF/lib/gson-2.8.7.jar"
- "BOOT-INF/lib/querydsl-jpa-5.0.0.jar"
- "BOOT-INF/lib/querydsl-apt-5.0.0.jar"
- "BOOT-INF/lib/s3-2.20.42.jar"
- "BOOT-INF/lib/aws-java-sdk-s3-1.12.281.jar"
- "BOOT-INF/lib/ec2-2.16.47.jar"
- "BOOT-INF/lib/aws-xml-protocol-2.20.42.jar"
- "BOOT-INF/lib/aws-query-protocol-2.20.42.jar"
- "BOOT-INF/lib/protocol-core-2.20.42.jar"
- "BOOT-INF/lib/aws-core-2.20.42.jar"
- "BOOT-INF/lib/auth-2.20.42.jar"
- "BOOT-INF/lib/regions-2.20.42.jar"
...
  • 스프링 부트 애플리케이션의 클래스패스 정보를 기록하여, 애플리케이션이 실행될 때 필요한 리소스와 클래스를 정확히 로드할 수 있도록 돕는다.
반응형
반응형

변수

변수하나의 값을 저장하기 위해 확보한 메모리 공간 자체 또는 그 메모리 공간을 식별하기 위해 붙인 이름을 말한다.

식별자

변수를 식별자라고도 부른다, 식별자는 어떤 값을 구별해서 식별할 수 있는 고유한 이름을 말한다.

식별자는 값이 아니라 메모리 주소를 기억하고 있다.

var result = 10 + 20;

위의 코드 동작을 실행했을 때 result는 10 + 20이 저장되어있는 메모리의 주소(0x0000ffff 예시)를 알고있다.

즉 메모리 주소에 붙인 이름을 식별자라고 할 수 있다.

식별자라는 용어는 변수 이름에만 국한해서 사용하지 않고, 변수, 함수, 클래스 등은 모두 식별자라고 부를 수 있다.

메모리 상에 존재하는 어떤 값을 식별할 수 있는 이름은 모두 식별자라고 부른다.

자바스크립트에서 10 + 20을 어떻게 계산하는가?

자바스크립트 엔진이 10+20의 값을 계산하기 위한 동작과정

    • 연산을 수행하기 위해 먼저 + 연산자의 좌변과 우변의 숫자 값, 즉 피연산자를 기억
  • CPU를 활용해 연산하고 메모리를 통해 데이터를 기억
    • 메모리는 데이터를 저장할 수 있는 메모리 셀의 집합체다.
    • 메모리 셀 하나의 크기는 1바이트 즉 1바이트 단위로 데이터를 저장하거나 읽어들인다
  • 각 셀은 0xFFFFFFFF 등과 같은 메모리 주소를 가지며, 메모리 공간의 위치를 나타낸다.
    • 0x00000000 ~ 0xFFFFFFFF
  • 위 예제의 숫자 값 10과 20은 메모리 상의 임의의 위치(메모리 주소)에 기억되고 CPU는 이 값을 읽어들여 연산을 수행한다. 연산 결과로 생성된 숫자 값 30도 메모리 상의 임의의 위치에 저장한다.

ex ) a = 10, b = 20, a + b = 30

각각 임의로 a = 0x0000ffff, b = 0x0000fffc 의 주소가 있다고 했을 때
a + b 의 코드가 실행되면 cpu에서 a의 주소, b의 주소를 참조하여 계산 후 임의의 주소 어딘가에
10 + 20의 결과값 30을 넣어둔다. 이 때 30을 변수에 저장한다면 메모리에서 저장해둔 채로 둘 수 있고,
변수에 저장하지 않는다면 1회성으로 사용 후 찾기 어렵다.

 


변수 선언

변수 선언을 하면, 하드웨어 에서는 값을 저장하기 위한 메모리 공간을 확보하고, 변수 이름과 확보된 메모리 공간의 주소를 연결시켜둔다.

변수를 선언할 때는 var, let, const 키워드를 사용한다 ES6에서 let, const 키워드가 도입되기 이전까지 var 키워드는 자바 스크립트에서 사용할 수 있는 유일한 키워드였다.

Var 키워드의 단점

  • 변수의 스코프 문제
  • 호이스팅(hoisting)
  • 중복 선언 허용

변수 단원이기에 세가지 큰 문제중 하나를 다루자면, 변수의 스코프 문제가 있다.

ES6에서 let과 const 키워드가 도입된 이유

var 키워드는 블록 레벨 스코프를 지원하지 않고 함수 레벨 스코프를 지원하는 것으로 의도치 않게 전역 변수가 선언되어 자주 부작용이 발생했다.

ES6에서 let과 const가 나왔지만 var가 사라지지는 않았는데, 이 이유는 이미 구현된 ES6 이전의 js코드들은 모두 var로 선언되었기 때문에 호환성 등을 위해 남겨두고 폐기시키지 않았다. 하지만 위의 설명과 같은 이유로 권장하지 않기에 사용을 지양

변수 선언과 메모리 관계

변수를 최초 선언 시에는 메모리주소를 정하고 공간을 확보했을 때 따로 넣는 값이 없다면 자바스크립트 에서는 undefined의 값이 해당 메모리 공간에 들어가 있다.

때문에 자바스크립트 엔진에서는 변수 선언을 2단계에 거쳐 수행한다.

  • 선언 단계 : 변수 이름을 등록해서 자바스크립트 엔진에 변수의 존재를 알린다.
  • 초기화 단계 : 값을 저장하기 위한 메모리 공간을 확보하고 암묵적으로 undefined를 할당해 초기화한다.

초기화 단계의 중요성

초기화 단계를 통해 undefined를 할당해 초기화 하는 이유는 초기화 단계를 거치지 않았을 때 확보된 메모리 공간에 이전에 사용했던 값이 남아있을 수 있기 때문.

할당 된 변수 이름들은 자바스크립트 실행 컨텍스트에 등록된다.

실행 컨텍스트

실행 컨텍스트는 자바스크립트 엔진이 소스코드를 평가하고 실행하기 위해 필요한 환경을 제공하고 코드의 실행 결과를 실제로 관리하는 영역으로 실행 컨텍스트를 통해 식별자와 스코프를 관리한다.


변수 선언의 실행 시점과 변수 호이스팅

console.log(score);

var score; // 변수 선언

위의 코드를 실행하면 선언 시점이 score를 사용하는 console.log보다 늦었기에 참조 에러ReferenceError가 발생할 것처럼 보이는데, 자바스크립트에서는 undefined가 출력된다.

  • 변수 선언이 소스코드가 한 줄씩 순차적으로 실행되는 시점, 런타임 시점이 아니라 그 이전 단계에서 먼저 실행되기 때문이다.

자바스크립트 엔진은 소스코드 실행을 위한 준비 단계인 소스코드의 평가 과정에서 모든 선언문(변수 선언문, 함수 선언문 등)을 소스코드에서 찾아내 먼저 실행한다. 평가 과정 이후 변수 선언을 포함한 모든 선언문을 제외하고 소스코드를 한 줄씩 순차적으로 실행한다.

즉 자바스크립트 엔진은 변수 선언이 소스코드의 어디에 있던지 다른 코드보다 먼저 실행된다. 따라서 변수 선언이 소스코드의 어디에 위치하는지와 상관없이 어디서든지 변수를 참조할 수 있다.

이처럼 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트 고유의 특징을 호이스팅(variable hoisting)이라 한다.


값의 할당

변수에 값을 할당(대입, 저장)할 때는 할당 연산자(=)를 사용한다.

var score = 80;

이 때 호이스팅 관련해서 주의할 점이 변수 선언 까지만 호이스팅 되고, 값의 할당은 실제 스크립트를 한줄씩 읽는 런타임 때 할당된다.

console.log(score); // undefined

var score; // 변수 선언
score = 80; // 변수 할당

var score = 80; // 변수 선언 및 할당

console.log(score); // 80

위의 예제에서 두 소스코드 모두 동일하게 undefined, 80으로 결과값이 같다.

  • 메모리 관점
    • 변수 선언과, 값의 할당은 서로 다른 메모리에 저장
    • 즉 score 식별자는 메모리 공간에 할당된 주소만 볼 뿐

console.log(score); // undefined

score = 80;
var score;

console.log(score); // 80

상수

값을 재할당 하지 못하게 하는 것

var score = 80; //변수 선언과 값의 할당
score = 90; //값의 재할당

엄밀하게 말하면 score = 80;의 시점부터 재할당이 된 것이다.
때문에 자바스크립트에서의 상수는 명확하게 말하면 한번 정해지면 변하지 않게 하는 값

ES6에서는 const 키워드를 사용해 상수 선언을 할 수 있다.

출처 : 모던 자바 스크립트 Deep Dive

반응형
반응형

이번에 쿼리문 성능개선 업무를 진행하며 판단한 기준을 정리
MySQL + INNO DB 스토리지 엔진 기준입니다. 다른 데이터베이스는 조금 다를 수 있습니다

//이번 예시인 example_table

CREATE TABLE example_table (
    id INT PRIMARY KEY,
    column1 INT,
    column2 INT,
    grade VARCHAR(255),
    score INT
);

인덱스를 걸지 않고도 성능 개선이 가능한 경우

특정 상황에서는 INDEX가 없어도 쿼리문의 변형만으로 성능개선이 가능하다.

EXPLAIN SELECT * FROM example_table WHERE grade = 'A' AND score = 30;

=>

+----+-------------+---------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table         | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+---------------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | example_table | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  4   | 25.00    | Using where |
+----+-------------+---------------+------------+------+---------------+------+---------+------+------+----------+-------------+

type : ALL
=> Table Full Scan으로 해당 테이블 전체 데이터 스캔
  • ‘AND’ 를 활용한 다중 WHERE절일 때
    • WHERE를 적용했을 때 고정적으로 더 적은 개수의 컬럼이 나오는 조건이 존재하는지
      • 위의 경우 학점과 점수의 연관 관계를 생각했을 때 score의 조건이 grade보다 더 적은 수의 컬럼으로 나오는게 명확
        example_table        
        id column1 column2 grade score
        1 1 1 a 96
        2 1 1 a 97
        3 1 1 a 98
        4 1 1 a 99
        5 1 1 a 99
        6 1 1 b 92
        7 1 1 b 91
        8 1 1 b 90
    • 위의 조건에 맞는 컬럼(score)을 WHERE절의 앞에 위치 시킨다
      SELECT * FROM example_table WHERE score = 30 AND grade = 'A';

⇒ 물론 명확하지 않은 조건일 때는 해당 where절이 반드시 성능 향상을 시킬 것이라고 기대할 수 없으며, 인덱스를 활용하지 못할 때만 사용할 수 있는 방법


인덱스를 활용해 성능 개선

인덱스의 특징

  • 인덱스는 해당 테이블의 빠른 조회를 위한 컬럼의 관련 데이터주소가 일방적으로 담긴 테이블이다
  • 인덱스를 생성했을 때 기본적으로 정렬되어 있기에 이후 트리(B+Tree)탐색을 진행한다.
  • 인덱스는 생성된 테이블에 대하여 기본키에 대한 인덱스가 하나 생성(예외인 경우 간혹 있음)되며, 추가로 인덱스 컬럼을 선정해 걸어둘 수 있다.

인덱스를 사용할 때 주의점

  • 인덱스는 기존의 테이블에서 추가적으로 테이블을 생성하는 것이기 때문에 남발해서는 안됨
    • insert시 부하가 많이 올라감
  • 인덱스가 복합적으로 있을 때는 DB가 자체적으로 판단해 여러 인덱스를 활용해 쿼리의 실행을 돕는다
    • 단, 쿼리의 실행 계획이 반드시 옳고 빠른 실행이 아닐 수 있음
  • 인덱스 기능은 조회 에만 성능 향상 효과를 볼 수 있으며 삽입, 삭제, 갱신 시에 기존의 테이블 수정 + 인덱스 테이블 수정의 상황 때문에 조회 외의 작업도 빈번하게 일어난다면 전반적인 성능이 하락할 수도 있다

단일 컬럼 인덱스

  • 하나의 컬럼만을 선택해 인덱스 테이블 생성
  • 위의 WHERE절에서 이야기했던 것과 동일하게 선택성이 좋은 컬럼을 사용하는 것
    ex) grade가 아닌 score

다중 컬럼 인덱스

  • 조건이 여러개 들어있는 조회문일 경우 단일 컬럼 인덱스보다 좋은 효율을 낼 수 있음
  • 조회시 다중컬럼 인덱스에 없는 컬럼도 조회한다면 인덱스 테이블이 아닌 실제 테이블도 접근해야하기에 고려해서 설정해야 한다.
  • 컬럼 순서가 중요
    • 선택성이 좋은 컬럼으로 구성
    • 범위 조건 컬럼보다 등치(=) 컬럼을 앞으로 구성
  • 한개의 인덱스로 여러 sql을 커버할 수 있도록 설계하면 좋다
  • 모든 조건을 인덱스로 구성하기엔 어려움이 있으니, 조건별로 카운트 해보고 적은 건수의 데이터 위주로 생성이 유리

ETC

쿼리를 실행하며 테스트 할 때는 캐시로 인해 명확한 판단이 힘들어질 수 있으므로 반드시 버퍼 캐시를 비워야함

반응형
반응형

CGI(Common Gateway Interface)

서버 사이드에서 사용자의 프로그램을 동작하도록 하기 위한 프로토콜

CGI는 웹 서버에서 동적인 클라이언트의 요구에 응답하기 위해 만들어졌다.

CGI의 역할 및 흐름

  • 보통의 경우 클라이언트가 HTTP요청을 했을 때 웹 서버에서는 요청이 백엔드 서버까지 들어가서 프로그램을 실행해야 하는지, 정적인 콘텐츠(html, css, js, image 등)만을 Response할지를 정함
  • 로직 처리 프로그램에 넘겨야 한다면 서버에서 CGI프로토콜로 HTTP요청을 변환하여 백엔드 서버로 넘김
  • 백엔드 서버에서 요청을 처리 후 CGI프로토콜로 서버에 Response 반환
  • 상당히 많은 언어가 이러한 프로토콜(FastCGI API)을 지원 ( 파이썬, PHP, JAVA, Node.js 등 )

FastCGI

기존 CGI의 변형된 버전으로 주요 목적은 웹 서버와 CGI 프로그램 간 통신 시 발생되는 부하를 줄이며, 더 많은 요청을 관리할 수 있게 하는 것이다.

FastCGI 특징

  • 여러 웹 서버들이 FastCGI를 구현하며, 다양한 언어로 만들어진 프로그램을 api호출할 수 있다
  • Nginx같은 웹 서버들은 이 FastCGI통신을 하기 위해 게이트웨이 역할(프로토콜 변환)을 포함한다.

WebServer와의 상관관계

아래의 자료와 함께 Nginx Web Server를 예시로 설명을 하자면

  1. 클라이언트가 웹 서버에 보내는 HTTP 또는 HTTPS 요청
  2. Nginx에서 HTTPS요청이였다면 HTTP로 변환 후 FastCGI프로토콜로 다시 담아 FastCGI API를 지원하는 언어에 요청
  3. 애플리케이션 프로그램은 요청된 로직을 처리 후 FastCGI 프로토콜로 Nginx측에 반환
  4. Nginx는 응답받은 FastCGI 데이터를 HTTPS였다면 HTTPS로, HTTP였다면 HTTP로 반환
  5. 통신 종료

정리

  • 위에서 Nginx는 WS의 정적 페이지 반환, 게이트웨이 역할, FastCGI API와 연계하며 WAS의 역할을 모두 수행
  • CGI, FastCGI는 웹 서버에서 사용 언어의 제약없이 통신에 활용할 수 있는 프로토콜
  • 현 시점에서는 CGI가 아닌 FastCGI로 동적 데이터 처리를 주로 하고있음

출처 : https://ko.wikipedia.org/wiki/FastCGI

반응형

'공부 > CS' 카테고리의 다른 글

Context Switching(문맥 교환)  (0) 2024.09.19
해시 - 솔트치기  (0) 2024.08.25
Web Server  (0) 2024.07.22
HTTP통신 과정  (0) 2024.07.01
왜 웹 개발자들은 익스플로러를 싫어하나요?  (0) 2023.04.29
반응형

WebServer

웹 서버의 주요 기능

  • 정적 콘텐츠 제공
    • HTML, CSS, JavaScript, 이미지파일 등과 같은 정적 파일을 클라이언트에게 제공하는 역할
    • 클라이언트가 URL을 통해 요청한 파일을 디스크에서 찾아 클라이언트에게 반환
  • HTTP 요청 처리
    • 웹 서버는 클라이언트로부터 HTTP 요청을 받고 이에 대한 HTTP 응답을 반환
    • GET, POST, PUT, DELETE등 HTTP메서드를 처리
  • 로드 밸런싱
    • 웹 서버는 여러 대의 서버에 요청을 분산시켜 처리 성능을 향상시키고 서버 과부하를 방지
  • SSL/TLS 암호화
    • 웹 서버는 HTTPS를 통해 클라이언트와의 통신을 암호화하여 데이터의 기밀성과 무결성을 보호

Apach (1995)

  • 기본적으로는 요청을 받기위한 프로세스가 생성되어있고, 프로세스를 미리 만들어두는 Prefork 방식을 사용.
  • 새로운 요청이 들어올 때 미리 만들어 둔 프로세스를 가져다 사용하는 방식
  • 만들어진 프로세스를 모두 사용하고 있다면 추가 프로세스를 만들어 할당
  • 첫 도입 때부터 이용자가 상당히 많았지만 절대적인 컴퓨터를 사용하는 유저가 적어 프로세스 생성 형식이 문제가 없었지만, 컴퓨터의 보급이 많이 이뤄진 후에는 그만큼 많은 요청이 들어왔고 결국 C10K (Connection 10,000) 의 문제가 발생
  • 많은 프로세스로 메모리 부족현상 발생

Nginx

  • 트래픽이 많은 웹사이트의 WAS를 돕는 비동기 이벤트 기반 경량화 웹 서버
  • WS의 역할만을 위해 활용되기도 하고, Reverse Proxy Server로 활용해 WAS의 부하를 줄일 수 있는 로드밸런서 역할도 가능
    • Reverse Proxy Server : 클라이언트가 리버스 프록시 서버에 요청하면 프록시 서버가 배후 서버(실제 응답서버)에서 데이터를 가져오는 형태
  • C10K 문제 해결의 핵심은 비동기 이벤트 기반구조
  • Nginx의 구조
    • 멀티 프로세스(master process)
      • master process는 서버의 설정, worker process를 관리하며 워커 프로세스의 생성, 관리 작업을 수행
    • 싱글 스레드(worker process)
      • 생성된 worker process에서는 각각 독립적으로 이벤트 루프 동작이 실행하며 루프의 큰 틀은 아래와 같음
        • 이벤트 감시
        • 이벤트 큐에 추가 : 이벤트 발생 시 이벤트 큐에 추가
        • 이벤트 처리 : 이벤트 큐에서 이벤트를 꺼내 non-blocking 비동기적으로 여러 요청을 수행
        • 반복

Nginx는 여러 프로세스를 사용하기 때문에 수 천개의 동시 연결을 처리할 수 있다.


반응형

'공부 > CS' 카테고리의 다른 글

해시 - 솔트치기  (0) 2024.08.25
CGI, FastCGI  (1) 2024.07.23
HTTP통신 과정  (0) 2024.07.01
왜 웹 개발자들은 익스플로러를 싫어하나요?  (0) 2023.04.29
HTML, CSS, JavaScript가 뭔가요?  (0) 2023.04.12
반응형

자바스크립트 실행 환경

모든 브라우저와 Node.js에서 ECMAScript를 실행할 수 있다.
하지만 ECMAScript 외의 기능들은 호환되지 않는다.

예) 브라우저에서 활용하는 자바스크립트 DOM API

// ECMAScript 코드
const button = document.getElementById('myButton'); // DOM API 사용

// ECMAScript 함수 정의
button.addEventListener('click', () => { // DOM API 사용
  alert('Button clicked!'); // ECMAScript 내장 함수 사용
});

Node.js가 DOM API를 활용하지 않는 이유는 Node.js역할과 관련있다.
Node.js는 브라우저 외부 환경에서 동작하는 백단의 구현을 위한 실행 환경 이기에 Core 언어로 제공하지 않으며, 필요한 경우 라이브러리를 사용해서 HTML문서를 가공할 수 있다.

백단에서 사용하는 우리가 아는 언어들은 모두 파일 시스템을 기본적으로 제공하는 것처럼 Node.js환경에서는 이를 동일하게 제공한다(Javascript범주). 하지만, 브라우저에서는 이런 파일 시스템을 기본 제공하지 않는다.(FileReader로 읽기동작은 가능)

제공하지 않는 이유 : 브라우저에서 동작하는 자바스크립트가 사용자의 파일을 임의로 다룰 수 있다면 사용자의 파일을 보안 공격으로 쉽게 공격할 수 있음

위에서 이야기한 내용을 정리하면

  • 브라우저
    • ECMAScript
    • DOM
    • BOM
    • Canvas
    • XMLHttpRequest
    • fetch
    • ….
  • Node.js
    • ECMAScript
    • 클라이언트 사이드 API(DOM …)를 미지원


Node.js와 NPM

  • Node.js : 크롬 V8 자바스크립트 엔진으로 빌드된 자바스크립트 런타임 환경
    ⇒ 서버 사이드 개발이 가능
  • NPM(Node Package Manager) : 자바스크립트 패키지 매니저로 Node.js에서 사용할 수 있는 모듈을 패키지화해서 모아둔 저장소 역할, 패키지 설치 및 관리를 위한 CLI를 제공
반응형
반응형

자바스크립트의 표준화

자바스크립트가 나온 초창기에는 자사 브라우저의 시장 점유율을 높이기 위해 자사 브라우저에만 동작하는 기능들을 추가했는데, 이로 인해 브라우저에 따라 웹페이지가 정상적으로 동작하지 않는 크로스 브라우징 이슈가 발생

이로 인해 비영리 표준화 기구인 ECMA 인터네셔널에서 자바스크립트 표준화 진행되어, 표준화된 자바스크립트 버전인 ECMAScript의 최초버전인 ES1이 등장

이후 ES2 버전에서는 ISO/IEC 16262 국제 표준과 동일한 규격을 적용하며 계속 버전업 되어왔음

자바스크립트 버전별 변천사

  • ES3(1999) → 정규 표현식, try catch
  • ES5(2009) → HTML5와 함께 등장 JSON, 배열조작(forEach, map 등), map, set
  • ES6(2015) → let/const, 클래스, 화살표 함수, 프로미스
  • ES7(2016) → prototype, **연산자
  • ES8(2017) → async/await, Object 정적 메서드
  • ES9(2018) → for await …of, async generator
  • ES10(2019) → Object.fromEntries, Array.prototype.flatMap
  • ES11(2020) → String. prototype. matchAll, BigInt, 옵셔널 체이닝 연산자 등

자바스크립트 성장의 역사

첫 자바스크립트는 웹 페이지의 보조기능을 수행하는 용도였고, 대부분의 로직이 웹 서버에서 실행되며 서버측에서 렌더링(Server Side Rendering)이 진행됨

Ajax

비동기(Asynchronous )방식으로 데이터를 교환할 수 있는 Ajax가 등장하며 렌더링을 Client측에서 직접 렌더링하기 시작했다

서버 측에서의 렌더링은 매번 데이터를 변경할 때마다 새로 페이지를 불러와 렌더링 해야하기에 성능상 문제가 있었고 사용자 입장에서도 좋지않은 경험이였다.

Ajax의 등장으로 변경되는 데이터만 특정하여 값을 변경해 부드러운 화면 전환 효과를 보게 되었다.

jQuery

jQuery는 자바스크립트의 까다롭고 복잡한 문제를 해결해주었고, 직관적인 jQuery로 인해 자바스크립트 환경이 더욱 개선되었다.

V8 자바스크립트 엔진

구글에서 개발한 V8엔진은 빠른 성능을 보여주었고, 데스크톱 애플리케이션과 유사한 사용자 경험을 제공할 수 있게 되었다.

이 이후 클라이언트 사이드 렌더링이 본격적으로 활성화되었고, 프론트엔드 영역이 주목받기 시작했다.

Node.js

브라우저에서만 동작하는 자바스크립트 엔진을 브라우저 이외의 환경에서도 동작할 수 있도록 브라우저에서 독립시킨 자바스크립트 실행 환경

Node.js가 생기며 프론트, 백 모두 자바스크립트를 사용해 개발할 수 있다는 동형성(동일한환경)이라는 *_장점이 있으며, *_비동기I/O, 단일스레드 이벤트 루프 기반으로 동작하기에 요청 처리성능이 좋으며 실시간으로 데이터를 처리하기 위해 I/O가 빈번하게 발생하는 SPA(Single Page Application)에 적합하다.

SPA 프레임워크

복잡해진 개발환경에서는 이전의 자바스크립트 환경에서는 수행하긴 하지만 변경에 유연해지며 CBD 방법론을 기반으로 하는 SPA(Single Page Application)가 대중화되었다.

SPA를 돕는 프레임워크/라이브러리로는 Angular, Reach, Vue.js, Svelte 등이 있다

자바스크립트와 ECMAScript

각 브라우저 제조사는 ECMAScript 사양을 준수해서 브라우저에 내장되는 자바스크립트 엔진을 구현한다.

자바스크립트는 큰 범주로 JavaScript내에 ECMAScript가 들어있다.

ECMAScript는 자바스크립트에서 구현할 때의 뼈대라고 생각해야 한다. 이 뼈대는 문법과 기본 기능을 정의하며 ECMAScript를 기반으로 브라우저에서 자바스크립트가 동작하는 엔진을 구현하여 자바스크립트 실행 환경을 제공한다.

자바스크립트는 브라우저 엔진을 통해 만들어져 실제 웹 페이지와 상호작용하기 위해 사용되는 프로그래밍 언어이다.

자바스크립트의 특징

  • 웹 브라우저에서 동작하는 유일한 프로그래밍 언어이다.
  • 개발자가 별도의 컴파일 작업을 수행하지 않는 인터프리터 언어이다.
  • 인터프리터 언어의 단점인 실행시간을 최적화하기 위해
    JIT(Just-In-Time) 컴파일 기술이 도입되어 자주 실행되는 코드를 컴파일해둔다
  • 명령형, 함수형, 프로토타입 기반 객체지향 프로그래밍을 지원하는 멀티 패러다임 프로그래밍 언어이다.

스터디 회고

자바스크립트가 ECMAScript를 아우른다는 것 추가설명

이 부분이 다음 주 주제와도 연결이 되다보니 이해가 중요한 부분이였는데, 제가 설명을 자세히 못 적었다고 생각이 드네요..!

다음번엔 이해하기 쉽도록 더 잘 정리해보겠습니다 ㅜㅜ

제가 이해할 때 도움됐던 부분을 조금 더 정보를 꺼내 부연 설명 드리자면, 크롬의 경우 우리가 자주 사용하는 확장프로그램을 예시로 들 수 있습니다.

확장프로그램은 ECMAScript 표준을 기반으로 작성되며, 브라우저 자체에서 제공하는 고유API와 모듈을 사용해 다른 기능과 상호작용합니다.

크롬 자체에서만 지원하는 API를 활용하기에 이는 JavaScript 라고 이해했었습니다!

  if (request.message === 'Tab updated') {
    console.log('Tab was updated');
  }
});

🤔 질문

자바스크립트에 대해 알게 된 좋은 글이었습니다! 감사합니다

올려주신 글 부분에서 Node.js 설명 중 궁금한 부분이 있습니다.
Node.js에서 단일 스레드 이벤트 루프 기반이라는데, 단일 스레드인 경우에는 여러 요청들이 몰리면 성능에 이슈가 생기는 것은 아닌가요?
단일 스레드라 요청 처리 성능이 좋다고 되어 있어서 궁금해져 여쭤봅니다!..

🖥️ 답변

저도 처음에 보고 당황해서 따로 검색을 했었는데, 명확히 이해하지 못했다 생각해 올리지는 않았었습니다..

제가 이해가 가능했던 부분까지 설명을 해보자면, 이벤트 루프에 대한 이해가 필요한데요.

간단하게 한 문장으로 표현하자면, 싱글스레드로 이벤트 루프 기능이 동작하고 있고, 상세 기능은 내부적으로 비동기요청들을 멀티스레드 처리생성해 로직 처리 후 콜백하는 형태입니다.

노드에서는 멀티스레드 처리하는 라이브러리를 libuv라고 부르며, 이 라이브러리가 스레드풀에 멀티스레드를 담은 후 요청들에 대한 처리를 진행 한다고 합니다.

때문에 싱글 스레드로 동작을 모니터링 후 동작 처리는 멀티스레드 라이브러리(libuv)에 비동기 요청으로 맡기는 식으로 동작한다고 합니다.

저도 완전한 이해를 기반으로 답변드리지 못하여 아쉽지만 생각보다 단번에 이해하기 어려워 큰 틀에서만 이해해둔 상태입니다..!

이는 이후 책 진도가 나가면 한번 더 깊게 다루도록 하겠습니다!!

반응형
반응형

우리가 알고있는 HTTP통신은 OSI 7계층의 응용계층에 속한다. 이 통신을 하기 위해 어떤 동작이 이루어지는지 키워드를 갖고 간단하게 다뤄보자

TCP(Transmission Controll Protocol)

TCP는 클라이언트와 서버간의 데이터 통신을 위해 사용하는 4계층에 속하는 프로토콜이다.
HTTP는 기본적으로 통신을 하기 위해 TCP를 활용해 3-way-handshake 과정을 거쳐 TCP 소켓 연결을 하고 데이터를 전송한다.이후 소켓 연결을 해제할 때는 4-way-handshake 과정을 거쳐 연결을 해제한다.

3-way-handshake

TCP통신은 기본적으로 3방향 핸드셰이크라는 방식으로 네트워크 소켓연결을 시도하는데
SYN → SYN-ACK → ACK 의 순서로 진행된다.

  1. SYN(SYNchronize) : 클라이언트가 TCP SYN FLAG패킷을 서버로 송신
  2. SYN-ACK(Synchronize-ACKnowledgement) : 서버는 SYN을 수신해 클라이언트에게 SYN-ACK를 송신
  3. ACK(ACKnowledge) : 클라이언트에서 SYN-ACK를 수신하면 ACK를 서버에 송신

서버는 위의 과정을 거친 최종ACK를 수신받았을 때 TCP 소켓연결이 설정된다.

4-way-handshake

소켓연결 후 소켓을 끊을 때는 4-way-handshake 과정을 진행한다. 여기서 사용하는 주요 FLAG는 FIN, ACK이 있다.

해당 연결해제 절차는 클라이언트 측과 서버 측 모두 사용 가능하기에 A, B로 설명하자면,

  1. A(FIN) → B
  2. B(ACK) → A
  3. B(FIN) → A
  4. A(ACK) → B

위의 순서를 통해 A와 B의 소켓이 해제되는데 최초 (1)FIN요청을 받은 B는 A에게 (2)ACK신호를 줌과 동시에 연결을 끊는 작업을 시행하고 연결을 종료할 수 있는 준비작업이 완료되면 B가 A에게 최종종료되었다는 (3)FIN FLAG를 넘겨준다. 이를 받은 A는 최종확인 (4)ACK FLAG를 B에게 보냄으로써 소켓은 서로 닫히게 된다.

HTTP 통신

HTTP통신으로 호출하기 위한 흐름은 기본적으로 TCP 소켓 연결을 기본으로 소켓연결이 된 후 Request & Response작업이 진행된다.

때문에 순서를 다시한번 적자면 이렇게 된다

3-way-handshake → Request → Response → 4-way-handshake의 순서로 통신 흐름을 이해할 수 있다.

HTTPS 통신

우리가 평소에 사용하는 사이트들은 모두 HTTP가 아닌 HTTPS를 사용하고있다.
HTTPS는 Http에 S(Secure)가 붙어서 보안을 강화시킨 통신방식인데, 이 통신을 시행하면 중간에 데이터를 가로채가도 데이터는 암호화되어있기에 어떤 데이터를 가로챈건지 알 수 없게 되는 보안프로토콜이다.

이 통신방식은 Secure Sockets Layer(SSL)/Transport Layer Security (TLS) 등으로 불리우는 암호화 프로토콜통신(SSL Handshake, TLS Negotiation)을 HTTP통신의 데이터 교환에 앞서서 시행한다.

SSL Handshake, TLS Negotiation

이 통신을 SSL Handshake/TLS Negotiation이라고도 불리며, 기존의 소켓연결인 3-way-handshake 직후에 시행하는 통신작업인데, 순서는 다음과 같다.

  1. Client → ClientHello → Server
  2. Server → ServerHello → Client
  3. Client → PremasterSecret → Server
  4. Server → PremasterSecret(decrypt) → Client
  5. HTTPS 통신가능

위의 과정을 거쳐 진행되고 전체 진행순서를 총 정리하면

3-way-handshake → SSL Handshake/TLS Negotiation(소켓 연결 완료) → Request → Response → 4-way-handshake(연결 해제)

의 순서가 된다.

출처 : https://sh-safer.tistory.com/146

https://velog.io/@dyunge_100/Network-TCPIP-4계층에-대하여

https://velog.io/@tess/아니-왜-http를-써요-tcp를-쓰지

https://brunch.co.kr/@sangjinkang/38

https://sooolog.dev/HTTP-통신과-TCP-통신-그리고-웹-소켓에-대한-기본-개념-정리/

반응형
반응형

BoardsService Test

라라벨 테스트 assert를 통해 테스트코드 작성 실습

테스트코드를 루트\Tests\Unit 폴더에 단위테스트 코드를 작성한 후 아래의 명령어를 터미널에서 실행하면 테스트가 가능

php .\\vendor\\bin\\phpunit

Mocking을 하고싶다면 composer를 통해 설치

composer require --dev mockery/mockery
<?php

namespace Tests\\Unit;

use Tests\\TestCase;
use Illuminate\\Foundation\\Testing\\RefreshDatabase;
use Illuminate\\Support\\Facades\\DB;
use Mockery;
use App\\Http\\Services\\BoardsService;
use stdClass;
use App\\Http\\Dtos\\BoardCommentAddDto;
use App\\Models\\Comment;
use Illuminate\\Database\\Eloquent\\Model;
use Illuminate\\Support\\Collection;

class UserTest extends TestCase
{

    private $boardsService;

    function setUp(): void
    {
        parent::setUp(); // TestCase의 기본 설정을 불러오기
        $this->boardsService = new BoardsService();
    }

    public function testShow()
    {
        $mock = Mockery::mock('Illuminate\\Database\\Query\\Builder');
        $mock->shouldReceive('join')
        ->once()
        ->with('users', 'boards.user_id', '=', 'users.id')
        ->andReturnSelf();  // 메서드 체이닝을 계속하기 위해 자기 자신을 반환

        $mock->shouldReceive('where')
            ->once()
            ->with('boards.id', 1)
            ->andReturnSelf();  // 메서드 체이닝을 계속하기 위해 자기 자신을 반환

        $mock->shouldReceive('select')
            ->once()
            ->with('boards.*', 'users.name as username')
            ->andReturnSelf();  // 메서드 체이닝을 계속하기 위해 자기 자신을 반환

        $mock->shouldReceive('first')
            ->once()
            ->andReturn((object)['id' => 1, 'username' => 'Test User', 'content' => '글입니다']);  // 가짜 데이터 반환

        DB::shouldReceive('table')
            ->once()
            ->with('boards')
            ->andReturn($mock);

        $result = $this->boardsService->show(1);

        // 결과 검증
        $this->assertInstanceOf(stdClass::class, $result);
        $this->assertEquals(1, $result->id);
        $this->assertEquals('Test User', $result->username);
        $this->assertEquals('글입니다', $result->content);
    }

    public function testCommentAdd()
    {
        $boardCommentAddDto = new BoardCommentAddDto('내용', 1);

        $comment = $this->boardsService->commentAdd($boardCommentAddDto);
        
        $mockComment = new Comment;
        $mockComment->content = '내용';
        $mockComment->board_id = 1;

        $this->assertInstanceOf(Model::class, $comment);
        $this->assertEquals($comment->content, $mockComment->content);
        $this->assertEquals($comment->board_id, $mockComment->board_id);
    }

    public function testCommentsShow()
    {
        $mock = Mockery::mock('Illuminate\\Database\\Query\\Builder');

        $mock->shouldReceive('where')
            ->once()
            ->with('board_id', 1)
            ->andReturnSelf();

        $mock->shouldReceive('select')
            ->once()
            ->with('comments.*')
            ->andReturnSelf();

        $mock->shouldReceive('get')
        ->once()
        ->andReturn(collect([
            (object)['id' => 1, 'content' => '내용', 'board_id' => 1],
            (object)['id' => 2, 'content' => '내용2', 'board_id' => 1]
        ]));

        DB::shouldReceive('table')
            ->once()
            ->with('comments')
            ->andReturn($mock);

        $result = $this->boardsService->commentsShow(1);
        
        $this->assertInstanceOf(Collection::class, $result);
        // $this->assertIsArray($result);
        $this->assertEquals(1, $result[0]->id);
        $this->assertEquals(2, $result[1]->id);
        $this->assertEquals('내용', $result[0]->content);
        $this->assertEquals('내용2', $result[1]->content);
        $this->assertEquals(1, $result[0]->board_id);
        $this->assertEquals(1, $result[1]->board_id);
    }
}

한 메서드안의 assert갯수가 assertions 메서드 각각 tests에 포함

반응형

'공부 > Laravel' 카테고리의 다른 글

Pusher를 활용한 Laravel5.8 + Javascript 소켓통신  (0) 2024.06.20

+ Recent posts