PHP + Circle CI Test Performance 개선 경험 공유

이 포스트는 PHPUnit 테스트 성능 개선에 대한 방법과 노하우를 공유하기 위해 작성했습니다.

PHPUnit 테스트가 느리신가요?

PHPUnit 은 단위 테스트에 가장 널리 사용되고 있는 PHP 테스트 프레임워크입니다. PHPUnit은 Laravel 기본 테스트 프레임워크로 설정되어 있습니다.

Laravel에서 기본적으로 설치되어 있는 PHPUnit 테스트 프레임워크를 통해 테스트를 진행하고 있으며 CI / 배포 툴인 CircleCI 환경에서 테스트를 진행하고 코드를 병합하고 있습니다. CI (Continuous Integration) 는 지속적인 코드를 통합하는 과정(빌드와 테스트)을 통해 저장소의 최신 상태로 유지해야합니다. 테스트 코드가 늘어남에 따라 테스트 시간이 증가되어 코드 통합이 지연되는 현상이 발생하였습니다.

PHPUnit 및 CircelCI 환경에서 테스트 성능 개선에 대한 방법을 공유하고자 합니다. 성능 개선을 진행한 환경은 아래와 같습니다.

  • PHP Version 7.2
  • PHPUnit Version 8.5
  • Xdebug 2
  • CircleCI

Code Coverage Tool Optimization

Code Coverage

코드 커버리지는 특정한 소스 코드가 실행되어 질 때 사용되는 정도를 측정하는 것입니다. 코드 커버리지 퍼센티지가 높아질수록 버그 및 휴먼 에러에서 벗어나 어플리케이션의 안정성을 확보할 수 있습니다.

코드 커버리지 측정 방법에 따라 테스트 성능이 결정되기도 합니다. 다수 측정 방법이 있지만 PHPUnit 코드 커버리지 툴에서 사용되는 방법을 살펴보았습니다. State Coverage(Line Coverage) 같은 경우 라인 실행 여부로 측정하며 Path Coverage 는 모든 분기 처리에 대한 코드 커버리지를 측정합니다. 모든 분기를 처리하는 Path Coverage의 경우에는 속도가 보다 느릴 수 밖에 없습니다.

PHPUnit에서 주로 사용되는 코드 커버리지 툴에는 Xdebug, PHPDBG, PCOV 이 있습니다. 각각의 툴로 테스트를 진행해 보았습니다.

Xdebug

Xdebug는 오랫동안 사용되었고 가장 널리 쓰이는 디버거와 코드 커버리지 툴로 사용되어진 PHPExtension입니다. 복잡하고 거대한 기능들이 내포되어 있으며 Path Coverage로 커버리지를 측정하기에 Xdebug를 통해 테스트를 진행하는 경우 테스트 속도가 느린 것을 확인할 수 있습니다.

Xdebug 3이 최근 릴리즈(2020-11-25 릴리즈) 되었습니다. 개선된 내용 중에서는 코드 커버리지 테스트 속도 개선 내용도 포함되어 있습니다만 코드 커버리지 방법은 동일하기에 개선된 효과는 미미합니다. 또한 PHP 7.4 버전 이하는 지원하지 않기에 개선 대상에서 포함되지 않습니다.

성능 개선 전 환경(PHPUnit + Xdebug 2)에서 테스트를 진행한 결과 소요시간 31.02 분, 메모리 사용량 68.00 MB 으로 측정됩니다.

PHPDBG

PHPDBG는 PHP에 내장된 디버거와 코드 커버리지 툴입니다. extension 추가 없이 테스트 진행할 수 있습니다. Xdebug 와 비슷한 특징을 가지고 있습니다. phpdbg 테스트 결과는 소요시간 21.48 분, 메모리 사용량 76.00 MB 으로 측정됩니다. Xdebug 2로 진행한 테스트 보다 10분 정도 성능 개선이 되었으나 메모리 사용량이 다소 늘어난 것을 확인할 수 있습니다.

PCOV

PCOV 은 코드 커버리지 성능 개선에 초점을 맞춰 개발된 PHP Extension입니다. Path Coverage는 지원하지 않으며 Xdebug 디버거 로드시 오버헤드를 제거하였습니다. CI에서 테스트 진행하기 적합해보입니다. PCOV 테스트 결과는 소요시간 15.94 분, 메모리 사용량 64.00 MB 입니다. Xdebug를 사용한 테스트보다 50% 정도 성능 개선을 확인할 수 있습니다.

# Xdebug 2
./vendor/bin/phpunit -v 

# PHPDBG
phpdbg -qrr ./vendor/bin/phpunit -v 

# PCOV
php -d pcov.enabled=1 ./vendor/bin/phpunit -v

# 성능이 가장 빠른 PCOV
- run: sudo pecl install pcov
      - run: sudo docker-php-ext-enable pcov
      - run: sudo rm /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
      - run:
          name: PHPUnit
          command: php -d pcov.enabled=1 ./vendor/bin/phpunit -v

Parallel Testing

CircleCI 기능 중에 테스트 코드를 병렬로 진행할 수 있는 제공되어 있습니다. 동작 원리는 단순합니다. 병렬로 테스트할 컨테이너 개수를 설정하고 테스트 파일을 분류한 뒤 각각의 컨테이너를 동작시키면서 테스트를 병렬로 진행합니다. 병렬로 테스트할 수 있는 ParaTest 라이브러리도 사용가능합니다만 CI Tool에서 제공하는 병렬 처리를 사용하여 개선하려고 합니다.

테스트 파일 분리

CircleCI CLI 은 테스트 파일을 나눠주는 기능을 제공합니다. 파일이름 / 파일사이즈 / 테스트 수행 시간으로 테스트파일들을 분리할 수 있습니다. 물론 파일의 물리적인 주소로 나눠주는 기능이 아니라 단순 테스트 파일명으로 나눠주는 기능이다. PHPUnit 에서는 Group 지정으로 유저가 원하는 테스트들을 분리하여 테스트할 수 있는 기능이 존재합니다. 필요에 맞게 설정하면 됩니다.

circleci tests glob "tests/Feature/**/*.php" "tests/Unit/**/*.php" | circleci tests split

# TEST 1
#
# test/Features/A/A.php
# test/Features/A/B.php
# test/Features/B/A.php

# TEST 2
#
# test/Features/A/C.php
# test/Features/B/B.php

CircleCI 병렬 테스트 코드 작성

위의 테스트 파일 분리 커맨드를 사용하여 아래와 같이 테스트 코드를 실행시켜줍니다. TESTS 라는 변수에는 각각 컨테이너별로 다른 테스트 파일들이 설정됩니다.

build:
    docker:
      - image: circleci/php:7.2-node-browsers
    parallelism: 2 
    steps:
      - checkout
      - setup
      - run:
            name: PHPUnit
            command: |
              TESTS=$(circleci tests glob "tests/Feature/**/*.php" "tests/Unit/**/*.php" | circleci tests split)
              mkdir ./tests/temp
              for xx in ${TESTS};do cp -r -p $xx ./tests/temp/; done
              ls ./tests/temp -lRs
              php -d pcov.enabled=0 ./vendor/bin/phpunit -v ./tests/temp

CircleCI 병렬로 처리한 결과 기존 빌드 시간 38m 56s 초에서 ms 로 % 줄어든 것들 확인할 수 있습니다. 줄어든 빌드 시간이 제법 만족스럽니다.

테스트 속도 향상을 얻었지만 아직 병렬 처리에서 개선할 점은 분명 존재합니다. 현재 컨테이너 간의 테스트 속도 차이는 거의 존재하지 않습니다. 하지만 코드 비대칭, 테스트 분류 비대칭의 문제로 컨테이너 간의 속도 차이가 발생할 수 있습니다. 이와 같은 문제가 발생하는 경우에는 각각의 컨테이너간의 테스트 시간을 최대한 일정하게 맞추는 작업이 필요합니다. 테스트 파일 분류시 직접 커스텀 하는 방법도 있으며 timing data를 통해 테스트를 분류하는 방법도 고려할만 합니다.

TL; DR

  • PHP 테스트 퍼포먼스를 향상 시키고자 하실때 PCOV 라이브러리를 사용하여 확실한 효과를 볼 수 있습니다.
  • 또한, 병렬 처리로 테스트 퍼포먼스를 향상시키는 방법도 고려해봅시다.

답글 남기기

이메일 주소는 공개되지 않습니다.