Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.

Powerlevel10k로 zsh 설정하기

그동안 꾸준히 bash를 사용했다. zsh이나 Fish가 더 강력한 부분이 있는 건 알고 있었지만 새로 세팅하는 것이 귀찮기도 했고 로컬 환경을 너무 잘 꾸며놓고 익숙해져도 서버 같은데 들어갔을 때나 다른 환경에서 사용이 어려워진다는 생각이 있어서 적당한 선에서만 편하게 유지하는 것도 있었다.

$ echo $0
-bash

위처럼 현재 shell을 확인해 보면 bash(Bourne Again Shell)로 설정된 걸 알 수가 있다. 하지만 macOS 10.15 Catalina부터는 bash가 아니라 zsh이 기본이 되었다. macOS가 기본 쉘을 바꾼 건 bash 최신 버전의 라이센스인 GPL v3 때문에 최신 bash로 올리지 못하고 있기 때문으로 알고 있다.

시스템 환경 설정에서 Control을 누르고 사용자 명을 누르면 기본 쉘을 변경할 수 있다.

macOS 사용자 및 그룹 설정

Catalina 이후에는 Terminal을 쓰면 zsh 쓰라는 안내대로 터미널에서 chsh -s /bin/zsh를 실행해도 된다.

Last login: Sun Jul 26 15:30:59 on ttys002

The default interactive shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.

미루다가 이번에 zsh로 갈아탔다. 언젠가는 zsh로 가긴 해야 할 것 같아서...

zsh 설정할 때 가장 많이 사용하는 Oh My Zsh과 최근에 알게 된 starship도 있었지만 트위터에서 추천받은 Powerlevel10k를 사용했다. 원래는 starship이랑 비교해 볼 생각이지만 Powerlevel10k 설정에 너무 많은 시간을 소비해서 그냥 Powerlevel10k에 안착했다.

Powerlevel10k

일단 Powerlevel10k를 설정해 보자. Powerlevel10k 문서에 아직 잘 모르겠으면 수동 설치를 권장하길래 수동 설치를 했다. 사용하는 zsh 플러그인 매니저가 있으면 해당 플러그인 매니저로 설치해서 관리하는 게 더 좋은 것 같다.

$ git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ~/powerlevel10k
$ echo 'source ~/powerlevel10k/powerlevel10k.zsh-theme' >>! ~/.zshrc

~/.zshrc 자체가 아예 없었기 때문에 powerlevel10k가 제공하는 내용으로 ~/.zshrc를 생성했다.

그러고 나면 자동으로 설정 마법사가 터미널에 열리게 된다. 아니면 p10k configure를 직접 실행해도 설정 마법사를 시작할 수 있다.

powerlevel10k 설정 마법사

powerlevel10k 설정 마법사

프롬프트 스타일, 시간 표시 등 설정 마법사에서 원하는 스타일로 설정할 수 있고 당연히 이후에도 수정할 수 있다.

설정이 끝나면 아래처럼 ~/.zshrc 파일이 생성된다.

# Enable Powerlevel10k instant prompt. Should stay close to the top of ~/.zshrc.
# Initialization code that may require console input (password prompts, [y/n]
# confirmations, etc.) must go above this block; everything else may go below.
if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
  source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
fi

source ~/powerlevel10k/powerlevel10k.zsh-theme

# To customize prompt, run `p10k configure` or edit ~/.p10k.zsh.
[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh

이 파일이 zsh의 기본 설정 파일이지만 Powerlevel10k는 같이 생성된 ~/.p10k.zsh 파일에서 원하는 대로 스타일이나 기능을 변경할 수 있다. ~/.p10k.zsh를 열어보면 어디를 먼저 봐야 할지 모를 정도로 많은 설정이 있다. 자세히 보면 모든 상황에 대한 색상, 문자, 기능들을 조정할 수 있는데 처음 사용해봐서 아직 잘 모르기도 하고 필요한 부분만 수정해서 설정했다.

Powerlevel10k 설정

특히, Powerlevel10k는 속도를 위해 많은 부분을 비동기로 처리하고 있어서 기존에 사용하던 동기 코드로 설정한 것과 맞지 않는 부분이 있었다. 동기로 처리되는 작업이 느리면 zsh을 시작할 때 시간이 걸리게 되는데 이런 작업을 비동기로 실행하고 지연시간을 없애서 바로 프롬프트를 사용할 수 있게 한 기능이 Instant prompt다. 작업을 비동기로 처리하기 때문에 처리 중 콘솔 출력이 있으면 이를 어떻게 처리할지 정해야 하는데 설정 마법사에서 아직 익숙지 않으면 verbose로 설정하라고 해서 POWERLEVEL9K_INSTANT_PROMPT=verbose로 설정해서 사용하고 있다.

그래서 내가 애용하는 direnv를 사용하면 zsh를 시작할 때 다음과 같은 경고 메시지가 나온다.

[WARNING]: Console output during zsh initialization detected.

When using Powerlevel10k with instant prompt, console output during zsh
initialization may indicate issues.

You can:

  - Recommended: Change ~/.zshrc so that it does not perform console I/O
    after the instant prompt preamble. See the link below for details.

    * You will not see this error message again.
    * Zsh will start quickly and prompt will update smoothly.

  - Suppress this warning either by running p10k configure or by manually
    defining the following parameter:

      typeset -g POWERLEVEL9K_INSTANT_PROMPT=quiet

    * You will not see this error message again.
    * Zsh will start quickly but prompt will jump down after initialization.

  - Disable instant prompt either by running p10k configure or by manually
    defining the following parameter:

      typeset -g POWERLEVEL9K_INSTANT_PROMPT=off

    * You will not see this error message again.
    * Zsh will start slowly.

  - Do nothing.

    * You will see this error message every time you start zsh.
    * Zsh will start quickly but prompt will jump down after initialization.

For details, see:
https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt

-- console output produced during zsh initialization follows --

direnv: loading .envrc

사실상 direnv의 출력은 direnv: loading .envrc 부분이고 앞은 powerlevel10k가 보여준 안내 메시지이다. 익숙해 지면 이 부분을 꺼도 될 것 같은데 어느 정도는 킨 채로 사용할 생각이다. bash를 쓸 때는 다 동기로 동작했기 때문에 이런 비동기 설정을 어떻게 해야 하는지 고민되는 부분이 있는데 온종일 만지고 있었더니 약간은 익숙해 진 것 같다.

zsh의 git 상태 표시

Git 설정이 된 디렉터리에 들어가면 브랜치와 커밋을 표시하고 수정된 파일과 git에 추가되지 않은 파일 수가 나온다. ~/.p10k.zsh에서 브랜치가 없을 때만 표시되게 되어 있던 커밋이 항상 보이도록 조건절을 없애고 res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}" 부분만 남겼다. 이전에 커밋 메시지를 계속 보면서 확인하던 설정이 익숙하던 터라...

~/.zshrc 파일에 사용 중인 rbenv, pyenv, direnv를 초기화하는 설정을 넣었다.

# rbenv
eval "$(rbenv init -)"
# pyenv
eval "$(pyenv init -)"
# direnv
eval "$(direnv hook zsh)"

덕분에 해당 환경이 활성화되면 쉘에서 지정한 버전이 표시된다.

zsh의 python 버전 표시

pyenv로 지정한 Python 버전이 표시된다.

zsh의 ruby 버전 표시

rbenv로 지정한 Ruby 버전이 표시된다.

zsh의 direnv 활성화 표시

direnv가 활성화되면 우측에 별(⭐)이 표시된다. 폴더 경로도 화면에 비해 길 경우 알아서 줄여준다. 필요에 따라 커스텀 가능하지만 이런 부분을 알아서 지원해주어서 무척 편하다.

입력하는 명령에 따라 동적으로 상태도 표시해 준다.

Terraform 프로필 표시

terraform 명령어를 입력하면 우측에 현재 클라우드와 AWS 프로필이 표시된다. AWS_PROFILE=tf로 설정되어 있어서 우측에 저렇게 나온 것이다.

zsh의 Kubernetes 컨텍스트 표시

kubectl을 입력하면 현재 Kubernetes 컨텍스트가 표시된다. 기본 네임스페이스도 보여주고 싶은데 아직 이 부분은 설정하지 못했다. 동적으로 표시하는 게 아니라 kube-ps1처럼 껐다가 키려면 문서에 나온 대로 kube-toggle()~/.zshrc에 추가하면 된다.

function kube-toggle() {
  if (( ${+POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND} )); then
    unset POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND
  else
    POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito'
  fi
  p10k reload
  if zle; then
    zle push-input
    zle accept-line
  fi
}

이제 kube-toggle을 입력할 때마다 Kubernetes 컨텍스트를 보였다 안 보였다 할 수 없다. bash와 달리 우측에도 뭔가 표시할 수 있어서 좋긴 하다.

Zinit

Zinit은 zsh 플러그인 매니저이다. zsh을 처음 사용해본지라 Powerlevel10k를 추천해 주신 simnalamburt님의 .zshrc에서 보고 괜찮아 보여서 적용했다.

$ sh -c "$(curl -fsSL https://raw.githubusercontent.com/zdharma/zinit/master/doc/install.sh)"

문서에 나온 대로 설치는 쉽게 할 수 있고 설치하고 나면 ~/.zinit/bin을 생성하고 이후 플러그인도 ~/.zinit 아래 설치된다. 그리고 ~/.zshrc 파일에 아래 같은 부분이 추가된다. zinit이 설치 안 된 곳에서는 설치하고 zinit을 초기화하는 코드다.

### Added by Zinit's installer
if [[ ! -f $HOME/.zinit/bin/zinit.zsh ]]; then
    print -P "%F{33}▓▒░ %F{220}Installing %F{33}DHARMA%F{220} Initiative Plugin Manager (%F{33}zdharma/zinit%F{220})…%f"
    command mkdir -p "$HOME/.zinit" && command chmod g-rwX "$HOME/.zinit"
    command git clone https://github.com/zdharma/zinit "$HOME/.zinit/bin" && \
        print -P "%F{33}▓▒░ %F{34}Installation successful.%f%b" || \
        print -P "%F{160}▓▒░ The clone has failed.%f%b"
fi

source "$HOME/.zinit/bin/zinit.zsh"
autoload -Uz _zinit
(( ${+_comps} )) && _comps[zinit]=_zinit

# Load a few important annexes, without Turbo
# (this is currently required for annexes)
zinit light-mode for \
    zinit-zsh/z-a-rust \
    zinit-zsh/z-a-as-monitor \
    zinit-zsh/z-a-patch-dl \
    zinit-zsh/z-a-bin-gem-node

아직 zsh은 잘 몰라서 터미널 사용에 편해 보이는 몇 가지 플러그인을 설치했다.

~/.zshrc에 아래 코드를 넣고 새 터미널 창을 열면 비동기로 플러그인을 설치한다. 한번 설치하면 이후에는 바로 로딩되니 그냥 사용하면 된다.

zinit light zdharma/fast-syntax-highlighting
zinit light zsh-users/zsh-autosuggestions
zinit light zsh-users/zsh-completions

zsh-completions에 따로 설정이 필요한지는 모르겠는데 Kubernetes와 Terraform 명령어는 자동 완성을 해주지 않아서 따로 ~/.zshrc에 추가했다.

zsh 속도

Powerlevel10k가 많은 작업을 비동기로 처리해 주기 때문에 뒤에서 진행되는 작업이 있어도 일단 프롬프트를 사용할 수 있다.

zsh 초기화 속도 테스트

설정하고 속도 테스트를 해봤는데 생각보다 빠르지는 않다. 이건 Powerlevel10k에 맞게 비동기로 설정하는 방법을 잘 몰라서 기존 bash에 쓰거나 필요한 설정을 그냥 넣었더니 쉘 초기화 속도에 영향을 미치는 것으로 보인다. 이건 이전 bash 쓸 때도 꽤 느렸기 때문에 불편하진 않지만, 천천히 zsh에 익숙해 지면서 튜닝을 해보려고 한다. 아무래도 계속 뭔가 추가하다 보면 더 느려질 테니까...

최근 bash 설정도 좀 너무 방치했더니 이상하게 된 부분이 있어서 정리가 필요했는데 이번 기회에 싹 정리하니까 좋긴 하다. zsh로 넘어가고 적응을 좀 걱정했는데 아직은 불편한 부분 없이 잘 쓰고 있다.

2020/07/29 00:49 2020/07/29 00:49

Mocha v8.0.0의 병렬 테스트

Mocha v8.0.0의 병렬 테스트

이 글은 IBM 개발자 사이트에 Mocha의 리드 메인테이너인 Christopher Hiller가 Mocha V8에 추가된 병렬 테스트 모드에 대해서 소개한 글을 번역한 글입니다. 번역은 IBM과 Christopher Hiller의 허락을 받아 진행했습니다.


Mocha는 v8.0.0 릴리스에서 Node.js에서 병렬 모드를 지원하기 시작했다. Mocha로 병렬 모드로 테스트를 실행하면 멀티코어 CPU의 이점을 얻어서 대규모 테스트 스위트에서 속도를 크게 향상할 수 있다.

Mocha 문서의 병렬 테스트 부분을 읽어보길 바란다.

v8.0.0 이전까지 Mocha는 직렬로만 테스트를 실행했다. 다음 테스트로 넘어가기 전에 테스트가 반드시 종료되어야 한다. 적은 수의 테스트 스위트에서는 결정적이고 빠르다는 장점은 있지만, 대량의 테스트를 실행할 때는 병목이 될 수 있다.

실제 프로젝트에서 어떻게 Mocha의 병렬 모드를 사용해서 장점을 얻을 수 있는지 살펴보자.

설치

Node.js v8.0.0의 생명주기가 끝났으므로 Mocha v8.0.0는 Node.js v10, v12, v14가 필요하다.

Mocha 자체를 꼭 설치할 필요는 없지만, 원하면 해도 된다. Mocha v8.0.0 이상의 버전이 필요한데 다음과 같이 설치할 수 있다.

npm i mocha@8 --save-dev


--parallel 플래그의 사용

많은 경우 mocha를 실행할 때 --parallel 지정하면 병렬 모드를 활성화 할 수 있다.

mocha --parallel test/*.spec.js

아니면 Mocha의 설정 파일을 사용해서 명령행 플래그를 지정할 수도 있다. Mocha는 기본 설정인 .mocharc.yml YAML 파일을 가지고 있고 다음과 같이 생겼다.(간결하게 일부는 제외했다.)

# .mocharc.yml
require: 'test/setup'
ui: 'bdd'
timeout: 300

병렬 모드를 활성화하려고 이 파일에 parallel: true를 추가할 것이다.

# .mocharc.yml w/ parallel mode enabled
require: 'test/setup'
ui: 'bdd'
timeout: 300
parallel: true

이후 예제에서는 명확하게 --parallel--no-parallel를 사용한다.

npm test를 실행하고 무슨 일이 벌어지는지 보자!

스포일러: 처음엔 동작하지 않는다

앗! 유닛테스트에서 기본 timeout 값(위에 나왔던 300ms)을 사용한 "timeout" 예외가 다수 발생했다.

  2) Mocha
       "before each" hook for "should return the Mocha instance":
     Error: Timeout of 300ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/Users/boneskull/projects/mochajs/mocha/test/node-unit/mocha.spec.js)
      at Hook.Runnable._timeoutError (lib/runnable.js:425:10)
      at done (lib/runnable.js:299:18)
      at callFn (lib/runnable.js:380:7)
      at Hook.Runnable.run (lib/runnable.js:345:5)
      at next (lib/runner.js:475:10)
      at Immediate._onImmediate (lib/runner.js:520:5)
      at processImmediate (internal/timers.js:456:21)

이상한 결과가 나왔다. 다시 테스트를 실행하자 다른 테스트가 "timeout" 예외를 던졌다. 왜 그런가?

Mocha에서부터 Node.js, OS, CPU에 걸친 다양한 변수로 인해 병렬 모드는 주어진 테스트에 대해 훨씬 더 넓은 범위의 타이밍을 보여준다. 이러한 timeout 예외는 새로운 성능 이슈를 보여주는 것이 아니라 자연스럽게 더 높아진 시스템 부하와 비결정적 실행 순서의 증상이다.

이를 해결하기 위해 기본 테스트 타임 만료 시간을 300ms(0.3초)에서 1,000ms(1초)로 올렸다.

# .mocharc.yml
# ...
timeout: 1000

Mocha의 "timeout" 기능은 벤치마킹용이 아니라 의도치 않게 실행에 오래 걸리는 코드를 잡아내기 위해 만들어진 것이다. 테스트 실행이 더 오래 걸릴 수 있음으로 안심하고 timeout 값을 증가시킬 수 있다.

이제 테스트가 성공했으므로 더 많은 테스트를 성공시키려고 한다.

병렬 모드 최적화

기본적으로 Mocha의 최대 잡 개수는 n-1이고 여기서 n은 기기의 CPU 코어 수이다. 이 기본값이 모든 프로젝트의 최적값은 아닐 것이다. 잡 개수도 운영체제에 따라 다르므로 "Mocha가 n-1 CPU 코어를 사용한다"를 의미하지는 않는다. 하지만, 이 값이 기본값이고 보통 기본값이 하는 일을 한다.

"최대 잡 개수"를 언급할 때 이는 Mocha가 필요하다면 이 개수만큼의 워커 프로세스를 생성할 수 있고 이는 테스트 파일의 수와 실행 시간에 따라 다르다는 의미이다.

성능을 비교해 보기 위해 익숙한 벤치마크 도구인 hyperfine을 사용할 것이다. 이를 통해 다양한 설정에 어떻게 동작하는지 알 수 있을 것이다.

hyperfine 사용법에 따라 아래 예제에서 hyperfine에 두 가지 옵션을 전달하고 있다. -r 5는 명령어를 5번 실행하라는 의미이고 기본값은 10이지만 이는 기다리기에 너무 느리다. 지정한 두 번째 옵션은 --warmup 1인데 이는 한 번의 "warmup" 실행을 뜻한다. 이 실행 결과는 버려진다.

warmup 실행은 첫 k번의 실행이 이어진 실행보다 확연히 느려서 최종 결과를 왜곡하는 것을 줄여준다. 이런 일이 벌어지면 hyperfine이 이에 관해 경고해 줄 것이다. 그래서 여기서 이 옵션을 사용하는 것이다!

직접 실행해 보려면 환경에 따라 bin/mochanode_modules/.bin/mochamocha로 바꿔야 한다. bin/mocha는 작업 폴더에서 mocha 실행 파일의 상대 경로이다.

Mocha의 통합 테스트(약 55개 파일의 260 테스트)는 보통 mocha 실행 파일의 출력을 assertion 한다. 유닛테스트보다 더 긴 timeout 값이 필요하고 아래에서 10초의 timeout을 사용한다.

직렬 모드로 통합 테스트를 실행했다. 말도 안 되는 속도에 아무도 불만을 얘기하지 않았다.

$ hyperfine -r 5 --warmup 1 "bin/mocha --no-parallel --timeout 10s test/integration/**/*.spec.js"
Benchmark #1: bin/mocha --no-parallel --timeout 10s test/integration/**/*.spec.js
  Time (mean ± σ):     141.873 s ±  0.315 s    [User: 72.444 s, System: 14.836 s]
  Range (min … max):   141.447 s … 142.296 s    5 runs

2분 이상이 걸렸다. 이를 병렬 모드로 실행해 보자. 내 환경에서는 8코어 CPU(n = 8)를 쓰고 있음으로 Mocha는 기본적으로 7개의 워커 프로세스를 사용한다.

$ hyperfine -r 5 --warmup 1 "bin/mocha --parallel --timeout 10s test/integration/**/*.spec.js"
Benchmark #1: bin/mocha --parallel --timeout 10s test/integration/**/*.spec.js
  Time (mean ± σ):     65.235 s ±  0.191 s    [User: 78.302 s, System: 16.523 s]
  Range (min … max):   65.002 s … 65.450 s    5 runs

병렬 모드를 사용해서 1분 이상인 76초를 줄였다. 거의 53%의 속도 향상이 있었다. 여기서 다 향상할 수 있는가?

Mocha가 얼마나 많은 워커 프로세스를 사용할지 정확히 지정하려고 --jobs/-j 옵션을 사용할 수 있다. 이 숫자를 4로 줄이면 무슨 일이 벌어지는지 살펴보자.

$ hyperfine -r 5 --warmup 1 "bin/mocha --parallel --jobs 4 --timeout 10s test/integration/**/*.spec.js"
Benchmark #1: bin/mocha --parallel --jobs 4 --timeout 10s test/integration/**/*.spec.js
  Time (mean ± σ):     69.764 s ±  0.512 s    [User: 79.176 s, System: 16.774 s]
  Range (min … max):   69.290 s … 70.597 s    5 runs

안타깝게도 더 느려졌다. 대신 이 숫자를 늘리면 어떻게 되는지 살펴보자.

$ hyperfine -r 5 --warmup 1 "bin/mocha --parallel --jobs 12 --timeout 10s test/integration/**/*.spec.js"
Benchmark #1: bin/mocha --parallel --jobs 12 --timeout 10s test/integration/**/*.spec.js
  Time (mean ± σ):     64.175 s ±  0.248 s    [User: 80.611 s, System: 17.109 s]
  Range (min … max):   63.809 s … 64.400 s    5 runs

기본값인 7보다는 12로 지정했을 때 약간 빨라졌다. 테스트 환경이 8코어임을 생각해 봐라. 왜 더 많은 프로세스를 만드니까 성능이 향상되었을까?

이는 테스트가 CPU에 의존하지 않기 때문으로 추측하고 있다. 테스트는 대부분 비동기 I/O를 수행하고 있음으로 CPU는 테스트를 완료되기를 기다리는 여유 사이클을 가지게 된다. 이 테스트의 500ms를 더 줄이려고 시간을 들일 수 있지만, 이 글의 목적은 아니다. 완벽함은 좋음의 적이지 않은가? 핵심은 각자의 프로젝트에 이 전력을 어떻게 적용하고 만족할 설정이 무엇인지 보여주는 것이다.

언제 병렬 모드를 피해야 하는가?

테스트를 항상 병렬로 돌리는 게 적절치 않는다고 말한다면 놀랄 것인가? 병렬 모드가 항상 좋은 것은 아니다. 두 가지를 이해해야 한다.

  1. Mocha는 개별 테스트를 병렬로 실행하지 않는다. Mocha는 테스트 파일을 병렬로 실행한다.
  2. 워커 프로세스를 생성하는 것은 공짜가 아니다.

즉 하나의 Mocha 테스트 파일이 있다면 하나의 워커 프로세스를 생성하고 이 프로세스가 파일을 실행할 것이다. 테스트 파일이 딱 하나만 있다면 병렬 모드에서 불이익을 받게 됩니다. 이렇게 하면 안 된다.

"하나의 파일"을 사용하는 일반적이지 않은 경우 외에도 테스트나 소스의 고유한 특정이 결과에 영향을 미칠 것입니다. 직렬로 테스트를 실행하는 것보다 병렬로 실행하는 것이 느린 변곡점이 있다.

실제로 Mocha 자체의 테스트(약 35개 파일의 740개 테스트)가 좋은 예시입니다. 좋은 유닛 테스트와 마찬가지로 I/O 없이 독립적으로 빠르게 실행하려고 한다. 기준선을 위해 직렬로 Mocha의 유닛테스트를 실행했다.

$ hyperfine -r 5 --warmup 1 "bin/mocha --no-parallel test/*unit/**/*.spec.js"
Benchmark #1: bin/mocha --no-parallel test/*unit/**/*.spec.js
  Time (mean ± σ):      1.262 s ±  0.026 s    [User: 1.286 s, System: 0.145 s]
  Range (min … max):    1.239 s …  1.297 s    5 runs

이제 병렬로 실행해 보자. 내 기대와 달리 결과는 다음과 같이 나왔다.

$ hyperfine -r 5 --warmup 1 "bin/mocha --parallel test/*unit/**/*.spec.js"
Benchmark #1: bin/mocha --parallel test/*unit/**/*.spec.js
  Time (mean ± σ):      1.718 s ±  0.023 s    [User: 3.443 s, System: 0.619 s]
  Range (min … max):    1.686 s …  1.747 s    5 runs

객관적으로 Mocha의 유닛 테스트를 병렬로 실행하는 것이 약 0.5초 느리다. 이는 워커 프로세스를 생성하는 부하이다.(그리고 프로세스 간의 통신을 위한 직렬화도 필요하다.)

매우 빠른 유닛 테스트를 가진 다수의 프로젝트는 Mocha의 병렬 모드의 혜택을 받지 못할 것으로 예상한다.

앞에서 작성한 .mocharc.yml를 기억하는가? 설정 파일에서 parallel: true를 제거했다. 대신 Mocha는 Mocha의 통합 테스트를 실행할 때만 병렬 모드를 사용한다.

보통 이러한 형식의 테스트와 맞지 않는 것 외에도 병렬 모드는 다른 제약사항이 있다. 아래에서 이에 관해 얘기하겠다.

주의 사항, 고지 사항, 발견한 내용

기술적인 제약(예: "이유")때문에 몇몇 기능은 병렬 모드와 호환되지 않는다. 이를 사용하면 Mocha가 예외를 던질 것이다.

추가적인 정보와 (있다면)우회방법은 문서를 확인해 봐라.

지원하지 않는 보고서

markdown, progress, json-stream 보고서를 사용하고 있다면 병렬 모드에서는 사용할 수 없다. 이 보고서들은 얼마나 많은 테스트를 실행할지 미리 알아야 한다. 병렬 모드는 이러한 정보를 가지고 있지 않다.

나중에는 달라질 수 있지만 이러한 보고서는 하위호환이 안 되는 변경을 하게 될 것이다.

단독 테스트

단독 테스트(.only())는 동작하지 않는다. 단독 테스트를 사용하면 Mocha는 .only()를 사용한 곳까지는 테스트를(.only()를 사용하지 않았으므로)를 실행하고 .only()를 만나면 중단하고 실패할 것이다.

단독 테스트는 보통 하나의 파일에서 하나의 파일에서 사용되므로 병렬 모드는 이 상황에 적합하지 않다.

지원하지 않는 옵션

--sort, --delay 옵션 특히 --file 옵션은 호환되지 않는다. 간단히 말하면 테스트를 지정한 순서로 실행할 수 없기 때문이다.

이 옵션 중 --file이 아마 가장 많은 수의 프로젝트에 영향을 줄 것이다. Mocha v8.0.0 이전에는 "루트 훅"을 정의할 때 --file을 사용하도록 추천했다. 루트 훅(beforeEach(), after(), setup() 등과 같은)은 다른 모든 파일이 상속받는다. 이 접근은 루트 훅을 이 파일에 정의하는 것으로 예를 들어 hooks.js로 Mocha를 다음과 같이 실행한다.

mocha --file hooks.js "test/**/*.spec.js"

모든 --file 파라미터는 테스트 파일로 간주하여 다른 테스트 파일(이 경우에는 test/**/*.spec.js)보다 먼저 실행될 것이다. 이를 보장해주므로 Mocha는 hooks.js에 정의된 훅으로 "부트스트랩"을 수행하고 이는 이어진 모든 테스트 파일에 영향을 준다.

이는 Mocha v8.0.0에서도 여전히 동작하지만, 직렬 모드에서만 동작한다. 하지만! 강력히 사용하지 말기를 권한다.(결국은 완전히 폐기될 것이다.) 대신 Mocha는 Root Hook 플러그인을 도입했다.

루트 훅 플러그인

루트 훅 플러그인은 mochaHooks라는 이름으로 익스포트 된 모듈(CJS나 ESM)로 이 모듈에서 사용자가 자유롭게 훅을 정의할 수 있다. 루트 훅 플러그인 모듈은 Mocha의 --require 옵션으로 로드된다.

루트 훅 플러그인 사용에 관한 문서를 읽어봐라.

위에 링크한 문서에 자세한 설명과 예시가 있지만 여기서 간단히 설명하겠다.

--file hooks.js로 로드되는 루트 훅을 가진 프로젝트가 있다고 해보자.

// hooks.js
beforeEach(function() {
  // do something before every test
  this.timeout(5000); // trivial example
});

이를 루트 훅 플러그인으로 바꾸려면 hooks.js를 다음과 같이 바꾼다.

// hooks.js
exports.mochaHooks = {
  beforeEach() {
    this.timeout(5000);
  }
};

팁: 이를 ESM 모듈로 작성할 수도 있다. 예를 들면 hooks.mjs로 만들어서 네임드 익스포트 mochaHooks를 사용하면 된다.

mocha를 실행할 때 --file hooks.js--require hooks.js로 바꾸면 된다. 간단하다!

병렬 모드 트러블 슈팅

많은 프로젝트에서 병렬 모드가 잘 동작하지만, 문제를 겪고 있다면 테스트를 준비하면서 다음 체크리스트를 참고해라.

  • 지원하는 보고서를 사용하는지 확인.
  • 지원하지 않는 플래그를 사용하지 않는지 확인.
  • 설정 파일을 다시 확인. 설정 파일에 설정된 옵션은 다른 커맨드라인 옵션과 합쳐진다.
  • ✅ 테스트에서 루트 훅을 찾는다.(문서에 나온 형태이다.) 이를 루트 훅 플러그인으로 바꾼다.
  • ✅ 사용하는 assertion, mock, 그 외 테스트 라이브러리에서 루트 훅을 사용하는가? 병렬 모드와 호환되려면 마이그레이션을 해야 한다.
  • ✅ 테스트가 예상과 달리 타임 만료가 된다면 기본 테스트 타임 만료 시간을 늘려야 할 것이다. (--timeout 사용)
  • ✅ 테스트가 특정 순서로 실행되지 않아도 괜찮은지 확인
  • ✅ 테스트가 스스로 정리하도록 해라. 임시 파일, 핸들, 소켓 등 삭제하고 테스트 파일 간에 상태나 리소스를 공유하면 안 된다.

다음은?

병렬 모드는 새 기능이고 아직 완전하지 않다. 개선할 부분이 많이 있지만 이를 위해서 Mocha는 여러분의 도움이 필요합니다. 피드백을 Mocha 팀에 보내주세요. Mocha v8.0.0을 사용해서 병렬 모드를 활성화하고 루트 훅 플러그인을 사용해 본 뒤 의견을 공유해 주세요.

2020/07/24 20:05 2020/07/24 20:05