분류 php

MySQL 시뮬레이션으로 더 빠른 애플리케이션 테스트

컨텐츠 정보

  • 조회 122 (작성일 )

본문

웹 기반 응용 프로그램이 있는 경우 정보를 저장하기 위해 일종의 데이터베이스를 사용할 가능성이 높습니다. 어떻게 든 해당 응용 프로그램을 테스트하고 싶을 가능성이 높습니다.


그런 다음 대답해야 하는 질문 중 하나는 "데이터베이스를 쿼리 하는 것을 어떻게 테스트합니까?"입니다.


옵션 1 : 데이터베이스 상호 작용을 모의 API로 제한 


유혹적인 첫 번째 단계는 높은 수준의 API 호출을 모의하는 것입니다. getCurrentUser 메소드가 있는 경우 모의는 메소드가 적절한 데이터가 있는 객체를 반환하는지 확인할 수 있습니다.


이것은 일부 간단한 CRUD 응용 프로그램에 적합하지만 간단한 SELECT <column> FROM <table> WHERE name = <value> 쿼리를 넘어 MySQL 기능을 사용하려는 경우 문제가 됩니다.


옵션 2 : 데이터베이스 호출을 직접 모의 처리 


다음과 같이 할 수 있습니다.


function testHighScores() { 
    $db_connection = new MockPDO();
    $db_connection->receiveQuery(
        'SELECT `name`, MAX('score') as `high_score`
         FROM `game_scores`
         GROUP BY `name
         ORDER BY `high_score` DESC'
    )->andReturn([
        ['name' => 'Kathleen', 'score' => 6700],
        ['name' => 'Will', 'score' => 6200],
        ['name' => 'Matt', 'score' => 2300],
    ]);
    assertNotEmpty(
      (new ScoreRepository($db))->getHighScores()
    );
}


테스트 중에 애플리케이션이 실제로 데이터베이스와 상호 작용하지 않기 때문에 속도가 빠릅니다. 모든 것을 조롱하면 테스트가 단위 테스트가 무엇인지에 대한 플라토닉 이상을 준수하는 데 도움이 됩니다. 테스트 중인 각 구성 요소를 효과적으로 분리 할 수 ​​있습니다.


그러나 지루함은 빠르게 설정 될 수 있습니다. 쿼리가 변경되거나 기본 테이블 스키마가 변경되면 모의 쿼리 결과가 부실해집니다. 빠르게 성장하는 회사의 경우 유지 관리 문제가 발생합니다.


옵션 3 : 실제 데이터베이스에 대해 테스트 실행 


실제 데이터베이스에 대해 테스트를 실행하여 오래된 문제를 피할 수 있습니다.


function createScoresTable($db_connection) {
    $db_connection->query(
        'DROP TABLE IF EXISTS `game_scores`'
    );
    $db_connection->query(
        'CREATE TABLE `game_scores` (
            `id` INT(10) NOT NULL AUTO_INCREMENT,
            `name` VARCHAR(16) NOT NULL DEFAULT '',
            `score` INT(10) NOT NULL,
            PRIMARY KEY (`id`)
        ) ENGINE=InnoDB'
    );
    $db_connection->query(
        'INSERT INTO `game_scores` (`name`, `score`)
           VALUES ("Matt", 20), ("Matt, 1200),
           ("Matt", 2300), ("Kathleen", 6700),
           ("Will", 6200), ("Will", 4800)'
    );
}
function testHighScores() { 
    $db_connection = new PDO(getConnectionString());
    
    createScoresTable($db_connection);
    // do some sort of test
    assertNotEmpty(
        (new ScoreRepository($db))->getHighScores()
    );
  
    // clean up after test
    $db_connection->query('TRUNCATE `game_scores`);
}


이로 인해 몇 가지 새로운 문제가 발생합니다. 데이터베이스 스키마를 복제하는 방법, 테스트 데이터를 생성하는 방법, 테스트가 완료되면 데이터베이스를 스크럽 하는 방법을 파악해야 하지만 그렇지 않으면 상당히 안정적입니다.


Vimeo에서 10 년 이상 동안 대부분의 PHP 테스트에는 데이터베이스 연결이 필요했습니다. 이것은 조롱과 관련된 몇 가지 문제를 피했지만 꽤 느립니다.

수년 동안 우리는 메모리 내 저장소를 사용하고 테스트 데이터베이스를 배치하고 테스트 결과를 캐싱하여 테스트를 더 빠르게 수행 할 수 있는 여러 가지 방법을 고안했지만 전체 테스트 실행은 여전히 ​​전체 런타임의 50 %를 소비합니다. MySQL과 대화.


실제 데이터베이스를 사용하는 것이 왜 그렇게 느린가요?


  • 데이터베이스 연결을 설정하고 외부 서비스와 통신하려면 많은 디지털 관료주의가 필요합니다. 이는 가상화 된 환경 (Mac 용 Docker 내부 등)에서 테스트를 실행할 때 특히 두드러집니다.
  • 데이터베이스는 일반적으로 많은 정보를 보유하고 해당 정보를 오랫동안 보유하도록 설계되었습니다. 그러나 테스트 할 때 일반적으로 각 테스트 사이에 데이터베이스를 지우고 테스트는 일반적으로 실제 프로덕션 데이터베이스가 보유 할 데이터의 아주 작은 부분을 작성합니다.


이러한 느린 속도로 인해 네 번째 옵션을 탐색하게되었습니다.


옵션 4 : MySQL 자체 시뮬레이션 


테스트와 동일한 언어로 작성된 라이브러리를 사용하여 MySQL 쿼리를 구문 분석하고 실행하여 실제 데이터베이스의 동작을 모방 할 수 있다면 어떨까요? 이 라이브러리는 테스트 런타임의 일부가 되어 프로세스 간 통신을 피할 수 있습니다.


function testHighScores() { 
    $db_connection = new FakePdo(getConnectionString());
    
    // create tables with SQL queries as above
    createScoresTable($db_connection);
    // do some sort of test
    assertNotEmpty(
        (new ScoreRepository($db))->getHighScores()
    );
  
    // clean up after test (as you would with a real database)
    $db_connection->query('TRUNCATE `game_scores`);
}


두 달 전에는 라이브러리가 존재하지 않았습니다. 적어도 PHP 용은 아니었습니다. 운 좋게도 비슷한 것이 있었습니다. Slack은 단위 테스트 속도를 높이기 위해 PHP에 인접한 언어로 작성된 자체 MySQL 엔진을 만들었습니다.


Slack의 테스트 스토리는 Vimeo의 테스트 스토리와 약간 다릅니다. 처음에는 테스트를 실행할 때 모든 데이터베이스 쿼리 결과를 모의하기로 선택했습니다. 2015 년에는 몇 가지 간단한 SELECT / UPDATE 쿼리를 시뮬레이션 하기 시작했지만 더 복잡한 구문 (예 : 조인이 있는 쿼리)에는 여전히 조롱이 필요했습니다.


2017 년에는 이러한 쿼리를 조롱하는 유지 관리 부담이 너무 많이 들기 때문에 Slack은 MySQL 시뮬레이션에 올인하기로 결정했고 그곳의 개발자는 그들이 던지는 모든 MySQL 쿼리를 지원하는 엔진을 만들었습니다.


엔진이 작성되는 언어 인 Hack은 PHP와 비슷할 정도로 코드를 대략적으로 PHP로 변환 할 수 있습니다. 2 년 전에 이 목적을 위해 장난감 변환기를 만들었습니다. 휴일에는 Slack의 MySQL 엔진에서 트랜스 파일러를 사용하고 PHP와 잘 어울리도록 조정했으며 Vimeo에서 사용하는 여러 MySQL 기능 (예 : 변수 및 자동 증가 인덱스)을 추가했습니다.


지루한 이름의 PHP MySQL Engine 인이 라이브러리는 이제 오픈 소스입니다. 테스트의 99 %가 실제 데이터베이스 대신 사용합니다. 전체 테스트 실행 중에 간단한 SELECT`id` FROM`foo`부터 기본적으로 MySQL 구문 분석 및 실행의 최종 보스 인 1,100 줄의 거대 기업에 이르기까지 모든 것을 처리합니다.


주의 사항 


테스트를 위해 무엇이든 시뮬레이션 할 때마다 시뮬레이션이 약간 떨어져서 실제로 실패해야 하는 테스트를 통과 할 위험이 있습니다. 또한 MySQL을 정확히 복제하는 것도 매우 어렵습니다 (예 : 계산 된 열 유형에 대한 MySQL의 추론이 때때로 약간 잘못됨).


PHP 엔진이 지원하지 않는 MySQL 기능도 많이 있습니다. 저장 프로 시저, 트리거 또는 뷰에 의존하는 모든 기능은 시뮬레이션 할 수 없습니다.


MySQL을 시뮬레이션 하기로 결정한 사람은 먼저 실제 MySQL 데이터베이스에 대해 모의를 철저히 평가하고 테스트를 실행했는지 확인해야 합니다. 발을 식히고 싶다면 이 기사를 읽으십시오.


혜택 


훨씬 더 빠릅니다. CI에서 단위 테스트 실행은 실제 MySQL 데이터베이스를 사용할 때 보다 평균 2 배 더 빠릅니다.


더 좋은 점은 순수 PHP 테스트 환경에서 더 이상 가상화가 필요하지 않기 때문에 이전에 Docker에서 모든 PHPUnit 테스트를 실행해야 했던 Mac 개발자의 경우 4 ~ 5 배 더 빠릅니다.


PHP가 MySQL (고도로 최적화 된 C 바이너리)보다 어떻게 더 빠를 수 있는지 궁금하다면 MySQL 서버에 연결하여 데이터를 전송하고 응답을 기다리는 오버 헤드가 수십만 번에 달하는 것입니다. 전체 테스트 실행.


제 희망은 이 성능 향상이 테스트를 좀 더 재미있게 만드는 것입니다.


다음은 어디? 


PHP MySQL Engine에 대한 우리의 작업은 적어도 지금은 기본적으로 완료되었습니다. 완벽하지는 않지만 우리가 해야 할 일을 정확히 합니다.


테스트 속도를 높이는 것 외에도 이 엔진을 향후에 사용할 수 있는 몇 가지 다른 사항이 있습니다.


  • 쿼리 파서는 프로덕션 환경에서 소량의 SQL 쿼리를 분석하고 데이터베이스가 실제로 어떻게 사용 되는지에 대한 정보를 수집하는 데 사용할 수 있습니다. 이 데이터는 데이터베이스 분할에 대한 결정을 내릴 수 있습니다.
  • SQL 문자열을 사용하여 결과 집합에 대해 메모리 내 필터링을 수행 할 수 있는 쿼리 엔진을 만드는 데 사용할 수 있습니다 (유사한 방식으로 메모리 내 JOIN을 수행 할 수도 있음).
  • 엔진의 CREATE TABLE 파서는 데이터베이스 스키마에 대한 진실 소스로 SQL 파일을 사용하는 ORM의 일부로 사용될 수 있습니다.

https://medium.com/vimeo-engineering-blog/the-great-pretender-faster-application-tests-with-mysql-simulation-26250f13d251