회원가입과 로그인 시, 이메일과 비밀번호 유효성 검사를 한 다음 유효하지 않은 문자열을 입력시 사용자에게 경고 메시지를 주고자 했다.
이메일은 @와 . 기호가 포함되어 있는지 여부를 기준으로 정규표현식을 사용하여 검사하는 함수를 작성했고, 비밀번호는 8~20자 사이의 영문자와 숫자를 최소 1개 이상 포함하는 경우 유효한 비밀번호라는 기준을 세웠다.
// It checks for a sequence of characters that starts with one or more non-whitespace characters ([^\s@]+),
// followed by an @ symbol, followed by one or more non-whitespace characters for the domain name ([^\s@]+),
// then a . character, and finally one or more non-whitespace characters for the top-level domain ([^\s@]+).
export const checkEmail = (email: string) => {
const regexp = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regexp.test(email);
};
export const checkPassword = (password: string) => {
const regexp = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,20}$/;
return regexp.test(password);
};
checkEmail 과 checkPassword 함수가 내 의도대로 동작하는지를 알아보기 위해 jest 로 테스트 코드를 작성해보고자 했다. CRA 프로젝트였기 때문에 jest가 기본으로 설치되어 있었다.
jest는 __test__ 라는 폴더 아래에 있는 js(x), ts(x) 파일 또는 .spec.js(x) 또는 .test.js(x) 파일을 찾아낸다(.ts(x)도 마찬가지이다).
testMatch [array<string>]
(default: [ "**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)" ])
출처: https://jestjs.io/docs/configuration/#testmatch-arraystring
그리고 Given-When-Then 패턴에 따라 다음과 같은 첫번째 테스트 코드를 작성했다.
test('should $1', () => {
// Given
const data = $4
// When
const result = $3
// Then
expect(result).toEqual($2)
})
1. 테스트 케이스의 목적을 작성한다.
2. 최종적으로 확인해야하는 부분, 즉 예상하는 값을 작성한다.
3. 어떠한 flow를 검증할지 작성한다.
4. 3번 검증을 위해 어떤 가정이 필요한지 작성한다.
import { describe, test, expect } from '@jest/globals';
import { checkEmail, checkPassword } from '../util/authorization/checkPassword';
test('@가 없는 이메일 주소는 유효하지 않은 이메일 주소입니다.', () => {
const email = 'kimcoding.com';
const result = checkEmail(email);
expect(result).toBe(false);
});
package.json의 script를 다음과 같이 수정하고 npm test를 돌려봤다. (스크립트를 수정하지 않고 npx jest 명령어를 입력해도 똑같이 동작한다.)
"scripts": {
"test": "jest"
}
그러자 곧바로 다음과 같은 에러가 떴다.
에러 메시지:
Jest encountered an unexpected token
SyntaxError: Cannot use import statement outside a module
테스트 코드 파일에서 import 구문을 사용하는 부분이 문제였고, typescript를 사용하고 있었기에 안내해준 링크에 들어가 @babel/preset-typescript 를 devDependencies 로 설치하고, 안내해준 대로 아래와 같이 babel.config.js를 작성하자 테스트가 작동했다.
module.exports = {
presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript']
};
하지만 babel.config.js 에서 다음과 같은 eslint 에러가 떴다.
에러 메시지:
'module' is not defined. eslint no-undef
eslint의 공식 문서를 보니 주석을 통해 환경 지정을 해줄 수 있다는 걸 알게 되었다.
To specify environments with a comment inside of a JavaScript file, use the following format:
/* eslint-env node, mocha */
This enables Node.js and Mocha environments.
출처: https://eslint.org/docs/latest/use/configure/language-options#using-configuration-comments
babel.config.js 파일 상단에 다음과 같이 주석 한 줄을 추가하니 eslint 에러가 사라졌다.
/* eslint-env node */
module.exports = {
presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript']
};
이렇게 eslint 에러를 해결하고 첫번째 테스트 작동을 확인하고 난 다음, 최종적으로 이메일과 비밀번호의 유효성 검사를 확인할 수 있는 테스트 코드를 다음과 같이 작성했다.
그리고 이제 다 됐나 싶어 테스트를 돌리자 예상치 못한 난관이 또 등장했다. 마지막 테스트가 통과가 안 된다. 🥹
비밀번호 유효성 검사 시, 특수문자의 여부와 상관없이 동작하는 걸 의도했는데 특수문자가 있으면 checkPassword 함수가 무조건 false 를 리턴하고 있다는 걸 알게 되었다. checkPassword의 정규식 패턴에서 [^\W_] 를 추가하여 특수문자를 제외하도록 했다.
// 수정 전
const regexp = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,20}$/;
// 수정 후
const regexp = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z0-9^\W_]{8,20}$/;
그리고 다시 테스트를 돌리자 10개의 테스트를 모두 통과했다.
이렇게 간단한 테스트 코드를 하나 작성해 보는데도 우여곡절이 늘 차고 넘친다. 새로운 걸 시도할 때면 항상 에러를 맞고, 그 에러를 해결해 나가는 게 이제 점점 당연한 일처럼 느껴진다. 조금의 과장을 보태서 '단 한 번도' 내 뜻대로 모든 것이 순탄하게 흘러간 적이 없었던 것 같다. 이 과정 자체가 개발의 일부라는 생각이 든다.
코드 구현과 화면 구성을 다 한 상태에서 테스트 코드를 작성하는 게 TDD 와는 거리가 멀어서 무슨 의미가 있나 싶은 생각도 들었는데, 정규식 패턴 자체에 문제가 있었다는 점을 이렇게 뒤늦게 알았다는 것이 또 하나의 소득이다 🥲
간단한 유틸 함수의 단위 테스트 외에 프론트엔드에서의 테스트 코드는 어때야 하는지 좀 더 깊이 알아보고 싶다. 리팩토링에도 끄떡없는 견고한 코드를 작성하고 싶다. 그전에 먼저.. 이제 코드를 정돈해서 pr을 올릴 시간이다.
📚 참고자료
'돌멩이 하나 > 에러는 미래의 연봉' 카테고리의 다른 글
styled-components에서 custom props 사용하기 (0) | 2023.07.28 |
---|---|
[기능 구현 챌린지] 무한 스크롤 UI (0) | 2023.05.30 |
typescript 에러 모음 (0) | 2023.04.04 |
netlify 자동 배포 설정 오류 해결 (0) | 2023.02.14 |
코드에 GitHub access token이 포함된 상태로 commit을 하면 어떻게 될까 (0) | 2023.01.31 |