Helper-Funktionen in Model-Glue 3 und Unit-Testing

Die in Model-Glue 3 eingeführten Helpers sind eine wahre Freude. Will man die kleinen Helferlein aber nicht nur in Views einsetzen innerhalb des MVC-Frameworks, wird die Geschichte schnell einmal knifflig. So zum Beispiel in Models und im Zusammenhang mit Unit-Testing.

Helpers sind gemäss Framework-Dokumentation für die Verwendung in Views und Controllern gedacht. Vor allem in Views funktioniert dies auch ausgezeichnet, bei Controllern wird’s ein wenig umständlicher und für Models sind die Helferlein grundsätzlich nicht vorgesehen. Das macht aus theoretischer Sicht für das Design Pattern Sinn, doch in der Realität benötigt man oftmals auch in Model-Komponenten kleine Hilfsfunktionen. Doch wie kann bekommt man die Struktur, die in Views einfach via den helpers-Scope abgerufen werden können, in Models?

Vorsicht mit dem Application-Scope

Eine simple Variante habe ich bereits in einem früheren Beitrag beschrieben: über den Application-Scope. Die Variante funktioniert, steht aber auf wackligen Füssen und sollte eigentliche nicht wirklich verwendet werden. Und zwar aus zwei Überlegungen:

  1. In meiner Lösung greife ich direkt auf den Application-Scope zu innerhalb eines Models. Und das sollte man grundsätzlich vermeiden, hier habe ich endlich ein passendes Beispiel für diesen generellen Leitsatz. Stattdessen hätte man einen entsprechenden Konstruktor verwenden sollen.
  2. Will man das Model testen, beispielsweise mit dem Unit-Testint-Framework mxUnit, wird sich mit Sicherheit kein Erfolgserlebnis generieren lassen. Die Application-Variablen sind nicht definiert. Und generell gehören die Helpers zum Model-Glue-Framework, worum sich aber mxUnit keinen Deut schert, weil es das auch in keinster Weise müsste. Der Test des Models sollte keine MVC-Framework-spezifischen Eigenschaften, Funktionen, Methoden oder Variablen veraussetzen, ausser sie können durch ein DI-Framework wie ColdSpring zur Verfügung gestellt werden.

Auf die Nase gefallen

Als ich heute also Test-Szenarios mit mxUnit aufsetzen, hagelt es im Handumdrehen Fehlermeldungen. Klar, der Application-Scope, blöd. Zwei Stunden später war das Resultat dann schon besser. Und zwar habe ich folgende Anpassungen gemacht.

Getter und Setter im Model

Mein Model habe um Getter- und Setter-Funktionen bereichert und in die Init-Funktion umgebaut. Und so wurde aus:

<cffunction name="init" access="public" output="false" returntype="Any">
<!--- helpers-mapping via Application-Scope --->
<cfset variables.helpers = application._modelglue.helpers />
<!--- ... --->
<cfreturn this/>
</cffunction>

neu

<cffunction name="init" access="public" output="false" returntype="Any">
<!--- helpers-mapping via Application-Scope --->
<cfset variables.helpers = "" />
<!--- ... --->
<cfreturn this/>
</cffunction>

<cffunction name="getHelpers" output="false" returntype="any">
<cfreturn variables.Helpers />
</cffunction>
    
<cffunction name="setHelpers" output="false">
<cfargument name="Helpers" />
<cfset variables.Helpers = arguments.Helpers />
</cffunction>

Innerhalb der Komponenten kann man nun auf die Funktionen wie gewohnt über den helpers-Scope zugreifen.

Initialisierung im Controller

Damit der helper-Scope im Model überhaupt zur Verfügung steht, muss es im Controller initialisiert werden. Zu diesem Zweck habe ich eine kleine Funktion erstellt:

<cffunction name="setLocalHelpers" output="false" access="private" returntype="void" hint="">
<cfargument name="BeanList">
<cfset var local = StructNew() />
<!--- helpers setzen --->
<cfloop list="#arguments.BeanList#" index="local.i">
<cfset local.bean = getModelGlue().getBean(local.i)>
<cfif isSimpleValue( local.bean.getHelpers() )>
<cfset local.bean.setHelpers(application._modelglue.helpers) />
</cfif>
</cfloop>
</cffunction>

In dieser Funktion wird einfach der Setter der entsprechenden Komponente aufgerufen und mit dem Helpers-Scope gefüllt. Immer noch nicht ganz sauber hierbei ist die Verwendung des application-Scopes. Aber anders konnte ich bislang auch im Controller nicht auf diese Struktur zugreifen.
Die Funktion setLocalHelpers() wiederum wird in der Init-Funktion des Controllers aufgerufen:

<cffunction name="init" access="public" output="false" hint="Constructor">
<cfargument name="framework" />
<cfset super.init(framework) />
<cfset setLocalHelpers("notizenService") />    
<cfreturn this />
</cffunction>

Da die Funktion bei der Initialisierung aufgerufen wird, hat man leider noch keinen Zugriff auf den mit MG3 eingeführten beans-Scope. Deshalb muss ich in der Funktion setLocalHelpers() noch nach MG2-Manier und getModelGlue().getBean(«xy») die Komponenten laden.

helpers in Test-Cases von mxUnit

So weit, so gut. Die bestehende Applikation funktioniert weiterhin. Und nun zu mxUnit. Das Ziel ist nun, die Setter-Funktion setHelpers() der zu testenden Komponente mit dem helpers-Scope von Model-Glue zu beliefern. Dazu benötigen wir zu erst eine kleine Hilfsfunktion, sagen wir einmal localHelpers()

<cffunction name="localHelpers" output="false" access="private" returntype="struct" hint="">
<cfargument name="helperMapping">
<cfset var local = StructNew() />
<!--- initalisiert einen leeren Helpers-Container im MG-Framework --->
<cfset local.helpers = createObject("component", "ModelGlue.gesture.helper.Helpers")>
<!--- initialisiert den Helpers-Lader --->
<cfset local.HelperInjector = createObject("component", "ModelGlue.gesture.helper.HelperInjector") />
<!--- alle helpers-Funktionen in den leeren Container stellen --->
<cfset local.HelperInjector.injectPath(target=local.helpers, path=arguments.helperMapping) />
<cfreturn local.helpers />
</cffunction>

So, nun müssen wir diese Hilfsfunktionen nur noch mit dem Pfad zur Helpers-Verzeichnis aufrufen. Dafür nutzen wir die setup()-Funktion eines mxUnit-Testcases:

<cffunction name="setup" output="false" access="public" returntype="any" hint="">
<cfset bf = CreateObject("component","coldspring.beans.DefaultXmlBeanFactory").init() />
<cfset bf.loadBeansFromXmlFile(expandPath("/config/ColdSpring.xml")) />
<!--- Test-Komponenten initalisieren --->
<cfset instance.sv = bf.getBean("testService") />
<!--- Helpers setzen --->        
<cfset instance.sv.setHelpers(localHelpers(bf.getBean("modelglue.modelGlueConfiguration").getHelperMappings() ) ) />
</cffunction>

Den Pfad erhalten wir wie ersichtlich von der Model-Glue-Konfiguration, muss also nicht statisch hinterlegt werden.

Verwandte Blog-Einträge

Kommentare

Leave this field empty
Ihren Kommentar hinzufügen

Falls Sie abonnieren, werden alle neuen Kommentare zu diesem Thema an Ihre E-Mail-Adresse gesandt.

TrackBacks

Es gibt keine Trackbacks für diesen Eintrag.

Trackback URL dieses Eintrages:
http://www.samelis.ch/blog/mischa/trackback.cfm?id=DA3E1263-CF86-42E4-A1CF661C0997696F