Jak testovat, že něco nehodí výjimku?

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

Ahoj,
jak správně a čistě otestovat, že kód nevyhodí chybu/výjimku?
Hodilo by se něco jako Assert::noError(callback), bohužel nic takového v testeru nevidím.

Use case: mám konstruktor přijímající nějakou strukturu parametrů, u některých povoluji více typů a typovost kontroluji až uvnitř konstruktoru a v případě chybného parametru házím InvalidArgumentException.

Příklad: mám třídu pro testování presenterů, kterou můžu nakrmit buď objektem presenteru, nebo stringem – jménem třídy presenteru, nebo callbackem (factory metodou):

	public function __construct($presenterCreator)
    {
        if (!($presenterCreator instanceof Presenter || is_string($presenterCreator)
            || is_callable($presenterCreator))) {
			throw new \Nette\InvalidArgumentException(
				"Presenter must be Presenter object, class name or callback factory");
		}
		...
    }


... // třída TestCase:
	public function testConstructor() {
		new PresenterTester('\App\HomepagePresenter');
		new PresenterTester(new HomepagePresenter);
		new PresenterTester(function() { return new HomepagePresenter; });
		Assert::true(TRUE); //aby tester neřval, že se nespustil žádný assert
	}

Chci test, který mi ověří, že mám parametry dobře, a tedy když předám do konstruktoru povolený typ, tak to projde (třeba kdybych v definici udělal překlep v názvu třídy Presenter, test to zjistí). Udělat to nějak není problém, ale jak na to čistě? mě napadá:

  • neřešit, jak je příklad výše – pokud vyletí chyba nebo výjimka, test stejně failne, ale není to moc elegantní, testy by měly v tom, co testují, failovat na assertech a neházet neošetřené výjimky.
  • udělat něco jako Assert::type('PresenterTester', new PresenterTester(...)) – to se mi nelíbí, je to hromada zbytečného boilerplatu a testovat, že konstruktor vrací správný typ, je divné.
  • hodit pull request do Testeru, kde přidám podporu Assert::noError(callback, description), který failne, pokud zabalený kód hodí výjimku nebo běhovou chybu, ale failne čistě assertem, nevyletí výjimka/chyba ven. Zároveň je to podle mě sémanticky nejlépe čitelné řešení. Rád uslyším názory, než se do toho pustím.
looky
Člen | 99
+
-2
-

exception, error

EDIT Ah, nějak jsem přeskočil to „NEhodí“

Editoval looky (1. 9. 2015 0:28)

David Matějka
Moderator | 6445
+
+1
-

Tak noError assert by mel jeste vice boilerplate kodu, ale je zas pravda, ze by to bylo vic expresivni. A plus by slo asi pridat nejakou @noError anotaci podobne jako je anotace @throws

Vetsinou to resim jako ty, tedy vykonat kod a na konci assert::true

@looky to je opak toho, co chce :)

Tharos
Člen | 1030
+
0
-

David Matějka napsal(a):

Vetsinou to resim jako ty, tedy vykonat kod a na konci assert::true

Namísto toho můžeš test zahájit voláním Tester\Environment::$checkAssertions = FALSE. Je to malinko méně WTF, než Assert::true. :)

David Matějka
Moderator | 6445
+
0
-

@Tharos to bych si musel vzdycky slozite vzpominat, ze je to v Environment :)


A lepsi nez noError anotace bude treba @noAssert

Milo
Nette Core | 1283
+
0
-

Možná by něco ve stylu noError() přišlo vhod, hlavně z pohledu čitelnosti.

Tohle je navíc trochu specifické, protože testuješ konstruktor. U běžné metody bys testoval návratovou hodnotu a nečekaná chyba/vyjímka by ukončila test, tak jak se Ti to teď nepozdává.

Ještě mě napadá:

Assert::error(function () {
	...
	...
	...
}, []);
llook
Člen | 407
+
+1
-

Obojí je IMHO WTF. Já používám Environment::$checkAssertions = FALSE, protože to tak používáme ve firmě. Assert::true(TRUE) by mi neprošlo přes review (vyzkoušeno). Ale pokud vím, tak v PHPUnit se běžně používá assertTrue(true), je to sice podivný, ale celkem zažitý zápis. Jedno z toho si vyber a to používej.

Kdo pamatujete SimpleTest (PHP4), tak ten k tomu míval metodu pass(), která inkrementovala počet splněných asercí. To by se mi v Testeru taky líbilo…

amik
Člen | 118
+
0
-

Jasně – jeden důvod je ten, že chci čistě zapsat, že test nepotřebuje Assert. na to by Environment::$checkAssertions stačilo.

Druhý důvod je expresivita (můj návrh je právě metoda, kterou explicitně řeknu, že něco nemá hodit výjimku, a ne jen že prostě pustím test a tím implicitně čekám, že nespadne).

Třetí důvod je taky ten, že když test selže na Assertu, hodí to hezkou custom message (třeba ‚Constructor of PresenterTester should accept instance of Presenter, but \SomeException was thrown‘).

Použili byste sami takovou věc, nebo zůstali u starého checkAssertions?

Milo
Nette Core | 1283
+
0
-

@amik PR je vítán, viz. Github

Michal Vyšinský
Člen | 608
+
+1
-

Ahoj, osobně testuji „nevyhození“ vyjímky tak, že obalím testované volání try-catch blokem a v catch zavolám Assert::fail('Exception was thrown.').