분류 php

5 분 이내에 3 만 개 이상의 테스트를 실행하는 방법

컨텐츠 정보

  • 조회 402 (작성일 )

본문

직업 경력을 시작했을 때 단위 테스트가 어떻게 생겼는지 전혀 몰랐습니다. W3Schools에서 그 부분을 건너 뛴 것 같습니다. 첫 번째 고용주가 테스트를 사용하지 않았기 때문에 문제가 되지 않았습니다. 

SVN 저장소에 커밋을 푸시 하기 전에 로컬 개발 환경에서 임의의 페이지를 확인했습니다. 그리고 모든 배포 후 Thunderbird 이메일 클라이언트로 들어오는 예외가 코드 변경으로 인한 것이 아닌지 확인했습니다.


https://blog.mollie.com/how-to-run-over-30k-tests-in-under-5-minutes-b43907e88d51


거의 7 년 전 Mollie에 지원했을 때 작은 코딩 과제를 주었습니다. 나는 로그인 화면을 만들고 그것이 작동하는지 증명하기 위해 몇 가지 테스트를 추가해야 했다. 로그인 화면은 30 분 만에 작동했고 나머지 하루는 자동화 된 테스트로 이를 증명하는 방법을 배우고 있었습니다.


Mollie에서의 첫 주 동안 나는 테스트가 왜 그렇게 중요한지 빨리 배웠습니다. 내 인생에서 새로운 기능을 출시하기 전에 그렇게 자신감을 느낀 적은 없었습니다. Mollie가 재무 정보를 처리하고 있기 때문에 코드에 대한 확신이 있으면 매우 좋습니다.


다양한 유형의 테스트 


수년에 걸쳐 우리는 많은 새로운 기능을 추가했습니다. 그리고 새로운 기능이 있을 때마다 테스트 수도 증가했습니다. 가장 큰 응용 프로그램에는 다양한 유형의 3 만 개 이상의 테스트가 있습니다.


  • 단위 테스트 (27,687x) — 우리가 보유한 가장 큰 테스트 그룹입니다. 그들은 우리 코드베이스의 단일 단위를 테스트하며 데이터베이스와 같은 외부 서비스에 의존하지 않습니다.
  • 통합 테스트 (5,348x) — 통합 테스트에서 시스템의 더 큰 부분을 함께 테스트합니다. 이 테스트는 MySQL, Redis, RabbitMQ 및 Elasticsearch를 사용하여 모든 것이 올바르게 작동하고 올바르게 구성되었는지 확인합니다.
  • 헤드리스 테스트 (39x) —이 테스트는 종단 간 흐름이 예상대로 작동하는지 확인합니다. Mollie 애플리케이션을 제공하는 서버, 공급 업체를 조롱하는 서버, 고객 역할을 하는 서버 등 세 개의 서버를 가동합니다. 또한 헤드리스 Chromium 브라우저를 사용하여 거래를 완료하는 소비자를 모방합니다. 그런 다음 공급자에게 올바른 호출이 이루어 졌는지, 클라이언트에게 웹 후크를 올바르게 호출하는지, 그리고 더 많은 것들을 확인합니다.
  • 기타 테스트 — 특정 도구를 사용하는 더 작은 하위 시스템에 대한보다 구체적인 테스트도 있습니다. 예를 들어 프런트 엔드 자산이 올바르게 구축되었는지 테스트하고 SVG 이미지에 악성 코드가 포함되어 있지 않은지 확인합니다.

Image for post 


Git 리포지토리에 푸시 할 때마다 자체 호스팅 GitLab 인스턴스에서 모든 테스트를 실행합니다. 스승뿐 아니라 우리 지점에서 도요 이는 소프트웨어 개발자에게 즉각적인 피드백을 제공합니다. 피드백 루프를 유용하게 유지하기 위해 항상 5 분 이내에 모든 테스트를 실행하려고 노력했습니다. 수년 동안 우리는 이 5 분 목표를 달성하기 위해 테스트 파이프 라인을 계속 개선해야 했습니다. 속도를 높이기 위해 다음 기술을 사용합니다.


Paratest 


brianium / paratest 도구를 사용하여 PHPUnit 테스트를 병렬로 실행합니다. Paratest는 병렬 테스트에 대한 지원을 추가하는 PhpUnit의 확장입니다. 단위 테스트를 위해 Paratest를 추가하기 만하면 구성 없이 작동합니다!


Image for post 


통합 테스트는 외부 서비스를 사용하기 때문에 Paratest를 사용하려면 이러한 서비스도 복제해야 했습니다. MySQL 및 Redis의 경우 동일한 서버에서 별도의 데이터베이스를 사용합니다. 우리는 RabbitMQ 서비스를 완전히 모의 하기로 결정했고 Elasticsearch를 사용하면서 동시에 동일한 서버에서 여러 테스트를 실행하는 것은 문제가 되지 않았습니다.


테스트를 병렬로 실행하면 실제로 테스트 속도가 빨라지지만 이제 모든 병렬 스레드에 대해 깨끗한 데이터베이스를 설정하는 것이 병목 현상이 되었습니다. 따라서 데이터베이스 마이그레이션을 실행하여 처음부터 모든 데이터베이스를 만드는 대신 한 번만 실행하고 일부 bash foodoo를 사용하여 다른 데이터베이스에도 병렬로 mysqldump를 수행합니다.


#!/bin/bash
#
# This script sets up multiple, identical test databases that can be used by
# integration tests that run in parallel.
set -euo pipefail
# Create one test database named `mollie_test_db_1`. Yes, our parallel test
# suits start counting from 1. Don't ask.
# [...]
# Create a dump of the first database
mysqldump --host=localhost --user=root "mollie_test_db_1" > "/tmp/mollie_test_db_dump.sql"
# Import it into all the other databases. Perform the process in the background
# (with the `&` at the end) so all the imports will happen async. Store the PIDs
# so we can later wait for these processes to be completed.
pids_to_wait_for=()
for i in $(seq 2 "${nr_of_databases}"); do
mysql --host=localhost --user=root "mollie_test_db_${i}" < "/tmp/mollie_test_db_dump.sql" &
pids_to_wait_for[${i}]=$!
done
# Wait for the MySQL imports to be completed.
for pid in ${pids_to_wait_for[*]}; do
wait $pid
done
echo "Done!"


mysql 가져 오기가 완료 될 때까지 명시 적으로 기다려야 하는 어려운 방법을 배웠습니다. 과거에는 테스트 스위트 시작시 모든 테이블이 존재하지 않았기 때문에 일부 테스트가 무작위로 실패했습니다. 이것은 파이프 라인 러너가 바쁠 때만 발생하여 가져 오기에 평소보다 더 많은 시간이 걸립니다.


Parallel pipelines 


테스트를 병렬로 실행하는 또 다른 방법은 완전히 독립적 인 병렬 파이프 라인 단계를 사용하는 것입니다. 모든 유형의 테스트는 자체 단계에서 실행됩니다. 그리고 우리의 통합 테스트는 두 단계로 나뉩니다. 테스트의 1 / N 만 실행하는 사용자 지정 Paratest 실행기를 만들었습니다. .gitlab-ci.yml의 parallel 속성을 사용하여 테스트를 분할 할 단계 수를 지정할 수 있습니다.


Image for post 


MySQL 트랜잭션 


우리는 데이터베이스 트랜잭션 내에서 모든 통합 테스트를 실행합니다. 이를 통해 테스트에 의해 변경된 사항을 롤백 할 수 있습니다. 테스트를 시작하기 전에 SQL 쿼리 BEGIN을 실행하고 마지막에 ROLLBACK을 실행합니다. 이것은 변경된 모든 테이블에서 TRUNCATE를 실행하는 것보다 훨씬 빠릅니다.


느린 테스트 확인 


지정된 임계 값보다 오래 걸리는 테스트를 출력하는 오픈 소스 PHPUnit 리스너 johnkary / phpunit-speedtrap을 사용합니다. 설치 및 구성이 매우 쉽습니다. 출력은 다음과 같습니다.


Image for post 


어떤 테스트가 느린 지 알고 있으면 테스트 속도를 높이는 방법을 조사 할 수 있습니다. 외부 서비스에 대한 요청을 수행하거나 조롱해야 하는 sleep () 메서드가 있을 수 있습니다.


GitLab runner specs 


Google Cloud에서 자체 GitLab을 호스팅하기 때문에 파이프 라인 실행자에게 원하는 사양을 자유롭게 제공 할 수 있습니다. 예전에는 러너 (16 개의 vCPU 및 64GB 램)에 대해 n2-standard-16 유형의 인스턴스가 있었지만 테스트에는 CPU 만 필요하다는 것을 깨달았습니다. 16 개의 vCPU와 16GB의 램만 있는 n2-highcpu-16 유형의 인스턴스로 전환했습니다. 이를 통해 파이프 라인 속도를 전혀 늦추지 않고도 상당한 비용을 절약 할 수 있었습니다. Auto Scaling을 사용하면 사용하지 않는 리소스를 많이 사용하지 않고도 항상 러너가 새 파이프 라인을 선택할 수 있습니다.


Image for post 


파이프 라인 간 캐싱 


파이프 라인 속도를 높이는 쉬운 방법 중 하나는 가능할 때마다 캐싱을 사용하는 것입니다. 우리는 각각의 PHP 및 Node 종속성에 대해 vendor / 및 node_modules / 디렉토리를 캐시합니다.


마스터 브랜치에서는 캐시 정책 "pull-push"를 사용하므로 새 종속성도 캐시로 푸시됩니다. 브랜치의 경우 캐시 정책 "pull"을 사용하여 아직 마스터에 병합되지 않은 캐시에 종속성을 추가하는 것을 방지합니다. 단점은 파이프 라인이 브랜치에서 실행될 때 항상 새 종속성이 다운로드 되지만 새 종속성을 자주 추가하지는 않는다는 것입니다. 분기당 전용 캐시를 사용하면 새 분기에 대한 모든 첫 번째 파이프 라인이 느려지고 예를 들어 Composer 캐시 디렉터리를 캐시 하면 마스터의 모든 실행 속도가 느려집니다.


캐시를 사용할 때 이러한 캐시도 러너에 다운로드해야 한다는 점을 잊지 마십시오. 때로는 캐시를 사용하는 것보다 빠르게 무언가를 만드는 것이 더 빠릅니다.


IDE와 로컬로 실행 중인 테스트 통합 


테스트는 CI 파이프 라인에서 더 빠르게 실행되지만 로컬에서 몇 가지 테스트 만 실행할 수 있으면 많은 시간을 절약 할 수 있습니다. 대부분의 개발자는 PhpStorm을 사용하며 PHPUnit 및 xDebug와의 통합은 즉시 사용할 수 있습니다. 이는 다른 파이프 라인의 리소스를 절약하는데도 도움이 됩니다.


결론 


익스트림 프로그래밍 (XP)에서 정의한 모범 사례 중 하나는 빌드 및 테스트를 10 분 미만으로 유지하는 것입니다. Martin Fowler는 다음과 같이 씁니다.


"빌드 시간을 줄이는 매분은 각 개발자가 커밋 할 때마다 1 분이 절약되기 때문에 이를 실현하기 위해 집중적 인 노력을 기울일 가치가 있습니다." 


이 기사가 빌드 속도를 높이는 방법에 대한 좋은 제안이 되었기를 바랍니다.