GiitHub Actions에서 동시에 테스트 및 고속화(parallelism)

15137 단어 GitHub ActionsCItech
A를 테스트하는 데 5분, B를 테스트하는 데 10분, C를 테스트하는 데 3분이 걸린다고 가정합니다.
직렬로 실행하면 총 5+10+3이 18분 걸린다.
이를 테스트 A와 C만 수행하는 잡과 테스트 B만 수행하는 잡 두 부분으로 나누는데, A+C의 8분과 B의 10분이기 때문에 전체적으로 긴 10분만 기다리면 된다.
졸작 도구인 split-test를 사용하면 JUnit Formt XML에 기록된 실행 시간을 기반으로 간단하게 그룹을 나눌 수 있습니다.
https://github.com/mtsmfm/split-test
split-test --junit-xml-report-dir <xml があるディレクトリ> --node-index {{ matrix.node_index }} --node-total 2 --tests-glob 'spec/**/*_spec.rb'
처럼 실행하면 nodeindex0
spec/a_spec.rb
spec/c_spec.rb
node_index1
spec/b_spec.rb
와 같이 그룹 결과를 표준 출력으로 되돌려줍니다.
이 결과를 테스트 명령(예를 들어 rspec)에 전달하여 분할합니다.
예:
bin/rspec --format progress --format RspecJunitFormatter --out report/rspec-${{ matrix.node_index }}.xml $(./split-test --junit-xml-report-dir report-tmp --node-index ${{ matrix.node_index }} --node-total 2 --tests-glob 'spec/**/*_spec.rb' --debug)
- debug 옵션을 추가하여 시간 데이터의 분할, 실행 누락을 확인할 수 있습니다.이것은 표준 오류로 출력됩니다.
예제https://github.com/mtsmfm/split-test-example/runs/1667231492
[2021-01-08T07:12:09Z WARN  split_test] Timing data not found: /home/runner/work/split-test-example/split-test-example/spec/1_spec.rb
...
[2021-01-08T07:12:09Z DEBUG split_test] node 0: recorded_total_time: 16.439781
[2021-01-08T07:12:09Z DEBUG split_test] /home/runner/work/split-test-example/split-test-example/spec/87_spec.rb
[2021-01-08T07:12:09Z DEBUG split_test] /home/runner/work/split-test-example/split-test-example/spec/62_spec.rb
[2021-01-08T07:12:09Z DEBUG split_test] /home/runner/work/split-test-example/split-test-example/spec/53_spec.rb
...
[2021-01-08T07:12:09Z DEBUG split_test] node 1: recorded_total_time: 16.440659
[2021-01-08T07:12:09Z DEBUG split_test] /home/runner/work/split-test-example/split-test-example/spec/97_spec.rb
[2021-01-08T07:12:09Z DEBUG split_test] /home/runner/work/split-test-example/split-test-example/spec/59_spec.rb
[2021-01-08T07:12:09Z DEBUG split_test] /home/runner/work/split-test-example/split-test-example/spec/50_spec.rb
...
[2021-01-08T07:12:09Z DEBUG split_test] node 2: recorded_total_time: 16.450345999999996
[2021-01-08T07:12:09Z DEBUG split_test] /home/runner/work/split-test-example/split-test-example/spec/80_spec.rb
[2021-01-08T07:12:09Z DEBUG split_test] /home/runner/work/split-test-example/split-test-example/spec/64_spec.rb
[2021-01-08T07:12:09Z DEBUG split_test] /home/runner/work/split-test-example/split-test-example/spec/56_spec.rb
...
GiitHub Action에 직접 의존하는 것이 아니라 실행 결과를 저장하고 펼치면 어떤 CI 서비스든 사용할 수 있다.

GiitHub Action 프로세스


설정 예는 다음과 같습니다.
on: push

jobs:
  # Download test-report and save as test-report-tmp to use the exactly same test report across parallel jobs.
  download-test-report:
    runs-on: ubuntu-latest
    steps:
      # Use dawidd6/action-download-artifact to download JUnit Format XML test report from another branch
      # https://github.com/actions/download-artifact/issues/3
      - uses: dawidd6/action-download-artifact@v2
        with:
          branch: main
          name: test-report
          workflow: ci.yml
          path: report
        # Use continue-on-error to run tests even if test-report is not uploaded
        continue-on-error: true
      - uses: actions/upload-artifact@v2
        with:
          name: test-report-tmp
          path: report

  test:
    needs: download-test-report
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node_index: [0, 1, 2]
    steps:
      - uses: actions/checkout@v2
      - uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true
          ruby-version: 3.0.0
      - uses: actions/download-artifact@v2
        with:
          name: test-report-tmp
          path: report-tmp
        # Use continue-on-error to run tests even if test-report is not uploaded
        continue-on-error: true
      - run: |
          curl -L --out split-test https://github.com/mtsmfm/split-test/releases/download/v0.3.0/split-test-x86_64-unknown-linux-gnu
          chmod +x split-test
      - run: bin/rspec --format progress --format RspecJunitFormatter --out report/rspec-${{ matrix.node_index }}.xml $(./split-test --junit-xml-report-dir report-tmp --node-index ${{ matrix.node_index }} --node-total 3 --tests-glob 'spec/**/*_spec.rb' --debug)
      - uses: actions/upload-artifact@v2
        with:
          name: test-report
          path: report
          if-no-files-found: error
        # Upload test-report on main branch only to avoid conflicting test report
        if: github.ref == 'refs/heads/main'
  • main지점(기본지점)에 테스트의 실행 결과를 기록하고artifacts로 저장합니다.
  • main 지점의 테스트 기록을 복구합니다.이때 정식 동작이면 다른 지점에서 얻을 수 없기 때문에 Dawidd6/action-download-artifact@v2사용하다.https://github.com/actions/download-artifact/issues/3
  • main분지의 테스트 기록은 matrix로 설정된 각job가 완전히 동시에 이동하는 것에 국한되지 않으며, 정해진 시간에 따라main측의workflow가 이동하여 업데이트될 가능성이 있기 때문에 당분간 스냅샷을 tmp로workflow로 저장한다.
  • matrix의 각job를 설정하여 tmp측을 복원한다.
  • 기록된 정보를 바탕으로 split-test 명령을 사용하여 분할합니다.
  • main 지점에서만 실행 기록을 저장합니다.
  • 분할 수법


    내부는 탐욕법로 분할한다.
    구체적으로 말하면 모든 파일에 실행 시간을 더하고 실행 시간이 많은 순서대로 배열하며 그룹의 총 실행 시간이 가장 적다.
    엄밀히 말하면 가장 적합한 분할이 아닐 수도 있지만 대부분의 경우 queue의 시간 등이 수초~10초 엇갈리기 때문에 여기가 가장 적합하지 않아도 오차의 범주가 되어야 한다.
    실제 사용하다가 심각한 상황이 발견되면issue로 보고.

    잡담: JunIt Format XML 사투리


    JunIt Format XML이라는 표현이 있지만 여러 사투리가 있는 것 같습니다.
    그리고 split-test에 있어서 가장 중요한 테스트 파일 이름의 정보는 사실 사투리와 같다. 어디가 공식 정보인지 모르겠지만 위에 나타난 IBM Knowledge Center의 정의에는 없다.
    https://www.ibm.com/support/knowledgecenter/SSQ2R2_9.1.1/com.ibm.rsar.analysis.codereview.cobol.doc/topics/cac_useresults_junit.html
    업무상 사용하는 것이 기본적으로 루비이기 때문에 나는 그들에게 Rspec과 Minitest를 먹게 하려고 시도했지만 이 두 가지만 다음과 같다.
    rspec_junit_formatter:
    <testsuite>
      <testcase file="foo_spec.rb">
      <testcase>
    </testsuite>
    
    minitest-reporters:
    <testsuites>
      <testsuite filepath="foo_spec.rb">
        <testcase>
        <testcase>
      </testsuite>
    </testsuites>
    
    Coverage 정보 등을 고려할 때 사용하기 쉬운 일관된 서식이 필요할 수 있습니다.

    좋은 웹페이지 즐겨찾기