Jeden z nejdůležitějších aspektů, jeden z nejméně testovaných.
Prolog
Představte si modelovou situaci:
- Zpřístupníte světu webovou aplikaci, kterou jste pečlivě ze všech
stran otestovali a jste si jistí, že funguje správně.
- Ze začátku o ni není velký zájem, ale těch pár lidí, co ji občas
použije, si ji nemůže vynachválit a tak časem začne uživatelů
přibývat. Word of mouth.
- Pak se stane něco divného. Nějaká data se ztratí, nějaký uživatel
dostane cizí data, něco se uloží dvakrát apod.
- Zaboha nemáte tušení, čím to bylo. Nakonec to svedete na skvrny na
slunci a pustíte to z hlavy.
- S rostoucí popularitou podobných situací začne přibývat.
- Jak podobých incidentů začne přibývat, začne se šířit pověst
o nespolehlivé aplikaci. Popularita a důvěra klesá. Word
of mouth.
Takhle můžete dopadnout, když nevěnujete dostatečnou pozornost tomu, co
se všechno může stát, pokud k vaší aplikaci přistupuje více lidí
zároveň.
Na co dávat pozor
Pozor je potřeba dávat vždy, když přistupujete k nějakým
sdíleným zdrojům. Sdílené zdroje = cokoli mimo kontext
aktuálního HTTP požadavku. Příkladem sdílených zdrojů jsou hlavně
soubory a databáze, ale zdaleka nejen.
V některých prostředích, včetně ASP.NET, také statické členy
tříd. Přestože pro každý požadavek se vytváří nová instance stránky,
nový HttpContext a spousta dalších věcí, samotné třídy jsou v celé
aplikaci sdílené (rozdíl od PHP). Lze snadno ověřit:
<%@ Page Language="C#" Inherits="System.Web.UI.Page" %>
<script runat="server">
static string Blah = "Default value.";
</script>
<html>
<body>
<p>
<%= Blah %>
<% Blah = "Changed value."; %>
</p>
</body>
</html>
Při prvním spuštění vypíše „Default value“, při každém dalším
obnovení stránky „Changed value“, až do restartu aplikace. Ovšem lze
kód upravit tak, aby ten člen byl „statický“ jen v rámci aktuálního
požadavku:
static string Blah
{
get { return HttpContext.Current.Items["Blah"] as string; }
set { HttpContext.Current.Items["Blah"] = value; }
}
HttpContext.Current je sice také statická property, ale
používá různé instance HttpContext podle toho, z kterého ji voláme
threadu.
Přístupy ke sdíleným zdrojům je nutné nějak synchronizovat. Aby
například během procesu zápisu do souboru (od otevření až do zavření),
žádný jiný thread nemohl ten soubor číst. Ale to teď nechám být, teď
mi jde jenom o odhalení těch přístupů.
Dávat pozor nestačí
Jenže dávat pozor nestačí.
Často si nemusíte uvědomit, že zdroj, ke kterému přistupujete, je
sdílený. Někdy to ani nemusí být moc zřejmé (třeba nějaké knihovny
třetích stran můžou používat statické členy vnitřně). Nebo si
nemusíte uvědomit, že některá operace není atomická.
Před třemi lety bych vůbec nepřemýšlel, jestli PHP funkce
file_put_contents() je nebo není atomická. Kdyby mě to bylo
napadlo, tak bych býval během chvilky splácl jednoduchý test,
ale mě to ani nenapadlo… Udělal jsem za tu dobu dostatečný pokrok, abych
se mohl spolehnout jen na svůj úsudek? Na to bych nespoléhal.
Epilog
Logicky tedy vyvstává potřeba nějak testovat chování aplikace při
více uživatelích naráz. Zkoušel jsem najít nějaké nástroje a postupy,
které by v tomhle pomáhali, ale asi jsem špatně hledal.
Představuju si to asi tak, že nadefinuju uživatelské kroky, které pak
nechám provést třeba 50 robotů zároveň.
Nakonec jsem si vytvořil takovou jednoduchou utilitku sám. Používá
Příkazový řádek, Selenium a spoustu
Firefoxů… Až udělám nějaké screenshoty, sepíšu o tom článek a
hodím to sem.
Ale už teď mě zajímá: Testujete nějak chování aplikace pod palbou
uživatelských akcí, ještě před jejím vypuštěním?