[Java] Hibernate testing strategie

Pagina: 1
Acties:

  • -FoX-
  • Registratie: Januari 2002
  • Niet online

-FoX-

Carpe Diem!

Topicstarter
Ik ben momenteel bezig aan een unit testing case waar ik verder wil gaan dan het testen van applicatie logica. Hiervoor wil ik me dan ook focussen op de mogelijkheden tot integratie testen met Hibernate.

Aangezien er vaak problemen zijn met de definities van hibernate mappings en deze zelfs pas in een latere fase aan het licht komen (tijdens het manuele testen); zou ik deze problemen graag zo snel mogelijk kunnen opvangen.

Hetgeen ik wil testen zijn voornamelijk volgende 2 punten:
• Relational mappings (1-n, n-n, ...); al dan niet bidirectioneel; zodat deze het correcte gedrag hebben en dus ook doen wat er verwacht wordt.
• Controle op persisteerbaarheid: De class moet wel gepersisteerd kunnen worden.
• Transitive persistence: cascading problematiek

Mijn idee was om een class te voorzien, die het generieke gedrag zou bevatten voor dergelijke testcases. Een superclass met daarin de voorzieningen van datasource injection; als voorbeeld.

Ik veronderstel dat ik niet de enige ben die al tegen dergelijke problematiek is aangelopen. Alle goede ideeën zijn dus welkom om een abstractie te kunnen voorzien voor het testen van Hibernate configuraties.

Ik heb al een basis versie uitgewerkt (gebruik makend van Spring), maar ben toch er nog niet tevreden over.
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
public abstract class HibernateTestCase extends TestCase {
  private HibernateTemplate hibernateTemplate;
  private SessionFactory sessionFactory;
  private Session session;

  protected void setUp() throws Exception {
    super.setUp();

    Configuration config = new Configuration();
    setupHibernateConfiguration(config);

    // add classes to be binded the the sessionFactory.
    for (int i = 0; i < getEntityClasses().length; i++) {
      Class clazz = getEntityClasses()[i];
      config.addClass(clazz);
    }

    // Build session factory.
    sessionFactory = config.buildSessionFactory();
    hibernateTemplate = new HibernateTemplate(sessionFactory);

    // For lazy loading, keeping the Hibernate Session open
    session = SessionFactoryUtils.getSession(sessionFactory, true);
    TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
  }

  /**
   * Unbind resources and release session.
   *
   * @throws Exception
   */
  protected void tearDown() throws Exception {
    TransactionSynchronizationManager.unbindResource(sessionFactory);
    SessionFactoryUtils.releaseSession(session, sessionFactory);
    flushHibernateSession();

    super.tearDown();
  }

  /**
   * Hibernate guarantees that within the context of a single HibernateSession,
   * no two different objects of the same entity exist.
   * This method must be called to avoid getting the retrieved instance from the session cache (first-level).
   */
  protected void flushHibernateSession() {
    hibernateTemplate.flush();
    hibernateTemplate.clear();
  }

  /**
   * Setup the correct Hibernate configuration.
   *
   * @param config        hibernate config
   * @throws IOException  if resource could not be loaded
   */
  private void setupHibernateConfiguration(Configuration config) throws IOException {
    // TODO how to handle datasource injection?
    ApplicationContext appContext = new FileSystemXmlApplicationContext("ApplicationContext.xml");

    Resource databaseProperties = appContext.getResource("database.properties");
    // Read properties file.
    Properties properties = new Properties();
    properties.load(databaseProperties.getInputStream());
    String driver = properties.getProperty("jdbc.driver");
    String url = properties.getProperty("jdbc.url");
    String database = properties.getProperty("jdbc.database");
    String user = properties.getProperty("jdbc.user");
    String password = properties.getProperty("jdbc.password");

    // Set configuration properties.
    config.setProperty(Environment.DRIVER, driver);
    config.setProperty(Environment.URL, url+database);
    config.setProperty(Environment.USER, user);
    config.setProperty(Environment.PASS, password);
    config.setProperty(Environment.DIALECT, MySQLDialect.class.getName());
    config.setProperty(Environment.SHOW_SQL, "true");

    // automatically create (when it connects) / drop (before connection release) database for each test.
    //config.setProperty(Environment.HBM2DDL_AUTO, "create-drop");

    // TODO how to handle transactions?
    config.setProperty(Environment.AUTOCOMMIT, "true");
  }

  /**
   * Retrieves the array of entity classes, associated with the specified DAO.
   *
   * @return Class array
   */
  public abstract Class[] getEntityClasses();

  /**
   * Returns Spring's HibernateTemplate, that will need to be injected into the DAO.
   *
   * @return HibernateTemplate that facilitates the ORM access
   */
  public HibernateTemplate getHibernateTemplate() {
    return hibernateTemplate;
  }


Met deze versie dien je nog steeds je cascadings expliciet te testen.
Voorbeeld
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void testDelete_cascadeDeleteDocuments() {
    User user = new User("theUserThatWillBeDeleted");
    user.addDocument(new Document("doc1"));
    user.addDocument(new Document("doc2"));
    long id = userDao.store(user);
    flushHibernateSession();

    User retrievedUser = userDao.get(id);
    assertNotSame("transient and persisted instance", user, retrievedUser);
    assertEquals("document.size", 2, retrievedUser.getDocuments().size());

    Document doc = (Document) retrievedUser.getDocuments().iterator().next();
    userDao.delete(retrievedUser);

    try {
      userDao.get(id);
      fail("User should have been deleted.");
    } catch (Exception e) {
      assertTrue("User deleted successfully", true);
    }
    flushHibernateSession();

    assertNull("Document should be deleted.", userDao.getDocumentById(doc.getId()));
  }

Maar deze doet ook niet helemaal wat het zou moeten doen. Hoe kan je er namelijk zeker van zijn dat je cascadings effectief gewerkt hebben?

  • KurtDB
  • Registratie: Juni 2004
  • Laatst online: 09-02 20:28
De combinatie van Spring (AbstractTransactionalDataSourceSpringContextTests) en DBUnit kan je ook gebruiken om hetzelfde resultaat te bereiken. Dan gaat ge ook minder code moeten schrijven. (je kan je begin testdata en expected data dan gewoon in 'n xml-file uitschrijven) Indien ge 'n deftige abstract class maakt werkt dat heel snel. (zowel het schrijven van tests en testdata als het effectief testen zelf)

  • -FoX-
  • Registratie: Januari 2002
  • Niet online

-FoX-

Carpe Diem!

Topicstarter
KurtDB schreef op woensdag 19 april 2006 @ 15:08:
De combinatie van Spring (AbstractTransactionalDataSourceSpringContextTests) en DBUnit kan je ook gebruiken om hetzelfde resultaat te bereiken. Dan gaat ge ook minder code moeten schrijven. (je kan je begin testdata en expected data dan gewoon in 'n xml-file uitschrijven) Indien ge 'n deftige abstract class maakt werkt dat heel snel. (zowel het schrijven van tests en testdata als het effectief testen zelf)
Ik had er al over gelezen en het is idd wel interessant om eens te bekijken, maar verder lost dat het probleem niet direct op over het omgaan met volgende zaken.

• Relational mappings (1-n, n-n, ...); al dan niet bidirectioneel; zodat deze het correcte gedrag hebben en dus ook doen wat er verwacht wordt.
• Controle op persisteerbaarheid: De class moet wel gepersisteerd kunnen worden.
• Transitive persistence: cascading problematiek