Github Actions으로 CI를 진행할 떄, 여러가지 환경에 대해 테스트를 진행할 필요가 있을 수 있다. 예를들면 노드 버전이 14일 때와 16일 때 모두를 테스트하고 싶을 수 있다. 이런 경우 matrix strategy를 사용하여 같은 Job에 대해 여러 환경에서 테스트를 진행해볼 수 있다. 또한, matrix strategy가 적용된 Job에서는 matrix Context와 strategy Context를 사용하여 필요한 정보들을 가져올 수 있다.
매트릭스 전략은 하나의 Job 정의만 사용하면서 여러가지 변수들의 조합을 사용한 실행이 가능하도록 해준다. 예를들자면 앞에서 얘기했던 것처럼 하나의 Job에 대해 여러 OS 환경이나 언어 버전을 사용하여 테스트를 진행할 수 있게 해준다.
jobs.<job_id>.strategy.matrix
구문을 사용하여 여러가지의 Job 설정의 매트릭스를 정의할 수 있다. 매트릭스 내부에서 배열을 원소로 가지는 한개 이상의 변수들을 설정해줄 수 있다. 이렇게 변수를 설정해주면, 정의된 변수들의 가능한 모든 조합의 수만큼 Job을 실행해준다. 아래 예시를 보자.
jobs:
test-job:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14, 16]
steps:
- use: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
test-job의 matrix strategy로 node-version
변수에 원소가 14와 16인 배열을 할당한 것을 볼 수 있다. 그렇다면 matrix에 정의된 변수들의 모든 가능한 조합의 수는 node-version
이 14인 경우와 16인 경우로 2개이다. 즉, test-job은 총 2번 실행되며, 각 실행에서 node-version
에는 14와 16이 들어가게 된다.
가장 아랫부분을 보면 ${{ matrix.node-version }}
구문을 볼 수 있는데, 이것이 matrix Context가 사용된 부분이다. matrix Context는 matrix.<변수명>
의 형식으로 사용하며, 이를 통해 각 실행에서 변수에 할당된 값을 가져올 수 있다.
jobs:
test-job:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, window-latest]
node-version: [14, 16]
steps:
- use: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
이번 예시에서는 2개의 변수에 대해 각각 길이가 2인 배열을 할당해주었다. 이 경우 가능한 조합의 수는 다음과 같다.
즉, 경우의 수는 총 4이기에 test-job은 총 4번 실행된다. 또한 각 실행에서 os
와 node-version
에는 위 4가지 경우가 각각 할당된다.
jobs.<job_id>.strategy.matrix.include
구문을 사용하여 매트릭스 설정을 확장하거나 새로운 설정을 추가할 수 있다. include
는 오브젝트의 배열을 값으로 가진다.
include
배열의 각 오브젝트는 key: value
의 형식의 페어들을 가진다. 이 페어들과 기존 매트릭스 조합을 비교한 뒤, 기존 매트릭스 조합에 페어들을 추가하거나 혹은 새로운 매트릭스 조합을 만든다. 그 기준은 다음과 같다.
또한 기존의 매트릭스 조합의 값은 덮어쓰지 않지만, include
로 인해 추가된 값은 덮어써질 수 있다.
strategy:
matrix:
피자: [페퍼로니, 불고기]
사이드: [감자튀김, 치즈볼]
include:
- 음료: 콜라
- 피자: 불고기
할인: 10%
- 피자: 하와이안
설명만으로는 이해하기 어려우니 예시를 보자. 우선 기존 매트릭스 조합의 모든 값은 다음과 같다.
이제 include
구문을 보자. include
는 배열의 앞부터 차례대로 값들을 검사한다. 그러니 우선 음료: 콜라
부터 검사를 해보자. 앞에서 설명했던 기준 중 2번째에 속한다. 해당 오브젝트의 모든 페어들의 키, 즉 음료
와 일치하는 키가 기존 매트릭스 조합에는 없다. 그러니 모든 기존 매트릭스 조합에 추가한다.
이제 피자: 불고기, 할인: 10%
를 보자. 이번에는 첫번째 기준에 속한다. 피자: 불고기
라는 페어가 기존 매트릭스 조합 중 3번째와 4번째 조합에 일치하는 것을 알 수 있다. 즉, 해당되는 조합에 추가한다.
마지막으로 피자: 하와이안
를 보자. 이 경우는 3번째 기준에 속한다. 피자
라는 키는 기존 매트릭스 조합이 가지고 있지만, 하와이안
이라는 값을 가지는 조합은 전혀 없다. 그러니 새로운 매트릭스 조합으로 추가한다.
위 조합이 최종 결과물이다. 이런식으로 include
를 사용하여 기존 매트릭스 조합에 특정 값을 추가하거나 아예 새로운 조합을 추가할 수 있다.
strategy:
matrix:
fruit: [apple, pear]
animal: [cat, dog]
include:
- color: green
- color: pink
animal: cat
- fruit: apple
shape: circle
- fruit: banana
- fruit: banana
animal: cat
조금 더 어려운 예시를 보자. 기존 매트릭스 조합은 다음과 같다.
color: green
은 2번째 경우이다. 모든 조합에 대해 추가된다.
color: pink, animal: cat
은 첫번째 경우이다. animal
이 cat
인 조합에만 추가된다. 또한 앞에서 설명한 것처럼 include
로 인해 추가된 값의 경우 덮어쓰기가 가능하기 때문에 기존 color: green
을 덮어쓴다.
fruit: apple, shape: circle
은 첫번째 경우이다.
fruit: banana
은 세번째 경우이다. 일치하는 것이 전혀 없다. 그러니 새로운 조합으로 추가해주자.
fruit: banana, animal: cat
역시도 세번째 경우이다. 이전 단계에서 추가된 fruit: banana
와 일치하지만, 이것은 기존의 매트릭스 조합이 아닌 추가된 매트릭스 조합이다. include
는 새롭게 추가된 매트릭스 조합 혹은 값이 아닌 기존의 매트릭스 조합에 대해서만 비교한다. 그래서 세번째 경우로 판단하는 것이다.
최종 결과물은 위 조합이 된다.
strategy:
matrix:
피자: [페퍼로니, 불고기]
include:
- 음료: 콜라
- 피자: 불고기
음료: 사이다
- 할인: 50%
음료: 콜라
바로 이전 예시에서 언급했던 것처럼 include
의 비교는 기존 매트릭스 조합에 대해 수행한다. 이것에 대해 예시를 통해 조금 더 알아보자.
음료: 콜라
와 피자: 불고기, 음료: 사이다
를 거치고 난 이후의 매트릭스 조합은 위와 같다. 그렇다면 이후에 할인: 50%, 음료: 콜라
를 검사하면 어떻게 될까? 위 매트릭스 조합을 사용하여 검사한다면 음료가 콜라인 조합에 할인: 50%
가 추가될 것이다.
그러나 실제 실행 결과는 위와 같다. 기존 매트릭스 조합에는 음료
라는 키를 가지는 페어가 없기 때문에 2번째 경우에 해당되고, 모든 조합에 대해 페어가 추가된다. 또한, 새롭게 추가된 값은 덮어쓰기가 된다고 했으니 음료: 사이다
는 덮어쓰기되어 음료: 콜라
가 된다.
include
는 기존 매트릭스 조합에 대해서만 검사한다는 점을 주의해두자.
exclude는 include와 반대로 기존 매트릭스 조합들 중 일부를 제거하는데 사용한다. exclude에 정의된 오브젝트와 부분적으로 일치하는 조합이 있다면, 해당 조합을 제거한다.
strategy:
matrix:
os: [macos-latest, windows-latest]
version: [12, 14, 16]
environment: [staging, production]
exclude:
- os: macos-latest
version: 12
environment: production
- os: windows-latest
version: 16
위 예시에서 기존 조합의 개수는 12개이다. 그러나 exclude
에 정의된 오브젝트와 일치하는 조합을 제거하면 9개가 된다.
우선 첫번째 오브젝트와 완벽히 일치하는 조합을 1개 제거할 수 있다.
다음으로 두번째 오브젝트인 os: windows-latest, version: 16
과 부분적으로 일치하는 2개의 조합을 제거할 수 있다.
참고로 include
는 exclude
가 완료된 이후에 진행된다. 즉, exclude
를 통해 제거된 조합이 include
단계에서 추가될 수도 있다.
strategy:
max-parallel: 2
matrix:
os: [ubuntu-latest, window-latest]
node-version: [14, 16]
기본적으로는 Github는 가용한 모든 러너를 사용해서 Job들을 병렬적으로 수행한다. 하지만 matrix strategy가 사용된 Job에서는 max-parallel
을 통해 동시에 실행할 Job의 개수를 설정해줄 수 있다. 위 예시에서는 동시에 실행될 수 있는 Job의 개수가 최대 2개이다.
strategy:
fail-fast: true
matrix:
os: [ubuntu-latest, window-latest]
node-version: [14, 16]
fail-fast
가 true
로 설정되어 있는 경우, 어느 한 Job이 실패했을 때 다른 모든 Job들을 캔슬시킨다. 기본값은 true
이다.
이렇게 설정한 Matrix Strategy들을 워크플로우 파일에서 다루기 위해서는 Context를 사용하면 된다. matrix Context와 strategy Context를 사용해서 매트릭스 전략으로 설정한 값들을 활용할 수 있다.
이전에 설명했던 것처럼 matrix.<변수명>
의 형식으로 사용할 수 있다. 각 Job 실행마다 할당된 값들이 저장되어 있다.
strategy Context는 현재 실행되고 있는 Job의 매트릭스 실행 전략에 관한 정보들이 들어있다.
strategy.fail-fast
strategy.max-parallel
strategy.job-index
strategy.job-total
위 4가지 속성을 가지고 있으며, fail-fast
와 max-parallel
은 각각 설정한 값들이 들어가있다.
job-index
는 매트릭스 조합들에서 현재 실행되고 있는 job의 인덱스이다. 첫번째 job의 인덱스는 0이다. job-total
은 매트릭스 조합의 총 개수이다.
Matrix Strategy는 여러가지 환경에서 테스트를 진행해볼 필요가 있을 때 매우 좋은 기능이 될 것이다. 잘 활용하여 중복된 코드를 줄이고 더 편리한 CI 환경을 구축해보자.