안녕하세요 깍돌이입니다.

오늘은 Express.js의 에러 핸들러 처리에 대해서 작성하려고 합니다.

사실 기존에는 관련 내용 작성이 안되어있었는데 사실 검색하면 워낙 많이들 나오는 내용이라 작성하지 않았습니다.

기본적인것들로 블로그 포스팅을 다 채우고 싶지 않았습니다. ( 조회수는 많이 늘어날텐데 ㅎㅎ;;)

초창기에 기본적인거 쓴건 히스토리용으로 냅두고 있습니다.

 

그외에는 작업하면서 경험한것들 잊어 버리지 않기 위해서 작성하고 있습니다.

 

그럼 금일은 뭐때문에 작성하게 되었냐면 Express.js의 ERROR 처리를 위한 Middleware처리는 2개의 방법이 있습니다.

 

ERROR Middleware 500 

기본적인 500 에러입니다. express.js의 서버에서 오류가 났을 경우 흔히들 볼수있는 Internal Server Error 오류입니다.

그래서 express.js에서는 해당 에러를 처리하기 위해서

Express Router Controller안에 있는 next라는 함수를 통해서 해당 next에 error객체를 넣어서 넘기고있습니다. 

Express의 error객체를 next에 넘겨 버리게 되면 기본적인 Middleware패턴에서 해당 에러 객체를 가진 함수로 넘어가게 됩니다.

 

 

app.ts 의 구현부는 아래와 같습니다.

 

 

ERROR Middleware 404 

기본적으로 Express는 middleware 패턴이라는 것을 알고 있다는 것을 감안하고 작성하겠습니다.

 

위의 Express Router 안에서 예외처리를 하는 catch 문에서 next(error) 를 사용을 했다는 것은 기본적으로 Express에서 선언된 Router가 매치가 됐다는 뜻입니다.

 

예를들어

http://localhost:5555/testRouter

http://localhost:5555/testNotExist

로 요청시에

 

Express에 router.get('/testRouter') 만 있다면 아래의 testNotExist는 매치가 되지 않습니다. 

그렇기 때문에 결국 Express에서 선언되는 Middleware중 매치되는 것을 찾지 못하고 가장 마지막 Middleware로 가게되는데요

 

 

그래서 가장 마지막 생성된 함수의 Middleware 함수로 넘어가게되고 해당 함수에서는 매치된 값을 찾지 못하였다는 에러 메시지를 res객체를 통해 반환하는 함수가 됩니다.

그래서 404 와 그 외 내부 예외처리를 통한 500 에러 크게 2가지로 나눌 수 있습니다.

 

이슈상황

그래서 이슈상황이 뭐냐! 네 위에 설명과 다르게 동작하는 경우 입니다.

 

네 맞습니다. next에 error 객체를 넣어서 넘겼을 경우 Middleware (500) 에러 함수(ERROR 객체가 선언된 함수 ) 로 넘어가져야 합니다. 

그런데 위의 상황에서는 404 ( 라우트가 매치 되지않았을 경우 ) 로 넘어가게 됩니다.

 

물론 둘다 오류의 상황은 맞기 때문에 상관없지 않나? 라고 생각 할수 있습니다.

 

하지만 개발하였을때 예상하지 못한 동작의 흐름에 생겼을 경우는 코드를 작성한 사람의 문제일 확률이 매우 높고 앞으로도 제대로 알지 못하는 케이스가 발생가능하기 때문에 해당 원인을 찾아보고자 했습니다.

 

 

Express.js 홈페이지

500 에러 함수 설명

라우트 맨 마지막에 선언 하여야 하며 -> OK

err 에 대한 인수가 있어야합니다. -> OK 

 

 

"next() 함수로 어떠한 내용을 전달하는 경우('route'라는 문자열 제외), Express는 현재의 요청에 오류가 있는 것으로 간주하며, 오류 처리와 관련되지 않은 나머지 라우팅 및 미들웨어 함수를 건너뜁니다. 이러한 오류를 어떻게든 처리하기 원하는 경우, 다음 섹션에 설명된 것과 같이 오류 처리 라우트를 작성해야 합니다."

 

-> 조금 잘못알고 있었네요 next 함수로 어떠한 내용 전달시 요청 오류로 간주하고 나머지 라우팅 건너 뜀 ( 여튼 OK )

 

제가 사용하고있는 형태는 기본 오류 핸들러 ( 404가 아닌 모든 오류를 받아서 처리함 ) 로 보이는데 특이사항은 없어 보입니다.

 

그럼 원인은 하나일거같습니다. next로 error를 넣지 않는건가?

 

에러가 발생한 로직입니다.

HTTP 요청 중 URL 오류가 발생해서 해당 axios.get  부분에서 catch 로 빠진 부분인데요  실제로 에러 객체를 reject하였고

 

하나씩 모든 에러를 따라가다보니

 

중간에 reject를 error가 아닌 error.message 

ERROR객체가 아닌 string으로 한 부분이 있었습니다... ㅜ

황급히 수정시  

 

역시 코드를 거짓말을 하지 않습니다...

 

사실 잠깐 그냥 봤을때는 원인이 잘 안보였는데 포스팅 을 하면서 한땀한땀 찾아가다보니 복병을 발견했네요.. 

 

다시 이런일 없기를 바라면서 히스토리로 남겨놓겠습니다.

 

감사합니다.

 

 

 

 

안녕하세요 깍돌이 입니다. 오늘은  간단한 트러블 슈팅 입니다.

폐쇠망 서버에 Node.js Agent를 심어야 하는 경우가 있는데 해당 서버에 새롭게 설치를 하려고하면

Node.js 설치 파일 및 Agent 코드를 심어 놓습니다. ( node_modules 포함 ) 

 

기존에 서버들은 노드 버전이 13 14 버전을 사용했는데 이번에 새로 하는 노드로 설치시 내부 의존성으로 설치하여 실행하던 PM2 명령어들이 먹지 않는 현상이 발생했습니다.

 

결론은 맨밑에 있습니다.

Node.js 버전

[root@private-inter-comm-send transaction-test]# node -v
v16.14.0
[root@private-inter-comm-send transaction-test]# npm -v
8.3.1
[root@private-inter-comm-send transaction-test]#

package.json

"scripts": {
    "dev": "nodemon app.js",
    "start": "pm2 start app.js --name 'ServerNode'",
    "nacl": "pm2 start app.js --name 'ServerNode' -- nacl",
    "udp": "pm2 start app.js --name 'ServerNode' -- udp",
    "list": "pm2 list",

 

기존에 사용하던 명령어는 npm run start 

 

[root@private-inter-comm-send transaction-test]# npm run start

> servernode@0.0.1 start
> pm2 start app.js --name 'ServerNode'

sh: pm2: command not found
[root@private-inter-comm-send transaction-test]#

 

sh: pm2: command not found

분명히 깔려는 있는데 연결이 안되는거 같습니다.

내부 폴더의 ./node_modules/pm2로 정상실행되는 모습입니다.

 

 

정상적으로 실행되는 경우

 

node랑 npm 버전이 다른거는 큰차이가 없을거 같아서 심링크를 확인해보겠습니다.

 

연결 문제는 아닌거같네요

 

package-lock.json 확인시

 

 

비정상 package-lock.json 을 보니 node_modules가 붙어있는데 

 

npm audit fix 

npm audit 으로 꼬여져있는 부분들을 재 설치 후 다시 말아서 이동

별효과 없음

 

이외에 별짓 다해봤는데

 

노드 16버전에 문제가 있다고 생각되어서 노드 14.19 마지막 버전으로 내리고 정상화되었습니다.

 

내부적으로 노드 14버전을 LTS로 가지고 작업해야겠네요

 

다시 정상화 되었네요;

 

 

14.19 버전으로 당분간 사용하는것으로 하겠습니다.

 

 

 

백업 대상 레포 주소 A

 

 

백업을 하기 위해 새로 만들어 놓은 레포 주소 B

 

 

git clone --mirror A

cd A

git remote set-url origin B

git push --mirror

 

 

안녕하세요 오늘은 간단하게 짚고 넘어가야 할 리액트 에서 이벤트 핸들링에 특징 이 있어 작성하려고 왔습니다.

 

SyntheticEvent ( 합성 이벤트 )

onChange={
            (e)=>{
              console.log(e.type);
              console.log(e.target.value);

              setTimeout(()=>{
                console.warn(e.type);
              })
            }
          }

 

위와같이 코드를 작성 했을 시에 

setTimeout(()=>{

   console.warn(e.type); // null 값이 들어옵니다.

});

해당 이유는 리액트에서의 onChange , onClick 등에 들어오는 event 파라미터는 브라우저에서 사용되고있는 Event 인

https://developer.mozilla.org/ko/docs/Web/API/EventTarget

 

EventTarget

EventTarget은 이벤트를 받을 수 있으며, 이벤트에 대한 수신기(listener)를 가질 수 있는 객체가 구현하는 DOM 인터페이스입니다.

developer.mozilla.org

와는 다른 객체라고 보시면 됩니다.  해당 event 객체는 React 에서 작성한 SyntheticEvent 로 웹 브라우저에 있는 Event 를 이용한 새로운 객체입니다. 그렇기 때문에 위의 비동기 처리시에는 null 값이 되며 해당 경고가 나타납니다.

 

리액트 공식 홈페이지에서도 해당 부분을 해결하기 위해서는 e.persist 라는 함수를 사용하라고 되어있는데요 사용은 간단합니다.

 

위의 에러 코드에서 e.persist()를 호출해주면 됩니다.

          onChange={
            (e)=>{
              e.persist();
              console.log(e.type);
              console.log(e.target.value);

              setTimeout(()=>{
                console.warn(e.type);
              })
            }
          }>

 호출시에는 이제 setTimeout에서도 e.type이 노출됩니다.

e.persist를 사용하고 안하고의 차이를 결과로는 알았습니다.

하지만?   동작이 왜이렇게 바뀌는지에 대한 이유는 기본적으로 리액트에서 사용되고있는 SyntheticEvent 는 객체 풀링 방식을 사용합니다. (Object Pooling) 매 이벤트마다 해당 객체 사용되는것에 대해서 성능상의 이유로 리액트에서는 Object Pooling을 사용함으로써 객체 생성 시간을 줄이고 GC에 대한 노출도 줄이며 메모리관리에 소비되는 시간을 줄이는 방식을 사용하고 있기 때문입니다.  그렇기 때문에 객체가 호출되고 난 후에 이벤트 속성이 초기화 됩니다. 

 

그렇기 때문에 비동기로 호출하였을 경우에는 해당 객체가 비어있는 현상이 발생합니다.

그렇기 때문에 e.persist를 호출하게되면 기존에 사용하고 있던 이벤트 풀 ( Event Pool ) 에서 제거되고 사용자 코드로 사용이 됩니다.

 

 

여기까지 간단한 e.persist에 대해서 적어보았습니다. 

그냥 단순히 비동기에서 왜안되지? -> 아 호출시작부에서 e.persist를 쓰면되겠다 보다는

왜 e.persist라는 거를 써서 했을까 왜 Object Pooling을 사용했을까 등을 생각해보고자 작성하였습니다.

 

감사합니다~

cd 등으로이동할때 tab키를 눌러서 이동하게 되면 해당 에러 메시지가 나타납니다.

아 이게 무슨 버그인가 무슨일인가 생각 하게 됩니다. 탭은 자동완성 기능으로만 알고 있었거든요

 

우선 해당 내용을 순서대로 보게 된다면 "장치" 라는 점에 주의깊게 보셔야 합니다.

 

리눅스 명령어 중에서 df 라는 명령어가 있습니다.  파일 시스템의 디스크 공간을 확인 하는 명령어인데요

 

-h 옵션을 같이 사용하여서 확인해 봅시다.

(-h 옵션은 -human-readable 로 사람이 보기 편하게 해주는 옵션입니다. )

 

df(disk free) - h 명령어로 확인해 보면

 

centos-root 가 꽉찬 모습 

예상이 맞았습니다. 화면에 보이시는 것처럼 /dev/mapper/centos-root 라는 공간이 꽉찼습니다.

 

마운트 위치는 / 로 되어있는데요 그냥 루트라는 뜻입니다.

 

여기서 조금 엉뚱한 생각을 해보자면요 ( 이렇게 생각 하시는 분이 가끔 있을수 있습니다. )

 

/dev/mapper/centos-root 로 들어가면 되는건가?

 

아니지 Mounted On 의 위치가 / 이니까 이 경로가 문제일거야 라고 생각 해볼수 있습니다.

 

전자와 후자 둘다 확인 해보겠습니다

/dev/mapper/centos-root 접속

 

네 아닙니다. 

보시면 디렉토리는 아니고 파일도 아니고 심링크로 연결되어있는 것으로 보입니다.

제가 원하는건 centos-root 인데요 -> ../dm-0 이라고 되어있네요

파일에 써있다기 보단 disk 에 들어가 있는 형태입니다. 

 

정확히 말하자면 내 하드드라이브가 1024G라면 그중 50G가 root라는 파티션에 들어가있다는 뜻이 되는데요

 

그럼 여기서 centos-root 는 어디부분을 뜻할까요

 

centos-root 를 비우는 방법

1차적으로 생각 해본다면 /var 와 /usr 부분을 1차적으로 백업하거나 지우시면 됩니다.

 

간단하게 생각해본다면 /usr의 경우는 크게 바뀌는 부분이 없을 것으로 기대됩니다.

 

/var에서 대부분 git 로그나 WAS(JEUS)의 Access.log 등이 차기 때문이죠

 

/ 로 이동합니다.

 

/var와 /usr가 범인인지 확인하기 위해서는 이번에는 df 가 아닌 du 라는 리눅스 명령어를 사용합니다. 

 

du (disk Usage) 

 

우선 var를 봅시다. 위치들이 다 루트기 때문에 루트 권한으로 봐야 합니다.

 

sudo -s du -sh /var

네 범인이 맞았네요 /usr도 같이 볼까요

sudo -s du -sh /usr

두 친구가 용량의 원인이 맞았던 것 같습니다. 확실히 로그를 쌓고 있는 var 쪽에 처리를 해주어야 할 것 같습니다.

 

/var 경로 정리 

/var 폴더 안에는 무수한 디렉토리 존재합니다.  어떤 폴더가 어느정도 용량을 쓰는지 매번  du -sh 를 할수 없는 노릇

 

편하게 볼수 없는 방법을 찾아보았습니다.

 

sudo -s du -h --max-depth=1

현재 폴더 경로에서 누가 용량을 많이 먹고 있는지 한번에 확인이 가능 합니다. 

예상대로 log가 30G를 먹고 있네요  마찬가지로 로그도 종류가 많기 때문에 같은 방법으로 찾아 봅니다.

Jenkins 로그가 너무 많았네요...

해당 로그로 접근을 위해서는 sudo -s 가 아닌 su 로 Root 아이디로 접근합니다.

 

그리고 해당 로그를 다시 /home 위치에 백업 합니다.

mv jenkins.log /home/oqa/logBackup

백업을 한 후에는 이제 하나로 묶여있는 backup로그들을 나눠 줄 필요가 있습니다.

split -l 300 jenkins.log part_jenkins

300줄씩 로그를 짤라서 part_jenkins 프리픽스를 붙여서 파일을 새로 만듭니다.

다음 이렇게 로그를 분리 하였고 (현재는 로그 백업용 이지만  다음 포스팅에서는 조금 더 유연한 관리를 해보겠습니다.)

 

결과 ( 비워지지 않음 )

보시면 cetnos-home은 분명 늘어났는데!!!

 

centos-root는 그대로 50G를 유지하고있습니다.

 

이유가 뭘까요..

 

원인 

Jenkins 로그였기 때문에 기존에 Jenkins가 실행되고 있었기 때문입니다.

 

Jenkins를 종료 하게되면 그 순간에 제대로된 영역이 잡히게됩니다.

 

이제 다시  시작해주면 끝입니다.

 

/etc/init.d/jenkins start

 

감사합니다.

Jenkins로 빌드 후 배포 

Jenkins로 서버 내부에서 빌드를 완료하게 되면 그에 대한 산출물이 남습니다.

저 같은 경우는 웹 프로젝트가 남게 되는데요.  *. jar 파일 또는 webApplication/ 형태의 폴더가 남게 됩니다.

하지만 빌드 서버에서는 의미가 없게 되는데  WAS(JEUS) 로 디플로이 된 서버는 다른 곳에 있기 때문입니다.

 

그래서 Jenkins로 빌드를 하고 남은 산출물을 빌드 서버로 배포를 해주어야 합니다. 해당 작업도

우리 Jenkins가 도와주는데요 SSH plugin 을 이용하면 너무 쉽게 해당 작업을 하실수 있습니다.

 

Publish Over ssh

해당 plugin 을 설치 하여야 합니다.

 

우선 Jenkins 홈에서 Jenkins 관리를 들어갑니다.

 

Jenkins 홈 - Jenkins 관리 

 

Jenkins 관리의 홈 - 플러그인 관리 로 이동 

 

 

Publish Over SSH 플러그인

 

플러그인을 설치 완료 하였다면 Jenkins 관리 -> 시스템 설정 -> Publish Over SSH로 이동합니다.

 

정상적으로 플러그인이 설치가 되었다면 시스템 설정에서 over SSH 로 검색 시 아래와 같은 상태를 확인하실 수 있습니다.

 

시스템 설정(System Configuration) 에서 Publish over SSH plugin 확인

이 부분에서는 어디로 배포할지 해당 서버에 대한 정보를 적어 놓는 데요 

만약에 A서버와 B서버가 있다면 2개를 추가해야 됩니다.

 

SSH 키 이용시 

  • Passphrase : SSH 연결 시 암호를 사용하고 있다면 암호를 설정합니다.
  • Path to Key : SSH 접속시 ssh key를 관련하여서 접속할 수도 있는데 요 이 SSH 키 를 이용하여 로그인 시 개인키 파일의 경로를 설정합니다.
  • Key : Path to key 와 같은데 키가 text file로 존재하게 될 경우 이 부분에 입력합니다.
  • Disable exec : 체크아웃 SSH 명령을 사용하지 않습니다.

SSH 키 이용하지 않을 경우 

  • Name : SSH 연결 시  (젠킨스 빌드 시에 어떤 서버인지 확인하기 위한 ) 이름을 설정합니다. 

Name 설정 화면 

위와 같이 설정하였다면 젠킨스 빌드 시에는 아래와 같이 메뉴에 이름이 표시됩니다. 

 

Jenkins Build 에서 설정 

  • HostName : SSH로 연결하는 실제 서버의 IP 나 도메인 이름을 지정합니다. (실제 보내야 할 서버 )
  • UserName : SSH 로 연결하는 서버의 사용자 계정 입력 ( IP와 포트나 도메인으로 접속해도 계정명을 아야 하기 때문)
  • Remote Directory : 원격 서버에 로그인한 후에 초기에 어디로 이동할지 선택 (* 필수로 있어야 하며 이 부분은 배포될 경로입니다.) * 저 같은 경우는 /home/계정명/publish로 지정하였습니다. 

Send build artifacts over SSH

이렇게 설정한 후에 Jenkins의 Item에서 빌드를 작업 중  빌드 후 조치  

 

탭에서 send build artifacts over SSH를 선택합니다.

Send build artifacts over SSH 설정

 

  • SSH Server : Jenkins System Configuration(시스템 설정)에서 설정하였던 서버(배포할 서버)의 Name을 선택합니다.

 

  • Source files : 위에 설정한 SSH Server 에보낼 파일을 설정합니다. 저는 gsAuth.war로 직접 지정하였지만 ant 패턴으로 **/*. jar 와같이 jar파일을 전부 보내는 등의 방법이 가능합니다. (여러 개의 파일을 배포할 경우)

 

  • Remove prefix : 배포되는 서버에 생성해서는 안 되는 파일 경로의 prefix 부분으로 현재 작업하고 있는 위치가 test/folder/gsAuth.war인데 단순히 gsAuth 로만 배포가 하고 싶을 경우 test/folder를 입력하게 되면 앞에 부분이 제거된다.

 

  • Remote directory :  파일이 업로드되는 경로인데 Jenkins System Configuration에서 설정한 Remote Directory 가 기준이 된다. /home/계정명/publish 였기 때문에 이 부분에 libs를 적게 되면 /home/계정명/publish/libs 가 최종경로가 된다.

 

  • Exec command : 현재 서버에서 실행될 커맨드 라인을 실행합니다. 위의 사진에서는 echo로 테스트하였지만 실제 사용 시에는 touch 나 >>> 을 이용하여서 배포된 날짜 및 정보를 서버에 적어 놓습니다. 

 

이렇게 세팅이 완료되면 간단하게 원하는 파일을 빌드 후 배포를 할 수 있을 것으로 보입니다.

 

추가적으로 확인해야 할 부분이 있다면

 

Test Configuration으로 테스트 시 위와 같이 나오게 될 수가 있습니다. SSH 키를 사용하지 않고 확인하기 때문인데요

고급 탭을 눌러 줍니다.

Use password authentifaction, or use a differecnt key를 체크한 후에 해당 서버의 계정에 대한 비밀번호를 입력 후

Test Configuration 하게 되면

 

Success 화면

해당 화면을 확인하실 수 있습니다.

 

앞으로 하나씩 더 추가해서 더 좋은 CI CD 환경을 구성할 수 있도록 해봅시다.

create-react-app (cra)가 v2.0가 된지 벌써 몇달이 됐죠 곧 3.0으로 간다고 하는데 현재는 v2.0 버전을 쓰고 있으니까요~


eject는 프로젝트 막바지에 cra버전은 그대로 fixed하고 eject로 따로 포크떠서 진행할 예정인데요


그전에 sass 모듈이 cra 2.0에서는 기본 지원이여서 너무 편했지만!




import styles from './RecentPostWrapper.scss';


그냥 이렇게 ! 모듈을 불러오면 만사 OK!!


하지만 기본적으로 node-sass는 설치 해주어야 합니다!


"dependencies": {
"classnames": "^2.2.6",
"cross-env": "^5.2.0",
"include-media": "^1.4.9",
"node-sass": "^4.11.0",
"query-string": "^6.3.0",
"react": "^16.8.3",
"react-dom": "^16.8.3",
"react-redux": "^6.0.1",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
"react-scripts": "^2.1.8",
"redux": "^4.0.1",
"redux-saga": "^1.0.2",
"redux-thunk": "^2.3.0"
},


현재 저는 윈도우에서 사용하고있는데 일단 컴포넌트들에 대한 path 를 잡기 위해서


cross-env를 사용하고 있습니다.


cross-env를 설치 한 후에! package.json 에 start script 에 


"start": "cross-env NODE_PATH=src react-scripts start",


이와같이 cross-env를 통하여서 src를 루트로 잡아 주게 됩니다.



보시면 PageTemplate.js 에서 호출하고 있지만 Blog Header를 components/common/BlogHeader로 호출하고 있습니다.


cross-env가 없었다면  ../BlogHeader로 호출했을 것 같습니다. 더 짧아서 좋은거 아니냐! 할수 있지만  


같은 경로에 있기 때문에 그렇지 다른 경로에 있다면 루트 경로가 지정되어있지 않은건 상당한 불편함과 개발 진행이 늦어질수 있습니다.


자 그렇다면 Style을 사용하기위해 SASS를 쓰는데 이에 대한 util.scss는 어떻게 될까요?




@import '../../../styles/utils.scss';

 


???????     ../ ../ ../ ../ 이게 도대체 무엇인가요!!!! 컴포넌트도 이처럼 


@import 'style/utils.scss';


 이렇게 하고싶은걸요..


그러는 와중 CRA공식 문서를 보다보니 이런 글이..




NODE_PATH 처럼 node-sass는 SASS_PATH 를 지원한다고????? ../../ 너무 거슬렸는데;;;


(** 여기서 유의 저는 cra1.0에서 이것저것 해보다가 2.0으로 넘어왔다)


.env 파일을 수정하라고 되어있지만   윈도우에서 수정 부분을 이 아닌 것으로 보여 


// @import '../../../styles/utils.scss';
@import 'styles/utils.scss';


그냥 수정하니 .. 지원된다... 그냥 되는거였습니다... 공식홈페이지 자주보겠습니다.


@import '../PageTemplate/PageTemplate.scss';


확실한지 확인하기 위해서 해당 scss를 수정하겠습니다.


// @import '../PageTemplate/PageTemplate.scss';
@import 'components/common/PageTemplate/PageTemplate.scss';


음.. 잘되네요


** cra2초반버전에서는 안되는것으로 보여서


공식 홈 다시 보니! 2.1.2 밑으로 쭈욱 내리다 보면




!!!!!!!!! SASS_PATH???





#5917을 따라 들어가 보니


그렇다 추가 된 것이였다.

https://github.com/facebook/create-react-app/pull/5917




공식 홈을 자주보는 것을 생활화 합시다!!!


https://facebook.github.io/create-react-app/docs/adding-a-sass-stylesheet








출근해서 코드 를 작성후에 평소와 같은 git작업




git status ~ 체크 


git add * 


git commit -m"솰라 솰라 이번주의 변경사항~"


git push origin master 


Error




??? 왜지? 잘되던 git이?


확인해보니 서버를 옮겼었습니다. 기존에 123.15 였다면 지금은 105.17 인거죠 gitlab의 경우 외부 접속을 하기 위한 


지정으로




sudo -s vi /etc/gitlab/gitlab.rb 


에 external_url 로 지정을 해놓을수가 있습니다.  이부분을 수정했습니다.

## GitLab URL

##! URL on which GitLab will be reachable.~~ 

##! For more details on configuring external_url see:

##! https://docs.gitlab.com/om~~

external_url 'http://111.111.105.17:8888'




저장 후 나가기를 위해서 ( ESC + :  > wq 입력 엔터 )  


gitlab을 사용하시는 분들은 알겠지만 db로 postgresql  rails  WAS도 nginx 등등 여러가지들을 한꺼번에 


구동시켜주어야 합니다. 이 구동을 위해서 gitlab-ctl 이라는 요녀석이 있는데 이녀석이 에 명령어로


command line 에서  gitlab-ctl reconfigure 를 입력하게되면 gitlab.rb를 시작으로 configure 파일들을 다시 읽음으로


적용과 함께 재시작 해주는 기능을 합니다.


그래서 


sudo -s gitlab-ctl reconfigure


Error  : not found command gitlab-ctl 


?? 알수 없는 명령어라니  6개월간 잘쓰던 git이 이상해졌습니다.


일단 gitlab-ctl 이 명령어가 없는거라면 환경변수(env_path) 설정이 문제일수 있으니 바이너리 먼저 찾아 보도록 합시다


find / -name 'gitlab-ctl'


result : nothing to do 


OMG!! 어떻게 된 일일까요??? (-__-) !  원인 파악이 잘되지않습니다. 해당 서버는 공용 서버(shared Server) TT 


기존에 설치된 ce버전으로 업그레이드를 시도해보았습니다.


sudo -s yum -y install gitlab-ce -> gitlab -ce upgrade


Error: nothing to do 


search sentence list


gitlab-ctl 을 찾기 위해서 여러가지 검색 조건을 찾아서 검색을 해보았습니다.


gitlab-ctl only install


git bin directory has been removed


gitlab start without gitlab-ctl 


gitlab-ctl error


gitlab-ctl not exist 등등


... etc


마지막으로 시도한  (last challenge)


gitlab-official home page에 있던  gitlab-ee upgrade입니다.


// 패키지 fork 


curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.rpm.sh | sudo bash


// gitlab EE upgrade


sudo yum install gitlab-ee -y


gitlab-ctl









이제 정상적으로 나오네요 감격 입니다. ㅜㅜ



이런일이 나도 복구 할수 있도록 백업 본을 하나 떠 놓도록 하겠습니다.


gitlab-rake gitlab:backup:create

해당 백업의 위치는 /var/opt/gitlab/backups/$timeStamp_version-eegitlab_backup.tar 로 있게 됩니다.



다시 gitlab 을 쓸수 있게 되었네요 ee로 업그레이드하는 바람에 좀더 최신의 느낌이긴 하지만요





다른 타 프로젝트들도 다행히 다 잘살아있네요







안녕하세요 테스트링크 및 젠킨스 연동 포스팅도 끝났고


Chrome 브라우저에 대한 자동화 빌드 테스트도 어느정도 마무리가 된것 같습니다. 


사실 연결만 되었고 결과 받고 100프로 다 된것 같지만 고도화 등 마무리 처리를 해주어야 할 작업이 산더미 입니다.


하지만 이 작업들은 여기까지 오셨다면 다들 할수 있을거라 생각하기 때문에 따로 포스팅은 하지 않으려고합니다.


현재 자동화 도식도를 좀더 눈으로 보기 좋게 그려봤습니다



젠킨스가 빌드를 땅 시작합니다.(when a build step) 빌드 유발을 통해서 (이부분은 따로 Jenkins - 빌드 유발로 포스팅하겠습니다.)


그럼 셀레니엄 테스트코드가 있는 최신 master 브랜치에서 코드를 가져온후에 셀레니엄 코드를 실행합니다.


위의 사진에선 파라미터로 Chrome을 넘겼기 때문에 크롬 브라우저가 실행되겠네요 


테스트할 페이지 (top.js -client) 의 아랫단에는 factory 라는 모듈(간단하게 제작)이 있는데 이 해당 모듈은 E2E TestCase 


에 기반하여 사용자 또는 셀레니움이 했던 행동들을 기록하며 리포팅을 해줄수 있도록 되어있습니다. top.js - client에는


테스트의 마지막을 뜻하는 End Point 지점이 있는데요 이 지점이 클릭 됐을 시 연초록색 화살표 대로 발생합니다.


앤드 포인트가 찍히는 시점은 2갠데요 


1. Selenium 이 찍었을때 


.click() 후 작업이  해당 테스트 결과를 Junit-report.xml 형태로 만들어놓습니다.


만드는건 xmlBuilder를 사용하여 직접 간단하게 만들었습니다.


만들어 놓게 되면  셀레니움 빌드가 끝나게 되는데요 그렇게되면 Jenkins에 있는 TestLink Plugin 이  제가 설정한대로


junit -report 파일을 찾습니다. 찾을 때는 설정에 따라 다른데요 저는 classname으로 지정해놨기 때문에


classname이 Custom Field랑 같은 경우를 찾습니다.


TestLink 에서 처음으로 날아온 TC(TestCase)의 customField값이 CustomField_0001 이라고하면


<testcase  classname ="CustomField_0001"></testcase> 이 값을 찾게 됩니다. 그래서 만약에 해당 xml을 찾았을때


<failure>라는 태그가 없을시에는 pass로 처리를 하고 있을 경우 fail로 처리합니다.


그래서 원래는 Iterative Build Steps에서


테스트링크로 매번 넘어오는 테스트 케이스에 대한정보를 통해서 그때그때 실행하고 결과를 받아야 합니다.


하지만 저 같은 경우는 E2E테스트가 먼저 이루어진 후에 해당 결과를 넣어 줘야 하는 경우이기 때문에 


이렇게 작성되어있으며


아래 사진은 top.js (Client ) 에 있는 factory 라는 모듈에서 


factory.report() 를 호출했을때 의 모습입니다. 보시면  45건은 기본 페이지 렌더링시 하는 케이스들인데


이부분들에 대한 성공 실패와 우측엔 가렸지만 실패 메시지등이 들어있습니다.


End Point에서는 해당 리포트를 호출한 후 Node.js로 보내게 됩니다. 2번에서 따로 말씀드립니다.


셀레니엄 에서는 이부분을 






topqa.prototype.createJunitReport = async function(){
try{
let array = [];
const retVal = await this.driver.executeScript('return factory.report()');
makeJunitReport(retVal);
}
catch(err){
console.log(err);
}
}


Selenium에서는 executeScript 라는 API 가있습니다. 브라우저 인젝션을 할수 있도록 만들어진 API 인데요


위에 보시는 것처럼 factory.report() 를 호출후에 return 을 넣어 주면 retVal 에 해당 테스트결과를 받아 볼수 있습니다.


중간에 보시면


makeJunitReport(retVal);


라는 함수가있는데 이 함수의 내용은 이렇습니다.


const { makeJunitReport } = require('./topqaXmlBuilder');


이렇게 가져와서 사용하고 있으며 toqpaXmlBuilder의 원문은


/*

***** Pass Test Case
<testsuite>
<testcase classname="casebind" name="testName"></testcase>
</testsuite>

***** Fail Test Case
<testsuite>
<testcase classname="casebind" name="testName">
<failure type="test">Error Message</failure>
</testcase>
</testsuite>
*/

const builder = require('xmlbuilder');
const fs = require('fs');
const path = require('path');
const xmlHeader="<?xml version='1.0' encoding='UTF-8'?>";

// Create Test Dir
function makeDir(path){
fs.mkdirSync(path,{mode:0777});
}
// write file junit-testCase.xml
function makeJunitReportFile(xmlContent,path,testCase){
console.log(testCase);
let fd = fs.openSync(path+'/junit-'+testCase+'.xml','w');
fs.writeFileSync(fd,xmlContent,{encoding:"utf-8"});
}

function _isArray (array){
if((!!array && (array.length !==0 && typeof array.length!=="undefined"))){
        // [1] 의 배열도 return 이 되는데 이건 추후 생각
        return array;
}
    else{
        return false;
}
}
function makeJunitReport(testResultArray){
const array = _isArray(testResultArray);
if(array===false){return;}

console.log(array);
// // File Write for junit-*.xml
const dirPath = path.join(__dirname,'../chromeBuilding');
// ie firefox에 따라 다를수 있기 때문에 chromeBuilding -> Building으로 수정
!fs.existsSync(dirPath) ? makeDir(dirPath) : console.log('폴더 미생성');

testResultArray.reduce((acc,curr,index,arr) =>{
let junitObj = {
'testsuite': {
'testcase':{
'@classname':curr.tc,
'@name':curr.tc,
}
}
};
// fail Case
if(curr.result.toLowerCase()==='fail' || curr.result.toLowerCase()==='block'){
let failObj = {
'#text':curr.msg,
'@type':curr.result.toLowerCase() + 'Case'
};
junitObj.testsuite.testcase['failure'] = failObj
}
// xml Create
const junit = builder.create(junitObj,{encoding:'utf-8'});
// Create xml pretty change
const xmlContent = junit.end({pretty:true});
if(curr.tc.length>=1){
makeJunitReportFile(xmlContent,dirPath,curr.tc);
}
},0);
console.warn('test Result reporting Complete');
}
function api(){console.log('api')}

module.exports = {
makeJunitReport,
api
}


단순합니다. 저 위에 factory.report 에 있는 배열의 형태를 받아서 _isArray 를 통해 확인 후 테스트결과 폴더 생성 (없을시)


생성 후에 배열을 순회하면서 xmlBuilder형태의 Object를 만드는데 fail케이스인 경우는 failure를 넣어주기 위한 작업을 행합니다. 그리고 결과에 대한 junit 파일을 전부 만들어 놓고 종료 합니다.


파일이 다만들어진 상태이기 때문에


Iterative Build Steps 은 필요가 없고 역시 Single Build Steps 또한 필요가 없습니다.


그렇게 되면 바로 다음 단계인


Test Seeking로 넘어가게 되는데요 이부분에서 위에 만든 파일들을 찾으면서 test Case를 update하게됩니다.



로그를 보면(Jenkins ConsoleOut) 계속 케이스가 업데이트 되는 걸 확인 하실수 있습니다.


2. Selenium 이 EndPoint를 찍고나서 해당 함수가 Call 됐을때


2번째 End Point인데요 뭔가 다른거 같은데 차이를 쉽게 설명 드리자면


EndPoint Function 이 있습니다.


function endPoint(){ console.log('do anything');}


해당 함수는 어떤 버튼에 callback 으로 작성되어있을 건데  1번은 그 버튼을 찾아서 찍고난 후 이고 이번에 2번은


셀레니움이 찍어서 관련 함수가 callback 되었을때의 시점입니다.


해당 함수에서는 1번과 같이 factory.report()를 통한 결과를 가져온 후에


ajax 로 Node.js 서버에 보낼때 테스트결과와 해당 lib버전을 같이 보내는데 그 내용을 DBMS와 파일 2가지로 저장합니다.


해당 이유는 React 페이지에서 결과 추이 (전버전과 차이점 , 버그가 많은 버전, 변경된 기능 )


등을 볼수 있도록 하기 위함입니다.


감사합니다.


factory 모듈에 대해서는 따로 포스팅은 않았습니다.  뭔가 정형화된 패턴도 아니고 저만의 테스트 방식이라서 .. 


여기까지 Jenkins TestLink E2E 테스트 관련 이였습니다.


감사합니다.



안녕하세요 저번 포스팅에서 


테스트링크 젠킨스 연동전에 기본적인 개요 및 전체적인 프로세스 등에 대해서 

포스팅하였습니다.

https://ipex.tistory.com/entry/Jenkins-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A7%81%ED%81%AC-%EC%A0%A0%ED%82%A8%EC%8A%A4-%EC%97%B0%EB%8F%99-1-%EB%B0%8F-%EC%9B%B9-%EC%9E%90%EB%8F%99%ED%99%94-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B0%9C%EC%9A%94-TestLink-with-Jenkins-Windowsver-and-webAutomation-OverView?category=767972


이번에 작성할 내용은 빌드 작업입니다.


기존 작업 기능 List 


1. General 탭  

 오래된 빌드 삭제 (25일 설정)


2. 소스코드 관리 탭 

빌드 시작전 git에서 master 브랜치를 가져와서 workpace에 세팅하는 작업


3. 빌드 유발 

Build when a change is pushed to GitLab. GitLab webhook URL :  체크 

없을시 GitLab Plugingit plugin 설치 후 gitLab에 있는 webHook 기능으로 빌드 유발


** 빌드 유발 쪽 체크 

4. 빌드 환경

Delete workspace before build start  + ant style **/chromeBuilding/ 을 통하여

workspace/chromeBuilding 폴더 빌드 전 삭제 

 

이제 5번 빌드 입니다.


Build Job 은 현재 3가지가 있습니다. (포스팅할때는 이미 다 해놓은 상태에서 포스팅!)


IE Automation

Chrome Automation

FireFox Automation 


세가지에 대한 빌드는  각각 다르게 돌아가지만 다 같은 내용이기 때문에  한번 포스팅 하겟습니다.


5. 빌드

빌드는 2가지로 나뉩니다. 

5.1 Selenium-WebDriver


5-2 TestLink 




5.1 Selenium-WebDriver


우선 셀레니엄 웹 드라이버 코드를 빌드는 매우 단순합니다.


Build 탭 - Add build step  - Execute Windows batch command 




을 누르신 후에 Command 에 node _auto.js [param]


_auto.js 는 미리 작성된 셀레니엄 자동화 테스트 코드 입니다. 


** 테스트 타겟인 자동화 메인 페이지의 경우 (lib가 변동되거나 할때 git으로 push 또는 merge시 같은 webhook 발생) 



5-2 TestLink 

자 문제의 testLink 입니다.


시작하기에 앞서 중요한 점이 있습니다. testLink 버전에 따라 이슈가 있을수 있기 때문에 버전 명을 기입하겠습니다.


testLink 1.9.16(Moka pot) 버전을 사용하고 있습니다.


Jenkins 2.141 


빌드 작업 전에 설치 및 세팅 작업을 해주어야 합니다.


플러그인 설치


- Jenkins Home ( Jenkins 관리 )  -> 플러그인 관리


- 필터 : TestLink 입력 후  설치 버튼 




- 재 시작 후 TestLink Plugin 설치 확인 을 위해 - Jenkins 관리 - 시스템 설정 


TestLink 있는지 확인 


TestLInk 접속 후 (로그인까지 완료 한 상태)  아래의 아이콘을 선택하여서 API 키 생성




TestLink Installation 에서 추가 버튼 클릭 후 아래 공란 입력


Name : ( 어떤 테스트링크에 연결할지 작성 )

URL   : 테스트링크주소:포트/testlink/lib/api/xmlrpc/v1/xmlrpc.php  (API 사용 주소)

만약에 잘안된다면 api 주소를 찾아봐야합니다.

Developer Key 저 위에 있는 API 인터페이스 밑 개인 API 접근 키 입력 




(동작 확인)


이제 빌드 단계로 가기전에 미리 해주어야 하는 작업이 있습니다.


// 테스트링크 작업 Custom Field 


// 



이제 다시 빌드 단계로 가면 


기존에 selenium 드라이버를 실행시키는 Command 가 있습니다. 이부분에서 


Add build step ! 클릭





Invoke TestLink 클릭



화면에 나타나는 TestLink Configuration 을 작성합니다.


Invoke TestLink  - TestLink Configuration


TestLink Version - TestLink Installation 에서 작성한 name을 콤보 박스에서 선택 

Test Project Name  - TestLink 에 있는 프로젝트 이름 (아래 그림 참조)


Test Plan Name  - TestLink 에서 해당 프로젝트에 따른 테스트 계획을 만들고 해당 계획을 작성합니다.

// 테스트 링크 기본 사용법은 간단하기 때문에 따로 작성하지 않겠습니다.

Build Name  - 새로운 빌드시 지정될 빌드 명을  입력합니다.

AUTOMATION_$BUILD_ID 로 되어있는데 아래 그림과같이 해당 형태로 빌드 가 자동으로 생성됩니다. 


Custom Fields - 매우 중요합니다.  테스트 결과에 대한 리포팅 파일에서 해당 CustomFields를 통하여서 작업 하기 때문입니다.

// TestLink 에서 커스텀 필드를 만든 후에 해당 필드 내에 값을 test Case와 일치 시킵니다.

// Test Case의 경우 한번에 넣는 방법이 있는데 이에 관련해서는 이 포스팅 다음에 번외로 작성하겠습니다.


Invoke TestLink  - TestExecution

해당란에는 2가지가 있습니다.

1. Single Build Steps  

해당 빌드 작업을 한번만 실행합니다.

2. Iterative Test Build Steps 

해당 빌드 작업을 여러번 실행합니다.

이해가 편하게 testlink 에서 케이스가 날라옵니다.  아래의 케이스


id : 55123

name : TOP_WIDGET_CREATE

summary : 위젯이 생성되는지 테스트합니다.

caseBind(custom설정을하였다면) : TOP_WIDGET_CREATE 

....

위의 한개의 케이스가 100개 1000개 있을텐데 해당 케이스가 한번에 날라오게 됩니다.


Single Build Steps의 경우는 이미 전의 테스트가 완료된 결과가 있는데 Junit이나 testNG등 리포팅 형태가 아닐 경우

해당 결과물들을 testLink Plugin 이 찾아서 실행할수 있도록 만들어 주는 커맨드를 작성합니다.


Interative Test Build Steps의 경우는 테스트 케이스가 위에 설정한 Test Plan 에 있는 순서대로 날아옵니다.

날아오는 케이스에 대한 정보를 통해서 테스트를 실행하여 결과를 받아서 사용합니다.

플러그인 도움말을 눌러 보시면 알겠지만 해당 값들을 통해서 원하는 스크립트에 파라미터로 넘길수 있습니다.


** 주의사항  (Windows는 앞뒤로 % 가 들어가야 하며 Linux 계열은 앞에 $ 가 붙어야 합니다.

Windows - %TESTLINK_TESTCASE_ID%


Linux - $TESTLINK_TESTCASE_ID




저는 첫 Build 시 node _auto.js chrome에서  결과 리포팅 파일을 만드는 작업을 하기 때문에  Test Exceution 을 사용하지 않겠습니다.

(테스트는 셀레니엄이 자체적으로 하였고 이에대한 테스트결과를 받아서 리포팅까지 작성해 놓은 상태 - 번외편에 작성하겠습니다.)


Invoke TestLink  - Result Seeking Strategy 

테스트 결과를 찾아서 입력 하는 과정입니다.

어떤 형태의 결과를 찾아서  입력할지 여러 방법이 있는데요 이중에서 저는 Junit Class Name 을 선택하였습니다.



입력 결과


// 셀레니엄 빌드 스텝에서 ChromeBuilding이라는 디렉토리를 만들고 해당 안에 테스트케이스에 대한 결과 값들을 저장합니다.

위의 패턴은 Ant Style pattern 이며 더 찾아서 공부해보셔도 됩니다.


설명하자면 workspace/ChromeBuilding/ 폴더 안에 junit-*.xml 형태의 파일들을 읽는데  junit class name 이기 때문에


caseBind (Custom Field)의 값이 classname으로 지정되어있는 xml들을 찾아서 결과를 입력 합니다.


해당 junit리포팅 형태는




***** Pass Test Case
<testsuite>
<testcase classname="casebind" name="testName"></testcase>
</testsuite>

***** Fail Test Case
<testsuite>
<testcase classname="casebind" name="testName">
<failure type="test">Error Message</failure>
</testcase>
</testsuite>



해당형태를 지닙니다. 


빌드 후 조치 단계에서는


빌드 환경에서


Delete workspace before build starts 로 해당 junit폴더를 지우고 시작했었는데요 이작업을 빌드 후 조치에서 하셔도 됩니다.



자 이제 빌드 시작 !






정상적으로 빌드가 나왔네요


눌러서 확인해보겠습니다.



45건만 자동으로 해보았는데요


첫 시작 부분은 이렇고 맨 마지막 부분은 실패한 케이스가 보이네요  



일단 왜 실패했는지도 궁금하지만? 그전에 testLink 에연동이 먼저 됐는지도 봐야겠습니다.


저기 위에 결과 보면


build name : AUTOMATION_12  라고 되어있으니 testLink 에 가서확인해보겠습니다.




정상적으로 되어있는것 같습니다. 하지만 실패 케이스의 이유가 너무 궁금하기 때문에 이것을 위해서 빌드 작업에


제가 체크한 게있는데요




저부분 때문에 testLink 에서 실패 케이스로 가서 누르시면




노트 부분에 결과에 대한 xml파일이 들어가 있습니다 . !!! 이부분은 제가 


junit파일 생성시에


testResultArray.reduce((acc,curr,index,arr) =>{
let junitObj = {
'testsuite': {
'testcase':{
'@classname':curr.tc,
'@name':curr.tc,
}
}
};
// fail Case
if(curr.result.toLowerCase()==='fail'){
let failObj = {
'#text':curr.msg,
'@type':'failCase'
};
junitObj.testsuite.testcase['failure'] = failObj
}
// xml Create
const junit = builder.create(junitObj,{encoding:'utf-8'});
// Create xml pretty change
const xmlContent = junit.end({pretty:true});
if(curr.tc.length>=1){
makeJunitReportFile(xmlContent,dirPath,curr.tc);
}
},0);


fail case일 경우에 실패 메시지와 같이 넣어 주었는데요


해당 xml을 눌러주게되면?



실패한 내용 (try catch 로 엮었던 직접 작성하였던) 확인이 가능합니다.!!

자동화 플로우가 다 끝났습니다.


Chrome과 FireFox는 거의 비슷하여서 문제가 없지만 ie의 경우는 문제가 많아서.. 트러블 슈팅 가이드를 따로 작성해야 할듯 합니다.



다음 포스팅은 Jenkins TestLink Intergration 마지막 작업으로 


번외 

1. TestCase를 한번에 넣는 방법 

2. 테스트가 어떤식으로 제작되었고  젠킨스 빌드 과정 중 결과를 어떻게 받아서 처리 하였는지 


로 작성하겠습니다.


1번과 2는 사실 나눈건 아니고 하나로 보시면 됩니다.


긴글 읽어주셔서 감사합니다.


꾸벅




+ Recent posts