테스트 데이터베이스 관리

21380 단어 databaseperltesting

문제.
테스트 데이터베이스는 오류가 발생하기 쉽다.간단해.몇 십 년 전, 내가 처음으로 테스트를 배웠을 때, 팀은 테스트 데이터베이스를 공유했다.만약 다른 개발자가 동시에 테스트를 실행한다면, 두 개의 테스트 세트는 모두 실패할 것입니다.그러나 우리가 사용하는 데이터베이스는 개인 허가증을 위해 비용을 지불해야 하기 때문에 우리는 유한한 일을 할 수 밖에 없다.
나중에 저는 MySQL을 사용하는 회사에서 일했고 복잡한 트리거 시스템을 만들어서 모든 데이터베이스 변경 사항을 추적했습니다.이것은 내가 테스트 실행을 시작해서 업무를 위조하고 지난번에 변경된 내용을 보고 자동으로 이 변경 사항을 복구할 수 있게 한다.그것의 장점은 여러 개의 데이터베이스 핸들이 서로의 변경 사항을 볼 수 있다는 것이다. (만약 단독 업무가 있다면 많은 데이터베이스에 대한 변경이 매우 어렵다.)그것은 다른 어떤 것보다 단점이 있다. 그것은 약하고 느리다.
나중에 저는 xUnit 프레임워크eventually writing a new one를 사용하기 시작했는데 이것은 대규모 테스트 솔루션이 필요한 회사에서 유행했습니다.이렇게 하면 모든 테스트 클래스는 하나의 단독 업무에서 쉽게 운행되고 운행 과정에서 정리될 수 있다.사무를 사용하면 좋은 격리를 제공할 수 있고 데이터베이스에서 이미 잘하는 기능을 이용하여 여러 종류를 병행적으로 실행할 수 있습니다.
그러나 삽입식 사무 논리를 파괴하기 쉽다.너는 모든 것이 같은 데이터베이스 핸들을 공유하는 것을 보장해야 한다. 그리고 코드에서 진정으로 업무를 테스트할 수 없다. 그리고...
결국 나를 붕괴시킨 것은 Minion job queue를 사용하여 고객을 위해 코드를 작성하는 것이다.대기열은 신뢰할 수 있지만, 데이터베이스 업무의 어떤 내용도 볼 수 없도록 새로운 데이터베이스 연결을 만들 것입니다.나는 해커의 해결 방안을 생각해 냈지만, 나는 해커의 해결 방안에 싫증이 났다.
내가 해결 방안을 연구할 때, 마테 트라우트 (Matt Trout) 는 왜 테스트에 사용되는 데이터베이스 사무 방법이 깨졌는지 다시 한 번 내게 일깨워 주었다.임시 테스트 데이터베이스를 만들어 사용하고 완성되면 버리면 된다.

거래처
나를 고용하고 싶은 회사가 나에게 기술 테스트를 주었는데, 한 가지 임무는 Catalyst web application 응용 프로그램에 간단한 기능을 추가하는 것이다.이것은 보잘것없는 것이다.그들은 나에게 유랑자 서류를 건네주었는데, 빠른 vagrant upvagrant ssh 후에 나는 준비를 시작했다.그리고 나는 그들이 컨트롤러에 대한 테스트를 보았다.
use strict;
use warnings;
use Test::More;

use Catalyst::Test 'Client';

ok( request('/some_path')->is_success, 'Request should succeed' );
done_testing();
이 작업은 URL에 대한 링크POST를 포함합니다.내가 추가한 기존 기능은 테스트가 없지만, 내가 작성한 어떤 테스트도 데이터베이스 상태를 변경할 것을 의미한다.코드를 여러 번 실행하면 데이터베이스에 쓰레기를 남긴다.나는 여러 가지 방법으로 이 문제를 처리하지만, 나는 동적으로 빠른 데이터베이스를 구축하고, 이를 기록하고, 그 다음에 그것을 처리할 때가 됐다고 결정했다.이 분야의 코드는 매우 간단하다.
package Test::DB;

use File::Temp qw(tempfile);
use DBI;
use parent 'Exporter';
use Client::Policy;

BEGIN {
    if ( exists $INC{'Client/Model/ClientDB.pm'} ) {
        croak("You must load Test::DB before Client::Model::ClientDB");
    }
}
use Client::Model::ClientDB;

our @EXPORT_OK = qw(test_dbh);

my $connect_info = Client::Model::ClientDB->config->{connect_info};
my $dsn          = $connect_info->{dsn};
my $user         = $connect_info->{user};
my $password     = $connect_info->{password};

# $$ is the process id (PID)
my $db_name = sprintf 'test_db_%d_%d', time, $$;
my ( $fh, $filename ) = tempfile();

my $dbh = DBI->connect(
    $dsn, $user, $password,
    { RaiseError => 1, AutoCommit => 1 } 
);
$dbh->do("CREATE DATABASE $db_name");
system("mysqldump -u $user --password=$password test_db > $filename") == 0
  or croak("mysqldump failed: $?");
system("mysql -u $user --password=$password $db_name < $filename") == 0
  or croak("importing schema to mysql failed: $?");

# XXX We’re being naughty in this quick hack. We’re writing
# this back to the Model so that modules which use this connect 
# to the correct database.
$connect_info->{dsn} = "dbi:mysql:$db_name";

# This is just a quick hack to get tests working for this code.
# A catastrophic failure in the test means this might not get
# run and we have a junk test database lying around.
# Obviously we want something far more robust

END { $dbh->do("DROP DATABASE $db_name") }

sub test_dbh () { $dbh }

1;
위의 내용은 여러모로 장난이 심하지만 고객은 내가 테스트로 돌아가는 속도가 하나의 요소일 수도 있다는 것을 암시한다(또는 그들이 없어서 내가 신호를 잘못 읽었다).그들은 코드가 완벽한지 아닌지가 아니라 내가 문제를 어떻게 처리하는지에 관심을 갖는다고 분명히 밝혔다.그래서 나는 내가 안전지대에 있다고 생각한다.이것은 내가 테스트에서 이 점을 할 수 있다는 것을 의미한다.
use strict;
use warnings;
use Test::More;
use lib 't/lib';
use Test::DB;

use Catalyst::Test 'Client';

ok( request('/some_path')->is_success, 'Request should succeed' );

# anything I do here is against a temporary test database
# and will be discarded when the test finishes

done_testing();
Test::DB 코드를 작성하는 것은 빠르고 간단하며 나에게 있어서 안전하게 작성하는 테스트는 매우 간단하다.나는 매우 기쁘다.

Test:DB에 무슨 문제가 있습니까?
초보 개발자에게 Test::DB는 아주 좋아 보일 수도 있다.경험이 있는 개발자에게 이것은 매우 나쁘다.그럼 어떻게 해야 생산 준비에 더 가까워질 수 있을까요?
다음은 내가 고려할 몇 가지 일이다.

향상된 데이터 검증
먼저 접속 정보를 살펴보겠습니다.
my $connect_info = Client::Model::ClientDB->config->{connect_info};
my $dsn          = $connect_info->{dsn};
my $user         = $connect_info->{user};
my $password     = $connect_info->{password};
이 내용은 Catalyst가 DBIx::Class(Perl ORM) 모델을 설정하는 빈도에 따라 다릅니다.
package Client::Model::ClientDB;

use strict;
use base 'Catalyst::Model::DBIC::Schema';

__PACKAGE__->config(
    schema_class => 'Client::Schema::ClientDB',
    connect_info => {
        dsn      => 'dbi:mysql:test_db',
        user     => 'root',
        password => 'rootpass',
    }
);
이 클래스를 불러오면 config 클래스 방법을 얻을 수 있습니다. 클래스가 어떻게 설정되었는지 알려 줍니다.그러나 Test::DB 측면에서 데이터의 구조가 나의 기대에 부합된다는 것을 보장할 수 없다.따라서 이 데이터를 검증하고 변경 사항이 발생할 때 즉시 이상을 던져야 합니다.
우리는 어떻게 테스트 데이터베이스를 만듭니까?
$dbh->do("CREATE DATABASE $db_name");
system("mysqldump -u $user --password=$password test_db > $filename") == 0
  or croak("mysqldump failed: $?");
system("mysql -u $user --password=$password $db_name < $filename") == 0
  or croak("importing schema to mysql failed: $?");
CREATE DATABASE 명령 속도가 빠르기 때문에 나는 이 점을 걱정하지 않는다.테스트는 표 한 장뿐이고 데이터가 매우 적기 때문에 이것은 매우 빠르다.그러나 Tau Station에 대해 우리는 수백 개의 표와 대량의 데이터를 가지고 있다.이것은 매우 느릴 것이다.어떤 상당히 성숙한 시스템에 대해서 말하자면, 매번 데이터베이스를 저장하는 것은 나쁜 생각이다.여러 번 데이터를 저장하는 것을 쉽게 피할 수 있는 방법도 있지만 다음 문제인 새로운 테스트 데이터베이스에 데이터를 추가하는 데 영향을 줄 수 있다.모든 테스트는 이렇게 해야 한다. 그러나 이것은 네가 쉽게 가속화할 수 있는 일이 아니다.
더 튼튼한 시스템에 대해 로컬 데이터베이스 서비스를 만들 수도 있습니다. 테스트 데이터베이스를 만들어서 기다리기만 하면 됩니다.테스트는 다음 테스트 데이터베이스를 요청하고 서비스는 등록 데이터베이스를 획득하며 테스트가 실행될 때 백엔드에 새로운 테스트 데이터베이스를 생성합니다.이 서비스는 귀하가 적절하다고 생각하는 어떤 정책에 따라 오래된 테스트 데이터베이스를 정리할 수도 있습니다.

멀리서 행동이 없다
이 말은 엉망이다.
$connect_info->{dsn} = "dbi:mysql:$db_name";
유효한 이유는 config 중의 Client::Model::ClientDB 데이터는 전역적이고 가변적이기 때문이다$connect_info는 이 데이터에 대한 인용일 뿐이다.반대로 만약에 내가'데이터베이스 서비스'가 있다면 코드가 어떤 데이터베이스를 사용할 수 있는지 알려주면Test::DB 이 서비스를 호출할 수도 있고 호출할 수도 있다Client::Model::ClientDB.모든 것은 전체적인 변수를 해결하는 것이 아니라 단일한 진실의 근원에 의존한다. 망치지 않기를 바란다.

테스트 데이터베이스를 삭제하지 마십시오.
만약에 많은 테스트 시스템에서 내가 싫어하는 것이 있다면 그것은 테스트가 심각하게 실패하는 것을 보고 있지만 데이터베이스가 정리(또는 삭제)되고 테스트가 끝난 후에 나는 실제 데이터를 볼 수 없다는 것이다.내가 자주 하는 일은 테스트에 실패할 때까지 코드를 실행하고 데이터베이스 핸들을 잡고 이런 방식으로 데이터를 검사하는 것이다.엉망진창이다.
여기서 우리는 이 줄을 간단하게 삭제함으로써 이 문제를 해결할 수 있다.
END { $dbh->do("DROP DATABASE $db_name") }
매번 테스트가 시작되고 끝날 때마다 우리는 diag 데이터베이스 이름을 테스트할 수 있다. 만약에 데이터베이스에 문제가 있는지 확인해야 한다면 나는 여전히 그것을 사용할 수 있다.Dell 데이터베이스 서비스는 다음과 같은 코드로 데이터베이스를 배치합니다.
다음 날
  • 다음 파일럿
  • 데이터베이스 임계값 초과
  • ... 혹은 당신이 필요로 하는 모든 것들
  • 요컨대, 디버깅을 위해 가치 있는 데이터를 보존한다.

    빠른 데이터베이스 개발
    데이터베이스 서비스 솔루션은 데이터베이스 변경 관리 전략과 결합해야 한다.나는 데이터베이스 변경을 관리하기 위해 sqitch를 대량으로 사용하고 이상한 변두리 상황을 지원하기 위해 대량의 코드를 작성했다.코드를 작성해서 데이터베이스 서비스가 로컬 버전 sqitch 과 동기화되었는지 확인하는 것은 어렵지 않습니다.사용자가 어떤 데이터베이스 변경 관리 정책을 사용하든지 간에 데이터베이스 서비스를 정확하게 자동화하기 위해 발견할 수 있는 것이 필요합니다.
    물론, 너는 이것이 명백히 알 수 있다고 생각한다.그러나, 나는 고객과 몇 번이나 합작한 적이 있습니까? 그들의 데이터베이스 변경 관리 전략은 SQL 파일 한 무더기를 열거하고, 그들의 mtime 를 검사해서 데이터베이스에 어떤 파일이 적용되어야 하는지 확인하면 놀랄 것입니다.아이구!

    빠른 테스트
    만약 잘했다면, 너의 테스트도 더욱 빨라졌을 것이다.너는 너의 코드를 초과해서 이미 있는 업무 비용을 초과하지 않을 것이다.그리고 너는 이런 문제를 피할 수 있다.
    sub test_basic_combat_attack_behavior ($test,$) {
        my $ovid    = $test->load_fixture('character.ovid');
        my $winston = $test->load_fixture('character.winston');
        my $station = $test->load_fixture('station.tau-station');
    
        $test->move_to($station->port, $ovid, $winston);
        ok !$ovid->attack($winston),
          'We should not be able to attack someone on the home station.';
        ...
    }
    
    위에서 우리는 몇몇 장치를 탑재하고 있다.때때로, 이 장치들은 매우 복잡해서, 그것들을 적재하는 데 시간이 필요하다.클라이언트에서 $test->load_all_fixtures('connection');를 실행할 때, 이것은 실행해야 할 모든 테스트에 몇 초의 추가 시간을 증가시킬 것이다.
    반대로 미리 구축된 테스트 데이터베이스는 테스트 클러치를 불러올 수 있다.또한 데이터베이스를 미리 채우면 코드가 빈 데이터베이스를 처리하는 것이 아니라 실제 세계에 가까운 문제를 처리하는 데 도움이 되며 오류를 초래할 수 있는 구석진 사례를 포착하지 않습니다.

    결론
    하나의 데이터베이스 서비스를 사용하면 임시 테스트 데이터베이스만 줄 수 있습니다. 데이터베이스를 엉망으로 만들거나 테스트 중의 업무를 관리하거나 테스트 중에 악성 해커를 만나 이런 문제를 해결할 염려가 없습니다.가장 중요한 것은 코드를 바꾸지 않은 행동입니다.너는 평상시처럼 데이터베이스를 사용하기만 하면 된다.이 데이터베이스를 만드는 데는 더 많은 초기 작업이 필요할 수도 있지만, 이것은 가치가 있다.
    나는 언젠가 적당한 데이터베이스 도구를 만들 수 있기를 정말 바란다. 이렇게.오늘은 그날이 아니다.하지만 짧은 몇 분 안에 완성된 빠른 해독도 테스트 코드를 이렇게 간단하게 만들어 줘서 기쁘다.나는 진작에 이렇게 했어야 했다.

    좋은 웹페이지 즐겨찾기