Pokazywanie postów oznaczonych etykietą JavaBeans XML serialization. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą JavaBeans XML serialization. Pokaż wszystkie posty

niedziela, 13 kwietnia 2008

Serializacja XML - zapis java.util.Date oraz wartości enumeracji

Niniejszy artykuł jest kontynuacją wątku z artykułu TestNG - dane testowe z plików XML o wykorzystaniu standardowej (J2SE) schemy serializacji XML objektów do preparowania danych testowych. Ten artykuł będzie traktował o zapisie danych typu java.util.Date oraz typu wyliczeniowego - enumeracji.
Gwoli przypomnienia w tym przypadku nie chodzi o serializację obiektów z poziomu javy, tylko o ręczne preparowania plików XML zawierających zserializowane obiekty, które zostaną zdeserializowane z użyciem standardowego mechanizmu - java.beans.XMLDecoder, a następnie zostaną użyte jako dane wejściowe dla testów jednostkowych.

Niemniej przy serializacji obiektu z poziomy javy, który ma jedno pole o typie java.util.Date a drugie o typie wyliczeniowym, uzyskamy efekt dalece niezadowalający, którego na pewno nie będziemy chcieli naśladować.
Pole o typie wyliczeniowym w ogóle nie zostanie zapisane. Z kolei pole z datom zostanie zapisane, ale w formie liczby milisekund od 1 stycznia 1970r. Na pewno nie jest to wygodna postać dla człowieka, anie do odczytu, a tym bardziej do zapisu. W każdym razie java.beans.XMLEncoder zserializuje java.util.Date w następujący sposób:
<void property="birthDate"> 
 <object class="java.util.Date"> 
  <long>439254000000</long> 
 </object> 
</void>
Not tak... to przecież całkiem logiczne, bo jest to jedyny nie "potępiony" sposób ustawiania daty jaki oferuje interfejs java.util.Date.
Normalnie (w javie) jakbym miał uzyskać datę ze stringa, to bym skorzystał z java.text.SimpleDateFormat. No ale jak go użyć w tym XML'u... już podaję:
<void id="sdf" class="java.text.SimpleDateFormat">
 <string>yyyy-MM-dd</string>
 <void id="date0" method="parse">
  <string>1983-12-03</string>
 </void>
</void>
...
<object class="pl.dwalczak.Osoba">
 ...
 <void property="dataUrodzin">
  <object idref="date0"/>
 </void>
 ...
</object>
Pierwsza część konstrukcji (znacznik void z id="sdf"), to deklaracja, której w javie odpowiada:
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
java.util.Date date0 = sdf.parse("1983-12-03");
Nie definiuje ona żadnego zserializowanego obiektu, a jedynie zmienne, które mogą być wykorzystane przy definiowaniu zserializowanych obietków. Może być umieszczona w elemencie głównym, lub też bezpośrednio w zserializowanym obiekcie.
Druga część konstrukcji (znacznik object) definiuje obiekt typu pl.dwalczak.Osoba, w którym ustawia pole dataUrodzin wcześniej zdefiniowaną zmienną date0.

Wracając do typu wyliczeniowego.
Załóżmy, że klasa pl.dwalczak.Osoba posiada pole typOsoby o typie pl.dwalczak.TypOsoby, który jest enumeracjom:
package pl.dwalczak;
public enum TypOsoby {
 OsobaFizyczna,
 OsobaPrawna;
}
Wówczas ustawienie pola typOsoby dla obiektu pl.dwalczak.Osoba może wyglądać następująco:
<void property="type"> 
 <object class="com.dwalczak.TypOsoby" field="OsobaFizyczna"/> 
</void>
Lub tak:
<void property="type"> 
 <object class="com.dwalczak.TypOsoby" method="valueOf">
  <string>OsobaPrawna</string>
 </object> 
</void>

Podsumowanie

O ile problem z serializowaniem wartości typu wyliczeniowego, nie specjalnie zmniejsza atrakcyjność formatu standardowej serializacji obiektów XML w J2SE, jako ogólnego formatu zapisywania danych testowych, gdyż manualnie można to zapisać w całkiem przyzwoitej formie. To problem z datą faktycznie obniża tą atrakcyjność, gdyż formuła zapisu jej w "ludzkiej" postaci jest trochę za długa i skomplikowana. Mimo to, myślę że stosowanie tego formatu może być mniej pracochłonne niż projektowanie własnego i pisanie kodu deserializującego z niego obiekty.

Zasoby

Long Term Persistence of JavaBeans Components: XML Schema
Sergey Malenkov's Blog - How to encode Type-Safe Enums?

wtorek, 1 kwietnia 2008

TestNG - dane testowe z plików XML

Pisząc artykuł TestNG - dostawca danych przypomniałem sobie o pewnym mini framework'u do wykonywania testów jednostkowych, który przyszło mi kiedyś napisać. Musiałem go napisać nie dlatego, że w projekcie nie znano jUnita ;) ale dlatego, że jUnit w pewnych przypadkach nie wiele pomagał. Konkretnie chodzi o sytuację gdy wiele testów ma ten sam algorytm, a różnią się między sobą danymi testowymi (czasami są to dość duże zestawy danych).
Moim zdaniem w takim przypadku warto oddzielnie zarządzać danymi i algorytmami testowymi i umiejscowić je w różnych plikach. Wówczas istnieje możliwość, aby dane testowe przygotowywała inna osoba niż autor algorytmów testowych, nie trzeba ponownie kompilować testów gdy się zmieniły dane, no i łatwiej można uniknąć problemów z kodowaniem danych wejściowych.

DataProvider w TestNG nie wspiera bezpośrednio zaczytywania danych testowych z plików XML. Ale jak się okazało, robienie tego samemu nie jest trudne i co najważniejsze nie wymaga znaczących nakładów kodowania. Wystarczy zastosować dostarczaną przez specyfikacje J2SE serializację XML JavaBean'ów.
Schema serializacji jest relatywnie zrozumiała dla ludzi, a co najważniejsze jest bardzo elastyczna. Schema ta dopuszcza zapisywanie dowolnej liczby dowolnych obiektów. Ponadto można łatwo wygenerować przykładowy plik wejściowy do testów, poprzez zserializowanie spreparowanych w kodzie przykładowych danych.

Poniżej zamieszczam przykładową zawartość pliku XML z danymi testowymi:
<?xml version="1.0" encoding="UTF-8" ?>
<java version="1.5.0" class="java.beans.XMLDecoder">
 <array>
  <string>test1</string>
  <object class="pl.dwalczak.testngdp1.User">
   <void property="nickName">
    <string>heniek</string>
   </void>
   <void property="address">
    <object class="pl.dwalczak.testngdp1.Address"
     id="addr1">
     <void property="city">
      <string>Poznań</string>
     </void>
     <void property="postcode">
      <string>11-111</string>
     </void>
     <void property="street">
      <string>Jadwigi</string>
     </void>
     <void property="number">
      <string>11a/3</string>
     </void>
    </object>
   </void>
   <void property="mailingAddress">
    <object idref="addr1" />
   </void>
  </object>
 </array>
 <array>
  <string>test2</string>
  <object class="pl.dwalczak.testngdp1.User">
   <void property="nickName">
    <string>maniek</string>
   </void>
   <void property="address">
    <object idref="addr1" />
   </void>
   <void property="mailingAddress">
    <object idref="addr1" />
   </void>
  </object>
 </array>
</java>
Natomiast implementacja TestNG'owego data providera, korzystającego z tego pliku XML, może wyglądać następująco:
package pl.dwalczak.testngdp1;
import java.beans.XMLDecoder;
import java.io.FileInputStream;
import java.util.ArrayList;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

@Test
public class SimpleTest {
 
 @DataProvider(name="dp")
 public Object[][] createData() {
  ArrayList<Object[]> result = new ArrayList<Object[]>();
  XMLDecoder dec = null;
  try {
   dec = new XMLDecoder(new FileInputStream("testData.xml"));
   while (true) {
    Object o = dec.readObject();
    result.add((Object[]) o);
   }
  } catch (ArrayIndexOutOfBoundsException e) {
   // no more objects in stream
  } catch (Exception e) {
   throw new RuntimeException(e);
  } finally {
   if (dec != null) {
    dec.close();
   }
  }
  
  return result.toArray(new Object[result.size()][]);
 }

 @Test(dataProvider="dp")
 public void test(String arg0, User user) {
  System.out.println("arg0: " + arg0);
  System.out.println("user: " + user);
 }
}

Zasoby

Pliki źródłowe przykładu
Long Term Persistence of JavaBeans Components: XML Schema
Dokumentacja TestNG - 5.5.2 - From DataProviders