Všetci vývojári webovských aplikácií vedia, že session tracking je kritickou súčasťou aplikácií vyžadujúcich sledovanie stavu medzi jednotlivými požiadavkami od klienta. Keďže HTTP je bezstavový protokol, neposkytuje žiadny mechanizmus, ktorý by umožnil identifikovať jednotlivé požiadavky od toho istého klienta. Našťastie existuje niekoľko možností, ako riešiť tento problém. Pravdepodobne najflexibilnejšie je session tracking servlet API, ktorého správnu funkčnosť budeme tentoraz testovať.

Session tracking API poskytuje šikovné mechanizmy na manažovanie informácií o klientovi, spracovávaných serverom. Každému unikátnemu klientovi je serverom priradený objekt javax.servlet.http.HttpSession. Tento objekt predstavuje priestor, v ktorom sú uložené informácie medzi jednotlivými klientskými požiadavkami. Keď je objekt HttpSession prvýkrát vytvorený, je mu serverom priradené unikátne ID. Použitie cookies a/alebo prepisu URL (najlepšie obidvoch), je spôsob akým si server a klient toto ID vymieňajú.

Pri každej požiadavke klienta si server toto ID priradí ku konkrétnemu session objektu. Server môže do aktuálnej session pridávať alebo z nej odoberať objekty, v závislosti od aktuálnych požiadaviek. Podotýkam, že session je možné aj vynulovať, pričom často je z bezpečnostných dôvodov nastavený aj timeout na vypršanie platnosti session. Viac informácií o sledovaní session v súvislosti so servletmi nájdete v seriály Java Servlets.

Prejdeme postupne na vytváranie príkladov, na ktorých si ukážeme testovanie funkčnosti sledovania session. Často využívaným príkladom na vysvetlenie použitia objektu HttpSession, je elektronický obchod. Zdá sa, že sa tomu nevyhnem ani ja. Dôvod je vcelku jednoduchý a to jednoduchosť celého riešenia. Ako prvé si vytvoríme servlet, ktorý nebude tak celkom úplný. Jeho úlohou bude pridávať/odoberať položky z nákupného košíka.

ShoppingCartServlet.java:

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class ShoppingCartServlet extends HttpServlet {
 public static final String INSERT_ITEM = „insert“;
 public static final String REMOVE_ITEM = „remove“;
 public static final String REMOVE_ALL = „removeAll“;
 public static final String INVALID = „invalid“;
 public static final String CART = „cart“;
 protected void doGet
           (HttpServletRequest req,HttpServletResponse res)
                      throws ServletException,IOException {
  HttpSession session = req.getSession(true);
  ShopCart cart = (ShopCart) session.getAttribute(CART);
  if (cart == null) {
   cart = new ShopCart();
   session.setAttribute(CART,cart);
  }
  updateShoppingCart(req,cart);
  session.setAttribute(CART, cart);
 }
 protected void updateShoppingCart
                     (HttpServletRequest req,ShopCart cart)
                                   throws ServletException {
  String operation = getOperation(req);
  if (INSERT_ITEM.equals(operation)) {
   // @todo – implementovať pridanie položky do košíka
  } else if (REMOVE_ITEM.equals(operation)) {
   // @todo – implementovať odobratie položky z košíka
  } else if (REMOVE_ALL.equals(operation)) {
   // @todo – implementovať odobratie všetkých položiek z košíka
  } else {
   throw new ServletException(„Nepovolená operácia: “ +
                                                 operation);
  }
 }
 protected String getOperation(HttpServletRequest req) {
  String operation = req.getParameter(„operation“);
  if (operation == null || „“.equals(operation)) {
   return INVALID;
  } else { if (!INSERT_ITEM.equals(operation)
      && !REMOVE_ITEM.equals(operation)
      && !REMOVE_ALL.equals(operation)) { return INVALID; }
   return operation;
  }
 }
}

Pri volaní metódy doGet() požiadame objekt HttpServletRequest o aktuálnu session. Značka „true“ znamená, že v prípade ak session zatiaľ nebola vytvorená, sa nová session vytvorí. Po získaní session zistíme, či obsahuje nákupný košík (ShopCart). Ak nie, vytvoríme ho a vložíme do session pod menom cart. Nasleduje volanie metódy updateShoppingCart(), ktorá má za úlohu vykonať update nákupného košíka. Samotnú implementáciu logiky tejto metódy som zatiaľ vynechal, vďaka čomu test neprejde, ako uvidíme ďalej. Po vykonaní testov pridáme príslušný kód, ktorého funkčnosť následne otestujeme.

Predtým ako napíšeme samotný test case, vám predstavím ešte dve pomocné triedy ShopCart a Item. Prvá z nich reprezentuje nákupný košík, ktorý môže obsahovať nula alebo viac položiek triedy Item. Tieto dve triedy nie sú závisle od serverového kódu a môžu byť použité v ľubovolnom inom projekte. Preto by mohli byť testované mimo servera, napríklad pomocou JUnit.

ShopCart.java:

import java.util.*;
public class ShopCart {
 private Map cart = new HashMap();
 public void addItem(Item item) {
  this.cart.put(item.getID(),item);
 }
 public void removeItem(String itemID) {
  this.cart.remove(itemID);
 }
 public Item getItem(String id) {
  return (Item)this.cart.get(id);
 }
 public Iterator getAllItems() {
  return this.cart.values().iterator();
 }
 public void clear() {
  this.cart.clear();
 }
}

Item.java:

public class Item {
 private String id;
 private String description;
 public Item(String id,String description) {
  this.id = id;
  this.description = description;
 }
 public String getID() {
  return this.id;
 }
 public String getDescription() {
  return this.description;
 }
}

Ako vidíte samotné triedy sú veľmi jednoduché a dúfam aj pochopiteľné. Teraz už môžeme/musíme napísať samotný test. Podotýkam, že v súčasnom stave nám tento test neprejde. To je tiež pozitívne, pretože si tým overíme skutočnosť. Ak by nám v tomto stave test prešiel, niekde by musel byť problém.

Test neprešiel

TestShoppingCartServlet.java:

import org.apache.cactus.ServletTestCase;
import org.apache.cactus.WebRequest;
public class TestShoppingCartServlet extends ServletTestCase {
 private ShoppingCartServlet servlet;
 public TestShoppingCartServlet(String name) {
  super(name);
 }
 public void setUp() {
  this.servlet = new ShoppingCartServlet();
 }
 public void beginAddItemToCart(WebRequest webRequest) {
  webRequest.addParameter(„operation“,
                           ShoppingCartServlet.INSERT_ITEM);
  webRequest.addParameter(„itemID“,“12345″);
 }
 public void testAddItemToCart() throws Exception {
  this.servlet.doGet(this.request,this.response);
  Object obj = this.session.getAttribute(
                                  ShoppingCartServlet.CART);
  assertNotNull(„Mal by byť vytvorený nákupný košík.“,obj);
  assertTrue(„Košík by mal byť inštanciou triedy ShopCart“,
                                   obj instanceof ShopCart);
  ShopCart cart = (ShopCart)obj;
  Item item = cart.getItem(„12345“);
  assertNotNull(„Mala by existovať položka 12345“,item);
 }
}

Myslím si, že už nie je nutné vysvetlovať vám krok za krokom jednotlivé operácie, ktoré sa v tomto teste vykonávajú. Základom je otestovanie pridania a odobratia položky do/z nákupného košíka. Bolo by možné ešte implementovať test na odobratie všetkých položiek z košíka. Ale princíp zostáva rovnaký. Ako som už viackrát povedal, zatiaľ nám test neprejde, pretože servlet nemá implementovanú logiku na pridávanie/odoberanie položiek. Preto tak musíme urobiť. Nasleduje aktualizovaná časť servletu.

ShoppingCartServlet.java:


 protected void updateShoppingCart
                     (HttpServletRequest req,ShopCart cart)
                                   throws ServletException {
  String operation = getOperation(req);
  if (INSERT_ITEM.equals(operation)) {
   addItemToCart(getItemID(req), cart);
  } else if (REMOVE_ITEM.equals(operation)) {
   removeItemFromCart(getItemID(req), cart);
  } else if (REMOVE_ALL.equals(operation)) {
   removeAllItemsFromCart(cart);
  } else {
   throw new ServletException(„Nepovolená operácia: “ +
                                                 operation);
  }
 }
 protected String getItemID(HttpServletRequest req) {
  String itemID = req.getParameter(„itemID“);
  if (itemID == null || „“.equals(itemID)) {
   return INVALID;
  } else { return itemID; }
 }
 protected void addItemToCart(String itemID,ShopCart cart) {
  Item item = findItem(itemID);
  cart.addItem(item);
 }
 protected Item findItem(String itemID) {
  return new Item(itemID, „Description“ + itemID);
 }
 protected void removeItemFromCart(String itemID,
                                            ShopCart cart) {
  cart.removeItem(itemID);
 }
 protected void removeAllItemsFromCart(ShopCart cart) {
  cart.clear();
 }

Test prešiel

Po vyššie uvedených úpravách nám test už prejde. Ako môžete vidieť náš príklad obsahuje len jeden test na overenie funkčnosti metódy zodpovednej za vloženie nového záznamu. Myslím však, že nie je pre vás problém v rámci vlastnej iniciatívy implementovať ďalšie testovacie metódy na overenie funkčnosti metód na odobratie jedného alebo všetkých záznamov. Na záver vám ponúkam balíček dnešných príkladov na stiahnutie.

Žádný příspěvek v diskuzi

Odpovědět