Zkusenost Tester i jako pomucka k ladeni + par dotazu

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

Tester + SOAP + Magento + browser tool pro spousteni testu

Uz peknych par mesicu Tester vyuzivam, sice ne s nette, ale presto nebo prave proto se chci podelit o zkusenost + mam par drobnych dotazu.

Challenge:

Vychozi stav
zadani: naprogramovat Magento modul pro obousmernou soap synchronizaci s ucetnim softwarem
znalost Magenta: 0
znalost Soap protokolu a WSDL: 0
znalost Testeru: 0
ucetni SW a dokumentace jeho API v jazyku, ve kterem neumim ani slovo

  1. Ukazalo se ze implementace SOAP v PHP+Zend a ucetni SW, potazmo nejake starsi verze knihoven ve Windows je trosku rozdil.
  2. Jak efektivne ladit co mi dela kod zpracovavajici SOAP request, kdyz ho nemuzu volat primo?

Dale magento pouziva defaultne dva ruzne log soubory pro chyby a ja navic potrebuju videt soap request i response tak jak je vidi soap server + nejake moje dumpy promennych

Reseni:

Rozhodl jsem se vytvorit jednoduchy tool, ktery simuluje windows clienta (posila soap requesty) a zobrazi mi vsechny logy. A protoze mi bylo jasne, ze takova ne zcela jednoducha implementace API by chtela i nejake testy, proc nepsat testy rovnou a nevyuzit je …

Protoze slo o jednoucelovou pomucku a dopredu jsem poradne nevedel co presne chci, tak o vygenerovani jednoduche html stranky s par odkazy se stara jeden ciste php soubor, jedna pomocna trida a Tracy. Podstatna cast kodu pro spousteni testu z prohlizece je:

	try {
		ob_start();
		require_once($testsDir . $testSet);
		$output = ob_get_flush();
		$titleClasses[] = 'passed';
	} catch (\Exception $e) {
		$exception = $e;
		$titleClasses[] = 'failed';
	}

Samotny test vkladam pomoci require a konstrukce s output buffering je pouzita proto, ze vykreslovani html stranky se odehrava az po kompletnim zpracovani testu a jakekoli chybove hlasky ci dumpy by mi znemoznily korektni odeslani headers …

diky toho pak na konci souboru muzu mit krasne citelne html a do toho jen obcas nejaky vystup z php

header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1.
header('Pragma: no-cache'); // HTTP 1.0.
header('Expires: 0'); // Proxies.
header('Content-Type: text/html; charset=utf8');
?>
<!DOCTYPE html>
<html lang="en">
<head>
	<link rel="stylesheet" media="screen,projection,tv"
	      href="/tests/css/style2.css">
	<link rel="stylesheet" media="screen,projection,tv"
	      href="/tests/css/sunburst.css">
</head>
<body onload="prettyPrint()">
<div id="topMenu">
	<span id="appName">KingSync API <span>Tester</span></span>
	<?php echo $testHelper->createTopMenu(); ?>
</div>
<div id="title">
	<H1>
		<span class="<?php echo implode(' ', $titleClasses); ?>">
			<?php echo $title; ?></span>
		<?php if ($testSet) {
			echo $testHelper->urlLink(
				$thisUrl,
				'Run again',
				null,
				'refresh'
			);
		} ?>
	</H1>
</div>
<div id="mainPanel">
	<div id="mainWrapper">
		<?php
		if ($testSet) {
			if ($output != '') {
				echo $logFormatter->formatTitle('Output buffer');
				dump($output);
			}
			if ($exception) {
				echo $logFormatter->formatTitle('Exception');
				dump($exception);
			}
			$testHelper->renderLogFile(
				$logDir . '/kingsync.log',
				$testHelper::LOG_TYPE_SPECIAL,
				$logFormatter
			);
			$testHelper->renderLogFile(
				$logDir . '/system.log',
				$testHelper::LOG_TYPE_TEXT,
				$logFormatter
			);
			$testHelper->renderLogFile(
				$logDir . '/error.log',
				$testHelper::LOG_TYPE_TEXT,
				$logFormatter
			);
		}
		if ($viewLogFile) {
			$testHelper->renderLogFile(
				$logDir . '/' . $viewLogFile,
				$testHelper::LOG_TYPE_SPECIAL,
				$logFormatter
			);
		}
		?>
	</div>
</div>
<div id="leftMenu">
	<div>
		<?php echo $logFormatter->formatTitle('Run Test sets'); ?>
		<ul>
			<?php
			foreach ($testHelper->getTests() as $file) {
				$url = $testHelper->buildUrl(['testSet=' . basename($file)]);
				echo sprintf(
					'<li>%s</li>',
					$testHelper->urlLink(
						$url,
						basename($file),
						null,
						$url == 'http://' . $hostName . $thisUrl ? 'active'
							: null
					)
				);
			}
			?>
		</ul>
		<?php echo $logFormatter->formatTitle('From logs'); ?>

pro lepsi citelnost xml data prohanim pres tidy pro odsazeni, o syntax highlighting se stara google prettify, dumpy z ladenky zobrazuju jak je tracy vygeneruje a protoze toho je obcas vic a potreboval jsem i nejake popisky co je co, tak jsem si jeste pridal do loggeru kod pro title a v mem ‚specialnim‘ log souboru to vypada treba tak nejak

2014-09-19T11:10:43+00:00 DEBUG (7):
+==+H:
FaultResponse
2014-09-19T11:10:43+00:00 DEBUG (7):
+==+X:
<?xml version="1.0" encoding="UTF-8"?><error><code>2</code><description>Access denied.</description></error>
2014-09-19T11:10:43+00:00 DEBUG (7):
+==+H:
FaultResponse
2014-09-19T11:10:43+00:00 DEBUG (7):
+==+X:
<?xml version="1.0" encoding="UTF-8"?><error><code>7</code><description>Invalid website code.</description></error>

Vysledek:

menu nalevo je vygenerovano jednoduse z obsahu slozky tests/tests – vsechny .phpt soubory
napravo se mi zobrazuji v predem danem poradi obsahy ruznych log souboru …

Pouzivam TestCases v mem pripade proto, ze pro kazdy test musim

  1. nacist konfiguraci pro dane vyvojove prostredi (local/staging/i v production je mozne testy poustet, je li treba)
  2. inicializovat SOAP clienta

Proto mam jednu abstraktni TestCase „AbstractMethod.php“ (.php, proto, at se mi nezobrazuje v menu), ktera vse potrebne pripravi v SetUp() a ma navic par pomocnych metod jako volani soap requestu, apod

Zkusenost

Naucit se psat TestCases byla diky jednoduchosti Testeru a skvelemu navodu Filipa Prochazky brnkacka. Mnohem tezsi pro mne jako ze jsem testy psal vlastne poprve bylo alespon ze zacatku rozvrhnout celou strukturu, tak aby pridavani dalsich testu byla otazka par minut.

Potom co jsem pred par dny pustil composer update, se mi zacaly testy poustet paralelne (mel jsem to predtim nejak hacknute). Podarilo se mi uspesne testy prepsat tak, ze dokonce i slozite operace, jako vytvoreni noveho produktu, uzivatele, nastaveni cenovych pravidel (vse pres soap) + otestovani, zda li magento vraci spravne ceny a nasledne zameteni stop (smazani vseho z magenta) funguje i paralelne, staci dusledne v kazdem jednotlivem testu pouzivat jedinecne identifikatory tam kde je treba.

A zatimco jsem se paralelnimu spousteni ve spojeni s magentem bal (protoze magento je samo o sobe dost narocne), zjistil jsem, ze je vlastne velmi uzitecne. V prvni fazi, mi zacaly nektere testy nahodne vypadavat s chybou „: '1|SQLSTATE[40001]: Serialization failure: 1213 Deadlock found“ Spousteny samostatne vzdy prosly. Trocha googleni napovedela, ze nejde o az tak neobvykly problem, ktery nekteri resi ruznymi hacky, primo v kodu Zend Engine (chyba), ale ktery se da vyresit predevsim spravnou architekturou aplikace, tj, kdy se co provadi.

Tj paralelni spousteni testu muze nekdy odhalit problemy, ktere pri beznem testovani jsou prakticky nezachytitelne. A diky testeru jsem je byl schopen pomerne rychle odladit, bez zadne slozite simulace zateze webu jinymi prostredky

A ted k dotazum

1. Z dokumentace jsem uplne nepochopil nahodnost spousteni testu v ramci jednoho TestCase.
Pokud neuvedu v hlavicce anotaci @testCase budou se testy spoustet po porade, tak jsou definovane ve zdrojaku nebo v jakem poradi?

(Momentalne mam jeden test, ktery bezi 30s, jedna se o nastaveni spousty veci, reindexaci, naslednou kontrolu, ktery by bylo mozne rozlozit na mensi bloky, ale zaroven, uz diky narocnosti jen samotne pripravy dat, ma smysl testovat celou sekvenci tak jak je. Rad bych tech par stovek radku rozdelil do vice metod, ale zatim je drzim v jedne)

2. Pokud porovnavam pole hodnot, bylo by mozne v pripade chyby nejak prehledne zobrazit rozdily?
chybova hlaska testeru je ted jen ve smyslu, ze se hodnoty nerovnaji (treba nejaka ascii tabulka?)

napr. vstupni data typu:

$correctPrices = [
			'testArt1' => [
				'test1customer' => [100, 100],
				'test2customer' => [100, 100],
				'test3customer' => [100, 100],
			],
			'testArt2' => [
				'test1customer' => [100, 100],
				'test2customer' => [100, 100],
				'test3customer' => [100, 100],
			],
			'testArt3' => [
				'test1customer' => [100, 100],
				'test2customer' => [100, 100],
				'test3customer' => [100, 100],
			],
			'testArt4' => [
				'test1customer' => [100, 100],
				'test2customer' => [100, 100],
				'test3customer' => [100, 100],
			],
		];
		Assert::equal(
			null,
			$this->verify_prices(
				$correctPrices,
				$this->productIds,
				$this->customerIds
			)
		);

3. Myslite si ze by se obecny nastroj pro spousteni testu z prohlizece hodil jeste nekomu, nebo to byl spis hodne specificky problem, ktery jsem resil?

Editoval LeonardoCA (19. 9. 2014 16:13)

Milo
Nette Core | 1283
+
+1
-

To je pěkný use case pro Tester. K otázkám:

ad 1)
Testovací metody se spustí v tom pořadí, jak je vrátí ReflectionObject::getMethods().

Každá metoda ale má být chápána jako samostatná jednotka a na pořadí bys neměl lpět. Pokud opravdu mají být testy spuštěny za sebou, musí být zasebou. Přesto si můžeš pomoci:

$testCase = new MyTestCase;
$testCase->run();  # spustí všechny metody

# Anebo jednu po druhé ručně. Ale tady se snadno zapomene.
$testCase->run('testOne');
$testCase->run('testTwo');
$testCase->run('testThree');

ad 2)
Když aserce selže, v adresáři s testem se vytvoří složka output. V ní najdeš soubory *.actual a *.expected s plným dumpem. Také výstup Testeru zobrazí řádek:

diff /cesta/k/dumpu.actual /cesta/k/dumpu.expected

PS: Testy se z PhpStormu dají spustit i jednodušeji.

LeonardoCA
Člen | 296
+
0
-

diky za odpovedi

ad 1) super, tohle reseni me nenapadlo, ze ma byt kazda metoda chapana jako samostatna jednotka je mi jasne, ale presto si myslim, ze je misto pro vyjimky, ktere potvrzuji pravidlo

v mem pripade

  • samotna priprava dat trva mozna 10s
  • pak nastavuju postupne 4 ruzne pravidla, ale jsou dulezite i jejich vzajemne kombinace (proto testuju soucasne na vetsi mnozine dat)
  • po kazde zmene pravidel musi probehnout reindexace (paralelni reindexace by se mohly bit, nebo by na sebe musely cekat)
  • priprava dat s ohledem na paralelni processing by byla v tomto pripade mnohem narocnejsi

ad 2) timhle jsem se zabyval nejaky patek zpatky se starsi verzi testeru (<1.0), tak si ted nevybavuju jestli mi tehdy tester diff vyhazoval, ale mozna jsem resil, proc mi nemuze tester ty dumpy rovnou zobrazit, treba s nejakym prepinacem?

add PS) jj, v phpStormu si je taky muzu poustet, akorat mi nezobrazi obsah log souboru, ta ma potreba pri ladeni sledovat nekolik log souboru byla asi hodne specificka

Editoval LeonardoCA (19. 9. 2014 17:26)

Milo
Nette Core | 1283
+
+1
-

Pročetl jsem si Tvůj první post znovu a některé věci pochopil jinak :)

ad 1) super, tohle reseni me nenapadlo, ze ma byt kazda metoda chapana jako samostatna jednotka je mi jasne, ale presto si myslim, ze je misto pro vyjimky, ktere potvrzuji pravidlo

Pokud tu test metodu chceš rozdělit pouze pro přehlednost, můžeš to rozdělit do více metod (třeba privátních), které nezačínají názvem test. Tester si jich nebude všímat.

ad 2) timhle jsem se zabyval nejaky patek zpatky se starsi verzi testeru (<1.0), tak si ted nevybavuju jestli mi tehdy tester diff vyhazoval, ale mozna jsem resil, proc mi nemuze tester ty dumpy rovnou zobrazit, treba s nejakym prepinacem?

Můžeš nahradit Assert::onFailure() callback. V něm dostaneš AssertException a s ní $e->actual, $e->expected. Pak si můžeš vypsat, co chceš.

Pokud Testy spustíš z příkazové řádky Testerem, může pomoci –setup a přes něj vlastní OutputHandler.

Pokud chceš v testu zjistit, jestli byl spuštěn Testerem nebo napřímo jako PHP skript, testuj proměnnou prostředí NETTE_TESTER_RUNNER, resp. Environment::RUNNER.

LeonardoCA
Člen | 296
+
0
-

Pokud tu test metodu chceš rozdělit pouze pro přehlednost, můžeš to rozdělit do více metod (třeba privátních), které nezačínají názvem test. Tester si jich nebude všímat.

Nj, to ale pomuze jen vizualnimu oddeleni kodu, ale stejne zas nebudu mit ve vypise okamzite dostupnou informaci ve kterem kroku neco neproslo :(

Můžeš nahradit Assert::onFailure() callback. V něm dostaneš AssertException a s ní $e->actual, $e->expected. Pak si můžeš vypsat, co chceš.

Pokud Testy spustíš z příkazové řádky Testerem, může pomoci –setup a přes něj vlastní OutputHandler.

Pokud chceš v testu zjistit, jestli byl spuštěn Testerem nebo napřímo jako PHP skript, testuj proměnnou prostředí NETTE_TESTER_RUNNER, resp. Environment::RUNNER.

nekdy zkusim

Editoval LeonardoCA (21. 9. 2014 19:04)

Milo
Nette Core | 1283
+
0
-

Nj, to ale pomuze jen vizualnimu oddeleni kodu, ale stejne zas nebudu mit ve vypise okamzite dostupnou informaci ve kterem kroku neco neproslo :(

Pak už nevím, co chceš. Ikdyž máš test špageťák, tak při selhání dostaneš indicie, která aserce selhala, ne?

LeonardoCA
Člen | 296
+
0
-

chtel jsem jen trivialni vec, kdyz bych mel 4 testy, kazdy jiny nazav, tak at jdou po sobe ve stejnem poradi, a z nazvu testu poznam se kterym pravidlem je problem, asserce se lisi jen v hodnotach, coz si muzu dohledat, ktera z nich to je, ale nepoznam to na prvni pohled

tj tohle reseni vyhovuje tomu co chci

# Anebo jednu po druhé ručně. Ale tady se snadno zapomene.
$testCase->run(‚testOne‘);
$testCase->run(‚testTwo‘);
$testCase->run(‚testThree‘);

Editoval LeonardoCA (22. 9. 2014 16:40)