Versionen im Vergleich

Schlüssel

  • Diese Zeile wurde hinzugefügt.
  • Diese Zeile wurde entfernt.
  • Formatierung wurde geändert.

...

Codeblock
languagexml
title<extension-server>/testng.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Suite" parallel="false">
    <listeners>
        <listener class-name="de.bmw.fdm.testng.LoggingListener" />
    </listeners>
    <test name="Test">
        <packages>
            <package name="de.fdm.*" />
        </packages>
    </test>
    <!-- Test -->
</suite>
<!-- Suite -->

Bei der Definition dieser XML Datei muss man leider einige Stolperfallen beachten.

Log4j für Unit Tests konfigurien

...

Codeblock
title<server-extension>/src/test/java/log4j.properties
 log4jlog4j.appender.logfile=org.apache.log4j.RollingFileAppender
log4j.appender.logfile.File=nuclos-test.log
log4j.appender.logfile.MaxBackupIndex=10
log4j.appender.logfile.MaxFileSize=2GB
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
#log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile.layout.ConversionPattern=%d %-5p %-9t [%c{3}] - %m%n
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
#log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.stdout.layout.ConversionPattern=%d %-5p %-9t [%c{3}] - %m%n
log4j.rootLogger=INFO, stdout, logfile

# http://www.benmccann.com/dev-blog/sample-log4j-properties-file/

# Adjust log levels

# SQL logging
log4j.logger.org.nuclos.server.dblayer=DEBUG

#log4j.logger.org.springframework=DEBUG

...

Codeblock
languagejava
title/<extension-server>/src/test/java/de/fdm/server/explorer/PdmcaeExplorerFacadeBeanTestDataFactory.java
package de.bmw.fdm.server.explorer;

import static de.fdm.common.FdmCommon.BO_Datenbedarf;
import static de.fdm.common.FdmCommon.BO_Datenpakettyp;
import static de.fdm.common.FdmCommon.BO_Fahrzeugbaum;
import static de.fdm.common.FdmCommon.BO_Knoten;
import static de.fdm.common.FdmCommon.F_Datenbedarf_fahrzeugbaum;
import static de.fdm.common.FdmCommon.F_Datenbedarf_link;
import static de.fdm.common.FdmCommon.F_Datenbedarf_modell;
import static de.fdm.common.FdmCommon.F_Datenbedarf_pfad;
import static de.fdm.common.FdmCommon.F_Datenbedarf_produktlinie;
import static de.fdm.common.FdmCommon.F_Datenbedarf_sachnummer;
import static de.fdm.common.FdmCommon.F_Datenpakettyp_bkrelevant;
import static de.fdm.common.FdmCommon.F_Datenpakettyp_knoten;
import static de.fdm.common.FdmCommon.F_Fahrzeugbaum_fahrzeug;
import static de.fdm.common.FdmCommon.F_Fahrzeugbaum_knotenname;
import static de.fdm.common.FdmCommon.F_Fahrzeugbaum_uebergeordnknoten;
import static de.fdm.common.FdmCommon.F_Knoten_bezeichnung;
import static de.fdm.common.FdmCommon.F_Knoten_uebergeordnknoten;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import org.apache.log4j.Logger;

import org.nuclos.common.UID;
import org.nuclos.common.UsageCriteria;
import org.nuclos.common.collection.Pair;
import org.nuclos.common.dal.vo.EntityObjectVO;
import org.nuclos.server.dal.DalSupportForGO;
import org.nuclos.server.genericobject.valueobject.GenericObjectVO;
import org.nuclos.server.masterdata.valueobject.MasterDataVO;
import org.nuclos.server.navigation.treenode.DefaultMasterDataTreeNode;
import org.nuclos.server.navigation.treenode.DefaultMasterDataTreeNodeParameters;
import org.nuclos.server.navigation.treenode.GenericObjectTreeNode.RelationDirection;
import org.nuclos.server.navigation.treenode.GenericObjectTreeNode.SystemRelationType;

import de.fdm.common.FdmCommon;
import de.fdm.common.explorer.FolderTreeNode;
import de.fdm.common.explorer.PdmcaeTreeNode;

class DataFactory {

    private static final Logger LOG = Logger.getLogger(DataFactory.class);
    
    //
    
    private final Random rand = new Random(9132278275L);
    
    DataFactory() {
    }
        
    EntityObjectVO<Long> finishEo(EntityObjectVO<Long> eo) {
        eo.setPrimaryKey(rand.nextLong());
        return eo;
    }
    
    GenericObjectVO getBO_KnotenAsGO(GenericObjectVO parent, String name) {
        return DalSupportForGO.getGenericObjectVOForUnitTest(getBO_Knoten(parent, name));
    }
        
    EntityObjectVO<Long> getBO_Knoten(GenericObjectVO parent, String name) {
        final EntityObjectVO<Long> eo = new EntityObjectVO<Long>(BO_Knoten);
        
        eo.setFieldValue(F_Knoten_bezeichnung, name + "Bez");
        if (parent != null) {
            eo.setFieldId(F_Knoten_uebergeordnknoten, parent.getPrimaryKey());
        } else {
            eo.setFieldId(F_Knoten_uebergeordnknoten, null);            
        }
        finishEo(eo);
        // final MasterDataVO<Long> der = new MasterDataVO<Long>(finishEo(eo));
        return eo;
    }
        
    MasterDataVO<Long> getBO_Datenpakettyp(GenericObjectVO parent, String name) {
        final EntityObjectVO<Long> eo = new EntityObjectVO<Long>(BO_Datenpakettyp);
        
        // BO_Knoten
        eo.setFieldValue(F_Knoten_bezeichnung, name + "Bez");
        if (parent != null) {
            eo.setFieldId(F_Datenpakettyp_knoten, parent.getPrimaryKey());
        } else {
            eo.setFieldId(F_Datenpakettyp_knoten, null);            
        }
        
        // BO_Datenpakettyp
        eo.setFieldValue(F_Datenpakettyp_bkrelevant, true);
        
        final MasterDataVO<Long> der = new MasterDataVO<Long>(finishEo(eo));
        return der;
    }
    
    MasterDataVO<Long> getBO_Fahrzeugbaum(MasterDataVO<Long> parent, String name) {
        final EntityObjectVO<Long> eo = new EntityObjectVO<Long>(BO_Fahrzeugbaum);
        
        eo.setFieldValue(F_Fahrzeugbaum_knotenname, name);
        eo.setFieldValue(F_Fahrzeugbaum_fahrzeug, name + "Vehicle");
        if (parent != null) {
            eo.setFieldId(F_Fahrzeugbaum_uebergeordnknoten, parent.getPrimaryKey());
        } else {
            eo.setFieldId(F_Fahrzeugbaum_uebergeordnknoten, null);            
        }
        
        final MasterDataVO<Long> result = new MasterDataVO<Long>(finishEo(eo));
        return result;        
    }
    
    MasterDataVO<Long> getBO_Datenbedarf(MasterDataVO<Long> parentFahrzeugbaum, String name) {
        final EntityObjectVO<Long> eo = new EntityObjectVO<Long>(BO_Datenbedarf);
        
        eo.setFieldValue(F_Datenbedarf_sachnummer, name + "Sachnummer");
        eo.setFieldValue(F_Datenbedarf_pfad, name + "Pfad");
        eo.setFieldValue(F_Datenbedarf_modell, name + "Modell");
        eo.setFieldValue(F_Datenbedarf_produktlinie, name + "PL");
        eo.setFieldValue(F_Datenbedarf_link, name + "Link");
        if (parentFahrzeugbaum != null) {
            eo.setFieldId(F_Datenbedarf_fahrzeugbaum, parentFahrzeugbaum.getPrimaryKey());
        } else {
            eo.setFieldId(F_Datenbedarf_fahrzeugbaum, null);            
        }
        
        final MasterDataVO<Long> result = new MasterDataVO<Long>(finishEo(eo));
        return result;        
    }
    
    PdmcaeTreeNode asPdmcaeTreeNode(MasterDataVO<Long> md) {
        final String name = (String) md.getFieldValue(FdmCommon.F_Fahrzeugbaum_knotenname);
        final String vehicle = (String) md.getFieldValue(FdmCommon.F_Fahrzeugbaum_fahrzeug);
        final Number id = md.getPrimaryKey();
        final PdmcaeTreeNode result = new PdmcaeTreeNode(id, name, true, id, name, vehicle);
        
        return result;
    }
    
    DefaultMasterDataTreeNode asDefaultMasterDataTreeNode(MasterDataVO<Long> md, String name) {
        final Long id = md.getPrimaryKey();
        final DefaultMasterDataTreeNodeParameters<Long> params = new DefaultMasterDataTreeNodeParameters<Long>(md.getEntityObject().getDalEntity(), 
                id, null, null, name + "Label", name + "Description");
        final DefaultMasterDataTreeNode<Long> result = new DefaultMasterDataTreeNode<Long>(params);
        return result;
    }
    
    FolderTreeNode asFolderTreeNode2(int level, GenericObjectVO go, List subNodes) {
        final FolderTreeNode result = mock(FolderTreeNode.class);
        when(result.getId()).thenReturn(go.getId());
        when(result.getEntityUID()).thenReturn(FdmCommon.BO_Knoten);
        when(result.hasSubNodes()).thenReturn(subNodes != null && !subNodes.isEmpty());
        when(result.getSubNodes()).thenReturn(subNodes);
        when(result.getLevel()).thenReturn(level);
        return result; 
    }
    
    FolderTreeNode asFolderTreeNode(int level, Long id, UsageCriteria usagecriteria,
            String sIdentifier, Long relationId,
            SystemRelationType relationtype, RelationDirection direction,
            String sUserName, UID state, UID uidNode, Long idRoot) {
        if (usagecriteria == null) {
            usagecriteria = new UsageCriteria(null, null, null, "pseudo-mocked usagecriteria");
        }
        final FolderTreeNode result = new FolderTreeNode(id, usagecriteria,
                sIdentifier, relationId,
                relationtype, direction,
                sUserName, state, uidNode, idRoot);
        result.setLevel(level);
        return result;
    }
        
    Pair<Date,Date> interval() {
        final long now = System.currentTimeMillis();
        Date start = new Date(now + rand.nextInt(1234567890));
        Date end = new Date(now + rand.nextInt(1234567890));
        if (start.after(end)) {
            final Date tmp = start;
            start = end;
            end = tmp;
        }
        return new Pair<Date, Date>(start, end);
    }
    
    void reset() {
    }

    Random getRand() {
        return rand;
    }
    
    <S,T> List<T> list(S... ts) {
        final List<T> result = new ArrayList<T>(ts.length);
        for (S t: ts) {
            result.add((T) t);
        }
        return result;
    }

    <S,T> Set<T> set(S... ts) {
        final Set<T> result = new HashSet<T>(ts.length);
        for (S t: ts) {
            result.add((T) t);
        }
        return result;
    }
}

Die DataFactory stellt die Value Objekte als Eingabe für den Test zur Verfügung. Bei Nuclos handelt es sich dabei oft um EntityObjectVOs, MasterDataVOs und/oder GenericObjectVOs. Das obrige Beispiel benutzt alle 3 Objektarten. Entscheiden für die Tests ist es, (zumindest) für jedes Value Objekt das referenziert, auch einen Primary Key zu hinterlegen. Dies gelingt besonders einfach mit einem Zufallsgenerator, dessen Startbedingung festgelegt wird. Ein solcher Generator liefert nämlich (bei jedem Test) immer wieder die gleiche (Pseudo-Zufalls-)Zahlenfolge. Nummern dieser Folge werden im Beispiel als Primary Key benutzt.

Definition Spring Context

Codeblock
languagexml
title/<extension-server>/src/test/resources/de/bmw/fdm/server/explorer/spring.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jms="http://www.springframework.org/schema/jms" 
    xmlns:amq="http://activemq.apache.org/schema/core"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:sec="http://www.springframework.org/schema/security" 
    xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:mockito="http://www.mockito.org/spring/mockito"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/jms
                           http://www.springframework.org/schema/jms/spring-jms.xsd
                           http://activemq.apache.org/schema/core
                           http://activemq.apache.org/schema/core/activemq-core.xsd
                           http://www.springframework.org/schema/tx
                           http://www.springframework.org/schema/tx/spring-tx.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/security
                           http://www.springframework.org/schema/security/spring-security.xsd
                           http://www.springframework.org/schema/jee
                           http://www.springframework.org/schema/jee/spring-jee.xsd
                           http://www.mockito.org/spring/mockito
                           https://bitbucket.org/kubek2k/springockito/raw/tip/springockito/src/main/resources/spring/mockito.xsd">

    <context:annotation-config />
    <context:spring-configured />

    <!-- No component scanning! - we place any bean needed here by hand. (tp) -->
    
    <!-- context:component-scan base-package="org.nuclos">
        <context:exclude-filter expression=".*_Roo_.*"
        type="regex" />
        <context:exclude-filter expression="org.springframework.stereotype.Controller"
        type="annotation" />
        </context:component-scan -->

    <!-- Avoid scanning for nuclos-common (this is important for *client* performance) -->
    <bean class="org.nuclos.common.ApplicationProperties"/>
    <bean id="appContext" class="org.nuclos.common.SpringApplicationContextHolder" />
    <bean id="domPreferencesFactory" class="org.nuclos.common.preferences.DOMPreferencesFactory" />

    <!-- Services/Beans -->
    <mockito:mock id="masterDataService" class="org.nuclos.server.masterdata.ejb3.MasterDataFacadeBean"/>
    <mockito:mock id="genericObjectService" class="org.nuclos.server.genericobject.ejb3.GenericObjectFacadeBean" />
    <mockito:mock id="parameterService" class="org.nuclos.server.common.ejb3.ParameterFacadeBean" />
    <mockito:mock id="stateService" class="org.nuclos.server.statemodel.ejb3.StateFacadeBean" />
    <mockito:mock id="serverMetaService" class="org.nuclos.server.servermeta.ejb3.ServerMetaFacadeBean" />
    <mockito:mock id="localeService" class="org.nuclos.server.common.ejb3.LocaleFacadeBean" />
    <mockito:mock id="preferencesService" class="org.nuclos.server.common.ejb3.PreferencesFacadeBean" />
    <mockito:mock id="metaDataService" class="org.nuclos.server.masterdata.ejb3.MetaDataFacadeBean"/>
    <mockito:mock id="resourceService" class="org.nuclos.server.resource.ejb3.ResourceFacadeBean" />
    <mockito:mock id="entityService" class="org.nuclos.server.masterdata.ejb3.EntityFacadeBean" />
    <mockito:mock id="entityObjectService" class="org.nuclos.server.common.ejb3.EntityObjectFacadeBean" />
    <mockito:mock id="treeNodeService" class="org.nuclos.server.navigation.ejb3.TreeNodeFacadeBean" />
    <mockito:mock id="lookupService" class="org.nuclos.server.common.ServerLocaleDelegate" />
    <mockito:mock id="parameterProvider" class="org.nuclos.server.common.ServerParameterProvider"/>
    <mockito:mock id="attributeProvider" class="org.nuclos.server.common.AttributeCache" />
    <mockito:mock id="nodeProvider" class="org.nuclos.server.common.NodeCache" />
    <mockito:mock id="moduleProvider" class="org.nuclos.server.genericobject.Modules" />
    <mockito:mock id="stateCache" class="org.nuclos.server.common.StateCache" />

    <!-- API beans -->
    <bean id="clientPreferences" class="org.nuclos.common2.ClientPreferences">
        <property name="preferencesFactory" ref="nuclosPreferencesSingleton" />
    </bean>
    <bean id="nuclosPreferencesSingleton"
        class="org.nuclos.common.preferences.NuclosPreferencesSingleton">
        <property name="preferencesFacadeRemote" ref="preferencesService" />
        <property name="preferencesFactory" ref="domPreferencesFactory" />
    </bean>

    <!-- mockito:mock id="treeService" class="org.nuclos.common.metadata.TreeMetaProvider" / -->
    <bean id="treeService" class="org.nuclos.common.metadata.TreeMetaProvider" />
    
    <mockito:mock id="mdTreeNodeFactory"
        class="org.nuclos.server.navigation.treenode.MasterDataTreeNodeFactory" />
        
    <!-- generic support for *FacadeLocal proxies -->
    
    <bean id="facadeLocalProxyFactoryBean" class="org.nuclos.server.spring.FacadeLocalProxyFactoryBean" />  
    <bean id="facadeLocalProxyBeanFactoryPostProcessor" class="org.nuclos.server.spring.FacadeLocalProxyBeanFactoryPostProcessor" />
    
    <!-- entity/field meta data support -->
    
    <bean id="xstreamSupport" class="org.nuclos.common2.XStreamSupport"/>
    
    <bean id="metaDataProvider" class="org.nuclos.server.common.TestMetaProvider">
        <constructor-arg>
            <!-- 
                this has been dump before by JMX with 
                org.nuclos.server.common.MetaProvider.dumpAllEntities(boolean false) 
            -->
            <value>classpath:bmw-fdm-meta.xml</value>
        </constructor-arg>
    </bean>
    
    <!-- Unavoidable dependencies of *ExplorerFacadeBeans -->
    
    <mockito:mock id="securityCache" class="org.nuclos.server.common.SecurityCache" />   
    <mockito:mock id="dynamicMetaDataProcessor" class="org.nuclos.server.dal.processor.jdbc.impl.DynamicMetaDataProcessor" />   
    <mockito:mock id="chartMetaDataProcessor" class="org.nuclos.server.dal.processor.jdbc.impl.ChartMetaDataProcessor" />   
    <mockito:mock id="nucletDalProvider" class="org.nuclos.server.dal.provider.NucletDalProvider" />  
    <mockito:mock id="recordGrantUtils" class="org.nuclos.server.common.RecordGrantUtils" />   
    <mockito:mock id="nuclosUserDetailsContextHolder" class="org.nuclos.server.common.NuclosUserDetailsContextHolder" />   
    <mockito:mock id="nuclosRemoteContextHolder" class="org.nuclos.server.common.NuclosRemoteContextHolder" />   
    <mockito:mock id="masterDataFacadeHelper" class="org.nuclos.server.masterdata.ejb3.MasterDataFacadeHelper" />    
    <mockito:mock id="springDataBaseHelper" class="org.nuclos.server.database.SpringDataBaseHelper" />    
    <mockito:mock id="collectableEOEntityProvider" class="org.nuclos.common.entityobject.CollectableEOEntityProvider" />
    
    <!-- Unavoidable local facades -->
    
    <mockito:mock id="entityObjectFacadeLocal" class="org.nuclos.server.common.ejb3.EntityObjectFacadeLocal" />    
    <mockito:mock id="masterDataFacadeLocal" class="org.nuclos.server.masterdata.ejb3.MasterDataFacadeLocal" />    
    <mockito:mock id="genericObjectFacadeLocal" class="org.nuclos.server.genericobject.ejb3.GenericObjectFacadeLocal" />    
    <mockito:mock id="treeNodeFacadeLocal" class="org.nuclos.server.navigation.ejb3.TreeNodeFacadeLocal" />
    
    <!-- Classes subject to test -->
    
    <bean class="de.bmw.fdm.server.explorer.VehicleExplorerFacadeBean" depends-on="facadeLocalProxyBeanFactoryPostProcessor"/>    
    <bean class="de.bmw.fdm.server.explorer.PdmcaeExplorerFacadeBean" depends-on="facadeLocalProxyBeanFactoryPostProcessor"/>
    
</beans>

Der XML Definition für den Test Spring Context erscheint auf den ersten Blick recht kompliziert. Allerdings sind viele Teile des Test Kontext stets gleich, so dass sich der Aufwand für die Erstellung doch in Grenzen hält. Der Kontext:

  1. Eine große Menge unvermeidbarer Mock Objekte. Diese werden durch mockito:mock Tags erzeugt.
  2. Boilerplate Code zur Konfiguration von Spring.
  3. Die beiden zu testenden Klassen PdmcaeExplorerFacadeBean und VehicleExplorerFacadeBean.
  4. Den TestMetaProvider, der die Metadaten für die BOs bereitstellt (s.o.).