상세 컨텐츠

본문 제목

[Node.js] 심화 실습 (11-13) (Jest / basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") / 정규표현식 / 테스트코드)

notes

by 서울의볼 2024. 2. 19. 23:14

본문

테스트코드에 대해 배움: 개발한 코드가 여러분이 의도한대로 동작하는지 작성하는 코드 (어떠한 언어에 얽매이지 않고 개념적으로 표현하기도 함). 종류는 다음과 같음:

  • 단위 테스트 (Unit Test): 가장 작은 규모의 기능을 테스트
  • 통합 테스트 (Integration Test): 다양한 기능을 합쳤을때 생기는 문제를 방지하기 위한 테스트
  • E2E 테스트 (End-to-end Test): 끝에서 끝(종단 간)을 의미하는 End to end 테스트
  • 이외에도 돌연변이 테스트와 같은 다양한 기법들로 자신들의 서비스가 더욱 치밀하고 탄탄한 서비스로 거듭날 수 있도록 테스트를 진행함.
    • 번외) 넷플릭스는 자신들의 서버가 아무때나 무작위로 다운되도록 해서 자신들의 인프라에 약점이 있는지 찾아내는것을 도와주는 "카오스 몽키"라는 솔루션을 개발해서 사용했다 함 --> 언제든 서버가 죽더라도 최대한 빠르게 자동으로 서버를 재구성하는 환경으로 운영할 수 있음.

나는 Jest를 배웠음.

새로운 파일을 하나 만들어 실습을 해봤음. yarn add -D jest로 jest를 설치하고 하기의 코드를 package.json에 입력해야 실행됨:

"scripts": {
    "test": "node --experimental-vm-modules node_modules/.bin/jest"
  },

일반적인 관례로 테스트할파일이름.spec.js 와 같은 형식으로 파일을 만든다함. jest 또한 위 형식의 이름을 가진 파일을 읽어들여 테스트 코드를 실행하는게 기본 설정이라 함.

 

테스트 할 간단한 함수는 validation.js 파일 내 만듦:

export const isEmail = (value) => {
  // value가 이메일 형식에 맞으면 true, 형식에 맞지 않으면 false를 return 하도록 구현해보세요
  return false;
};

그리고 아래의 단위 테스트 코드로 validation.spec.js 파일을 만들어 테스트를 해봄 (yarn test).

import { isEmail } from './validation';

test('테스트가 성공하는 상황', () => {
  expect(isEmail('이메일이 아니에요')).toEqual(false);
});

test('테스트가 실패하는 상황', () => {
  expect(isEmail('my-email@domain.com')).toEqual(true);
});

여기서 test()는 단위 테스트를 묶어주는 함수이고, expect()는 특정 값이 만족되는지(정상적인지) 확인하기 위한 표현식을 작성할 수 있게 해주는 함수임.

그리고, 요구사항 1개마다 test함수를 1개씩 만들고, 그 안에 테스트 코드를 작성하면 됨.

 

아 근데, 윈도우에선 계속 말도 안되는 문제가 계속 발생해서 맥에어로 갈아탐...

해당 파일에 가서 ")" 괄호도 추가해보고 뭐 이것저것 해봤는데, 구글링해도 안나와서 포기함...

아무튼 실행하면 아래와 같은 그림이 나와야 정상임:

이후 임의로 정해준 요구사항마다 test함수를 1개씩 만들고, 그 안에 테스트 코드를 validation.spec.js에 작성함:

import { isEmail } from './validation';

test('입력한 이메일 주소에는 "@" 문자가 1개만 있어야 이메일 형식이다.', () => {
  expect(isEmail("my-email@domain.com")).toEqual(true); // 1개만 있는 상황
  expect(isEmail("my-email@@@@domain.com")).toEqual(false); // 여러개 있는 상황
  expect(isEmail("my-emaildomain.com")).toEqual(false); // 하나도 없는 상황
});

test("입력한 이메일 주소에 공백(스페이스)이 존재하면 이메일 형식이 아니다.", () => {
  expect(isEmail("myemail@domain.com")).toEqual(true);
  expect(isEmail("my email@domain.com")).toEqual(false);
});

test("입력한 이메일 주소 맨 앞에 하이픈(-)이 있으면 이메일 형식이 아니다.", () => {
  expect(isEmail("e-m-a-i-l@domain.com")).toEqual(true);
  expect(isEmail("-email@domain.com")).toEqual(false);
});

isEmail함수를 디버깅하여 테스트코드가 통과하도록 함:

// validation.js

export const isEmail = (value) => {
  const email = (value || '');

  if (email.split('@').length !== 2) {
      return false;
  } else if (email.includes(' ')) {
      return false;
  } else if (email[0] === '-') {
      return false;
  }

  return true;
};

yarn test를 실행하게 되면 ...

더 나아가 추가 요구사항을 구현함:

  • "입력한 이메일 주소중, 로컬 파트(골뱅이 기준 앞부분)에는 영문 대소문자숫자, 특수문자덧셈기호(+), 하이픈(-), 언더바(_) 3개 외에 다른 값이 존재하면 이메일 형식이 아니다."
  • " 입력한 이메일 주소중, 도메인(골뱅이 기준 뒷부분)에는 영문 대소문자숫자, 점(.), 하이픈(-) 외에 다른 값이 존재하면 이메일 형식이 아니다."

추가 작성한 테스트코드:

// validation.spec.js

...
test("입력한 이메일 주소중, 로컬 파트(골뱅이 기준 앞부분)에는 영문 대소문자와 숫자, 특수문자는 덧셈기호(+), 하이픈(-), 언더바(_) 3개 외에 다른 값이 존재하면 이메일 형식이 아니다.", () => {
  expect(isEmail('_good-Email+test99@domain.com')).toEqual(true);
  expect(isEmail('my$bad-Email9999@domain.com')).toEqual(false);
});

test("입력한 이메일 주소중, 도메인(골뱅이 기준 뒷부분)에는 영문 대소문자와 숫자, 하이픈(-) 외에 다른 값이 존재하면 이메일 형식이 아니다.", () => {
  expect(isEmail('my-email@my-Domain99.com')).toEqual(true);
  expect(isEmail('my-email@my_Domain99.com')).toEqual(false);
  expect(isEmail('my-email@my$Domain99.com')).toEqual(false);
});

 

위의 추가 요구사항에 따른 isEmail함수 디버깅:

// validation.js

export const isEmail = (value) => {
  const email = value || "";
  const [localPart, domain, ...etc] = email.split("@");

  if (!localPart || !domain || etc.length) {
    return false;
  } else if (email.includes(" ")) {
    return false;
  } else if (email[0] === "-") {
    return false;
  }

  for (const word of localPart.toLowerCase().split("")) {
    if (!["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9","-","+","_"].includes(word)) {
      return false;
    }
  }

  for (const word of domain.toLowerCase().split("")) {
    if (!["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9","-", "."].includes(word)) {
      return false;
    }
  }

  return true;
};

그럼, 테스트 모두 성공함..!

 

여기서 정규표현식 (전에 코드카타하며 관련 글을 쓴 적이 있음)의 위대함이 나옴. 정규표현식을 아래와 같이 쓰면 코드가 상당히 간결해짐 (향후 여러 서비스를 크롤링할 때 상당히 유용하다 함):

export const isEmail = (value) => {
  const email = value || "";
  const [localPart, domain, ...etc] = email.split("@");

  if (!localPart || !domain || etc.length) {
    return false;
  } else if (email.includes(" ")) {
    return false;
  } else if (email[0] === "-") {
    return false;
  } else if (!/^[a-z0-9+_-]+$/gi.test(localPart)) {
    return false;
  } else if (!/^[a-z0-9.-]+$/gi.test(domain)) {
    return false;
  }

  return true;
};

관련글 더보기