간단한 Lotto 구매 및 당첨확인하는 로직을 콘솔기반으로 했다가 HTML, CSS을 더해서 웹기반으로 변경하는 작업까지 통틀어서 피드백 받았던거를 적어보려고 합니당
웹팩 설정
일단 당연히 콘솔기반에서 웹기반으로 옮기려고 하다보면 webpack을 통해 dev를 열기위해 웹팩 설정이 필요했습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
mode: "development",
resolve: {
extensions: [".js", ".mjs"],
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
},
],
},
devtool: "inline-source-map",
devServer: {
port: 9000,
},
entry: "./src/step2-index.js",
output: {
filename: "step2-bundle.js",
path: path.resolve(__dirname, "dist"),
},
module: {
rules: [
...baseConfig.module.rules,
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: "./index.html",
}),
],
};
|
cs |
mode: 웹 팩 빌드할때 모드를 설정하는 옵션인데, 'developement', 'production' 아니면 'none'으로 설정할 수 있고, none을 설정하게 되면 기본 최적화 옵션에서 제외된다.
resolve: 이 옵션은 웹팩이 모듈을 어떻게 해석할지에 대한 설정을 해주는 곳이다. 즉 import해올때, .js, .mjs를 생략해도 되는 것이고, 리액트에 경우는 jsx, tsx를 더해주는 게 좋을 것 같다.
module: 이 옵션은 웹팩이 다른 타입의 모듈들을 어떻게 다뤄야할지 지정해주는 것이다. 여기 옵션에서는 .js 확장자 파일에 관련해서, node_modules안에 있는 파일들은 제외하고, babel-loader를 통해 preset으로 설정된 @babel/preset-env를 통해 자바스크립트 코드를 트랜스파일해준다. 여기서 @babel/reset-env는 최신 자바스크립트 문법과 기능을 기존의 브라우저와 환경에서 호환성을 유지시켜줍니다. 그리고 보통은 리액트 프로젝트에서는 style-loader와 css-loader가 필수적으로 장착이되는데 이유는 런타임 동안 CSS 스타일을 돔에다가 삽입을 해주기 위함입니다. 자바스크립트 코드에서 CSS 파일을 가져오면 스타일 로더가 <style> 태그를 생성하고 문서의 <head>에 추가하여 페이지에 CSS를 추가하는 작업을 처리합니다. 이를 통해 React 컴포넌트에서 CSS 모듈 및 기타 CSS-in-JS 솔루션을 사용할 수 있습니다. 스타일 로더가 없으면 HTML 파일에 CSS를 수동으로 추가해야 하므로 번거롭고 오류가 발생하기 쉽습니다.
devtool: 이 옵션은 생성할 소스 맵의 유형을 지정합니다. 이 경우 "inline-source-map"으로 설정되어 있으며, 이는 소스 맵이 번들 파일에 포함됨을 의미합니다. 그리고 여기서 Source-map이라는 것은 원본 소스 코드를 최종 생성된 코드에 매핑하는 파일입니다. 이를 통해 개발자는 생성된 코드 대신 원본 소스 코드를 디버깅할 수 있습니다. 소스 맵 파일은 빌드 프로세스 중에 생성되며 브라우저의 개발자 도구에서 생성된 코드를 원본 소스 코드에 다시 매핑하는 데 사용됩니다.
entry: 어플리케이션의 시작 파일의 위치를 넣는 곳이죠.
output: This option specifies the output configuration for the bundle file. In this case, it specifies the name of the output file as "step2-bundle.js" and the output path as "dist". 빌드파일 결과물에 대한 옵션들을 명시하는 곳인데, 파일이름과, 저장할 경로, 그리고 publicPath, chunkFIlename등을 더 추가로 지정할 수 있는데 publicPath는 이미지나 글꼴과 같이 런타임에 동적으로 로드되는 Asset의 URL을 생성하는 데 사용됩니다. 그리고 chunkFIlename은 청크 이름음과 해시에 대한 플레이스 홀더를 추가할 수 잇죵. ex) [name].[chunkhash].js
plugins: This option specifies the plugins that should be used by webpack. In this case, it specifies two plugins: CleanWebpackPlugin and HtmlWebpackPlugin. CleanWebpackPlugin is used to clean the output directory before each build, and HtmlWebpackPlugin is used to generate an HTML file that includes the bundle file.
이 옵션은 웹팩에서 사용해야 하는 플러그인을 지정합니다. CleanWebpackPlugin은 각 빌드 전에 출력 디렉터리를 정리하는 데 사용되며, HtmlWebpackPlugin은 번들 파일을 포함하는 HTML 파일을 생성하는 데 사용됩니다.
이벤트 바인딩
처음에는 일단 무식하게 요소 하나하나 가져와서 맞는 callfn과 event를 addEventListener를 통해 적용했었다.
이런 피드백을 받았었고, 이벤트 위임 ( Event Delegation ) ? 얼핏 들어보았는데 또 오르지 않았던 그 이름이다.
https://ko.javascript.info/event-delegation
캡처링과 버블링을 활용하면 강력한 이벤트 핸들링 패턴인 이벤트 위임(event delegation) 을 구현할 수 있습니다.
이벤트 위임은 비슷한 방식으로 여러 요소를 다뤄야 할 때 사용됩니다. 이벤트 위임을 사용하면 요소마다 핸들러를 할당하지 않고, 요소의 공통 조상에 이벤트 핸들러를 단 하나만 할당해도 여러 요소를 한꺼번에 다룰 수 있습니다.
공통 조상에 할당한 핸들러에서 event.target을 이용하면 실제 어디서 이벤트가 발생했는지 알 수 있습니다. 이를 이용해 이벤트를 핸들링합니다.
이렇게 나온다. 그래서 이를 어떻게 적용할 수 있을까 생각을 해보았다. 그래서 이 Lotto 로직에서 사용되는 이벤트는 총 4개다.
Click, Submit, Input, Change 이렇게 있고, 이를 부모인 app에 해당 이벤트를 다 적용을 해준다.
이렇게 되면 이제 App 내 어디서든 저 네가지중에 하나라도 이벤트가 발동되면 저 콜백함수들이 각각 호출되게 된다. Click같은 경우는 어디를 클릭하더라도, onClick발생되는 사이드이펙트가 있지만 이는 어떻게 해결할까?
나는 extranctDataFromEvent라는 메소드를 만들어서 event에서 해당요소의 ID를 가져온다. 애초에 HTML에 넣어준 data-set이나 ID를 뽑아준다고 보면 된다. 그러므로 해당이 안되는 Element들이 버블링 혹은 캡쳐링을 통해서 이벤트가 발동하더라도. Early Return으로 빠져나오기때문에 괜찮아지는거고, 그리고 switch문을 통해 원하는 요소에 원하는 이벤트를 넣어주도록 리팩토링을 했습니다. 그리고 문제가 하나 있었는데, 버튼안에 svg와 path가 있었는데 이 요소들이 자꾸 클릭되는 문제가 있었다. 이럴 경우에는 CSS처리가 가능했다.
클릭자체를 막기위해서는 해당 태크에 `cursor-pointer: none;`을 줌으로써 조건문으로 굳이 걸러줄 필요가 없었다.
VIEW
처음에 콘솔에서 Web기반으로 옮겼을때 엘린먼트 스타일이나 컨텐츠를 어떻게 조작을 할까하다가 무식하다 다 때려박았ㄷ..
그리고 저 LottoOutput이라는 클래스안에서 모든 조작활동이 다 일어나서 너무 나도 많은 코드가 밀집되어 있었달가...
바로 피드백으로 뚜두려 맞고...
애초에 밀집되어 있던것을 인지는 하고 있었는데, 어떻게 나눌지 고민하다가 아토믹하게 조작을 해보라는 피드백을 받아 컴포넌트 처럼 나눠서 적용을 해보았다.
리액트 컴포넌트 처럼 따로 나누고, 각 엘리먼트에 대한 스타일과 액션은 그 안에서 수행함으로 Controller가 알아야하는 뷰 자체는 적어졌다. 이제 LottoOutput자체 내에서 메소드는 많이 적어졌으니까(?)
CYPRESS를 이용한 E2E 테스트
일단 jest로 진행했던 unit테스트와 크게 다르지 않은데 큰 차이점은 Jest는 콘솔로 나오지만, Cypress는 따로 크롬이나 application으로 창이 열려서 확인 할 수 있다. 그리고 문서가 굉장히 잘 되어있었다. 완전 처음부터 알려줘서 편했달까...
https://docs.cypress.io/guides/getting-started/installing-cypress
cypress를 설치한 후, 실행을 시킨다음에 create new empty spec을 하게되면 IDE에 기본적으로 필요한 파일과 시작 테스트 파일이 자동적으로 생성된다.
그 외에는 얼추 Jest와 비슷하지만 일단 첫번째로는 port가 열려있어야 cypress를 테스트할 수 있다 그렇기 때문에 본인 프로젝트를 켜 놓아야하고, 약간 국룰로 다가 모든 테스트들의 beforeEach로 해당 localHost로 들어가는 메소드를 추가할 수 있다.
그리고 요소는 어떻게 가져오냐면 cy.get을 통해서 가져올 수 있고, 다양한 방법을 통해 가져올 수 있다.
1. CSS Selector
2. ID
3. data-set 혹은 클래스 네임
이 될 수 있는데 나는 테스트를 위해서 필요한 요소들에 이미 data-set 이름들이 존재했기때문에 그를 이용해서 진행하였다.
단순한 로직이기 때문에 설명하기보다는 내가 사용했던 모든 cypress 메소드들을 적어보겠다.
cy.visit(): 브라우저 URL로 갑니다.
cy.get(): 위에서 설명했던 몇가지 방법으로 page에서 요소를 가져옵니다.
cy.should(): 클래스를 가졌는지 요소가 존재하는 지 같은 요소가 가져야 할 것을 정의합니다.
cy.type(): 인풋에 입력값을 삽입합니다.
cy.then(): 앞서 커맨드에 대한 결과물에 대한 액션수행합니다.
cy.wrap(): 명령의 연쇄를 활성화하기 위해 객체를 래핑합니다.
cy.clear(): 인풋에 값을 초기화합니다.
cy.each(): 엘리먼트 콜렉션을 순회하면서 특정 행위를 수행합니다.
expect(): 특정 값에 대한 기대값을 정의합니다.
등 별거 없었고 생각보다 쉬웠는데 간단한 애플리케이션이라 딱히 더 찾아볼 필요는 없었다.
간단한 작업들이였지만 생각할게 너무 많다고 생각하고 아직 모르는 것도 너무 많은 것을 계속 느끼게 된다. 구조적으로 생각하고 코드를 짜야하는데 항상 먼저 손가락이 먼저 나가는 것 같고 설계부터 잘 해보자라는 생각이 많이 들었고,, E2E를 먼저 구축해놓고 리팩토링하니까 확실히 놓친부분이 바로 잡혀서 진짜 다음 프로젝트에 꼭 넣어보고 싶다는 생각을 하게 되었다.
'Study > TDD' 카테고리의 다른 글
TDD, Clean Code - first cycle (0) | 2023.08.13 |
---|