Nette\Tester a opakovaná inicializace databáze

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
radas
Člen | 221
+
0
-

Ahoj,
chci testovat třídy představující modelovou část aplikace. V určitých chvílích by se mi hodilo mít databázi inicializovanou do určitého stavu. Mám proto SQL skript, který se postará o dropnutí a vytvoření tabulek a jejich naplnění nějakými daty. Tento skript pak u některých testů spouštím v konstruktoru takto:

class SettingTest extends TestCase
{
  private $container;
  private $setting;

  public function __construct(Container $container)
  {
    $this->container = $container;
    $this->setting = new Setting($container);

    $this->container->getService('database')->loadFile(__DIR__.'/../initialization.sql');
  }

  public function testMethod1...() {}
  ...

Problém je ten, pokud se inicializace spouští ve více testovacích souborech, potom to vždy skončí chybou s tím, že tabulka v databázi neexistuje. Předpokládám, že Tester asi spouští testy paralelně, jiná příčina mě zatím nenapadla. Existuje nějaký způsob jak toto chování potlačit nebo jinak docílit toho, co jsem popsal?

Díky

Filip Procházka
Moderator | 4668
+
0
-

Databázové testy by jsi měl serializovat pomocí zámku

class SettingTest extends TestCase
{
	private $container;
	private $setting;

	public function __construct(Container $container)
	{
		$this->container = $container;
	}

	protected function setUp()
	{
		Tester\Helpers::lock('db', dirname(TEMP_DIR));

		$this->setting = new Setting($container);
		$this->container->getService('database')
			->loadFile(__DIR__.'/../initialization.sql');
	}

	public function testMethod1...() {}
llook
Člen | 407
+
0
-

Taky lze pro každý test vytvářet v setUp samostatnou databázi a v tearDown ji dropnout. Takhle to dělám s Dibi (s NDB to půjde velmi podobně):

for ($i = 1; ; $i++)
{
	$dbName = $dbNamePrefix . $i;
	try
	{
		$connection->query('CREATE DATABASE %n', $dbName, ' CHARSET=utf8 COLLATE=utf8_czech_ci');
		$connection->query('USE %n', $dbName);
		return $dbName;
	}
	catch (DibiException $e)
	{
	}
}

Použití zámku na databázové testy ale bude výkonnější.

radas
Člen | 221
+
0
-

@Filip Procházka: Díky, se zámkem to funguje. Jelikož mi stačí pro celý jeden test (třídu) inicializovat databázi jednou, mohlo by stačit toto?

public function __construct(Container $container)
{
  ...
  Tester\Helpers::lock('initialization', TEMP_DIR);
  $this->container->getService('database')->loadFile(__DIR__.'/../initialization.sql');
}

S tím, že pak pro každý jednotlivý test ve třídě by byl další zámek pro samotné dotazování do databáze:

protected function setUp()
{
  parent::setUp();
  Helpers::lock('db', TEMP_DIR);
  $this->setting = new Setting($this->container);
}

Dávají takhle zámky smysl? Díky.

Editoval radas (3. 5. 2013 14:14)

Filip Procházka
Moderator | 4668
+
0
-

@radas jenže pokud testy pouštíš přes tester, tak se pro každou testovací metodu spustí samostatné vlákno. Tím že tu inicializaci oddělíš do konstruktoru nic moc nezískáš, imho to bude jenom méně přehledné (srovnej moje a tvoje).

radas
Člen | 221
+
0
-

@Filip Procházka: Pominu-li to, že inicializace DB, pokud je v metodě setUp(), způsobí delší běhu testu (nevadí), tak mi ale testy stejně neprojdou a skončí stejnou chybou jako jsem psal v prvním postu (tabulka neexistuje). Asi to záleží na tom, jak je nastavená konstanta TEMP_DIR, u mě

define('TEMP_DIR', __DIR__.'/../tmp/'.getmypid());

Což znamená, že se zámek uloží do adresáře s aktuálním vláknem/testem. Nemělo by to být tak, že by se zámek měl ukládat mimo adresáře, které vznikají pro jednotlivé procesy? V takovém případě testy prošly.

Filip Procházka
Moderator | 4668
+
0
-

Ano, máš pravdu, mělo by tam být něco jako

Tester\Helpers::lock('db', dirname(TEMP_DIR));