Také vás občas štve, že když změníte malou věc v implementaci, tak musíte předělat mnoho testů, které jsou na to navázány?

A možná jste si už kvůli něcemu takovému i říkali, zda má vůbec cenu testy psát. Vždyť to přece hronzně zdržuje! Obzvlášť když se ty testy musí každou chvilku opravovat, protože jsou tak nesmyslně křehké!

Možná jste se i zkoušeli zamyslet co s tím. Přidat mocky? Ubrat mocky? Testovat komponenty trochu jinak? Nebo možná mít robustnější API? To vše vede na různé cesty a různé pokusy končí všelijak.

Abych byl upřímný, tak mi přemýšlení o testech, jejich užitečnosti a tom, jak je napsat lépe příjde jako nekonečná pouť na konci které možná také není vůbec nic. Nebo nějaký nový UI model, který to vše udělá za nás a my o práci i přemýšlení o testech přijdeme. I tak zůstane otázkou jak testovat výstup z takového modelu a jsme u jiné formy testů, ale to jsem odběhl poněkud od tématu.

Prvním krokem, který jsem zvolil k zmenšení množství testů je testovat (to co jde) z pohledu uživatele či API. To znamená, že mou unit jednotkou není metoda nebo třída, ale celý modul. To v podstatě znamená, že kromě externích služeb (jako jsou jiné služby, DB a tak) je vše instanciované tak, jak v reálné aplikaci. Někdo dokonce nepovažuje DB za externí službu, pokud ji nasazuje vždy s aplikací.

Takže takový test z pohledu uživatele může mít za úkol něco jako zablokováný uživatel se nemůže přihlásit. Test pak může vypadat nějak takto:

Vytvoř uživatele s uživatelským jménem michal.novy
Zablokuj uživatele michal.novy
Přihlášení jako michal.novy by mělo selhat

To pak může vypadat v kódu třeba takto (Kotlin):

def `blocked user cannot perform login action`() {
	rest.post("/users") {
		formData {
			add("username", "michal.novy")
			add("password", "pass")
		}
	}
	rest.post("/users/michal.novy/block")

	rest.post("/login") {
		formData {
			add("username", "michal.novy")
			add("password", "pass")
		}
	}.andExpect() {
		status { toBeUnauthorized() }
	}

}

Pokud takových událostí však budeme mít více, tak můžeme snadno skončit tak, že přidáním pole datum narození do registrace musíme upravit 100 testů. Pojdmě tedy přidat jednu vrstvu abstrakce a upravit kód na:

def `blocked user cannot perform login action`() {
	testApi.createUser("michal.novy", "pass")
	testApi.blockUser("michal.novy")
	testApi.login("user.novy", "pass").expectToBeUnauthorized()
}

Teď si možná říkate, ale to jsme jen posunuli problém jinam. Ano! A ne! Posunuli jsme problém jinam, ale pokud nyní přidáme datum narození jako povinný údaj pro vytvoření uživatele, tak nám stačí upravit a nebo přidat jednu metodu v test API.


def createUser(username: String, password: String): TestResult {
	...
}

bude nyní vypadat takto:


def createUser(
	username: String, 
	password: String, 
	birthdate: LocalDate = LocalDate.now()
): TestResult {
	...
}

A díky tomu nemusíme měnit nic v našich testech!