Reif für die Insel? Triff uns auf der BASTA! 01.09.2015

Svenja Henß
Svenja Henß, Senior Assistant

Reif für die Insel?

Oder brauchst Du eher Kollegen auf Augenhöhe, begeistern Dich aktuelle Technologien, liebst Du attraktive Enterprise-Projekte und schläfst abends gerne zu Hause im eigenen Bett?

Dann triff uns auf der BASTA!

Vom 29. September bis 01. Oktober sind wir als Aussteller auf der BASTA in Mainz mit dabei.


2015_Flyer_Insel_Leporello 

Da wir mit der gesamten “Affenbande” unterwegs sind, wirst Du uns sicherlich schnell finden Smiley 
Komme vorbei, spiele unser Bilderrätsel und nimm einen Xamarin Affen mit nach Hause. Oder mit etwas Glück den Hauptpreis: Einen Gutschein für “Ab in den Urlaub”!

WP_20150610_11_58_24_Pro

Vielleicht können wir Dich ja auch begeistern, bei uns im starken eXpert Team zu arbeiten!
Wir entwickeln Win-/Web-/Mobile- und BI-Lösungen auf der Microsoft Application Plattform sowie mit Xamarin und Cordova. Uns eint die gemeinsame Leidenschaft für erstklassige Architekturen und attraktive Lösungen.

Wir suchen Verstärkung für Frankfurt und München, jeweils vom Junior (Berufseinsteiger) bis zum erfahrenen Chief:

Web-/Mobile-Developer (m/w)
.NET Software Architekt (m/w)
.NET Enterprise Dev.(m/w)
BI-Developer (m/w)

Unsere Projekte sind regional begrenzt und so können unsere eXperts den Feierabend mit Familie und Freunden verbringen. Sie schlafen abends zu Hause im eigenen Bett und nicht im Hotel.

Weitere Infos zu den SDX eXperts und unseren aktuellen Jobs findest du auch auf www.sdx.de oder auf Facebook: www.Facebook.com/SDX.AG.

Solltest Du weitere Fragen haben, kannst Du mich auch gerne anrufen: 069 / 24 75 18 10

Share |

JavaScript testen – Testumgebung und Auswahl des Frameworks 24.08.2015

Lukasz Momot
Lukasz Momot, .NET Expert

Das Ausführen von manuellen Tests reicht heutzutage nicht, um die gesamte Komplexität der Anwendung abzudecken. Es sollte die Aufgabe der Entwickler sein, um sicherzustellen, dass die Funktionalität die implementiert wird, tatsächlich funktioniert. Die Testfälle sollten vor allem automatisch reproduzierbar sein. Um dieses Ziel zu erreichen kommen Unit Tests zum Einsatz. In JavaScript sollten sie besonders von Bedeutung sein, da die Sprache über keine strenge Typisierung verfügt. Immer mehr Logik wird clientseitig implementiert, deshalb ist die Komplexität der Webanwendungen heutzutage signifikant gestiegen.

Gliederung des Testframeworks

In JavaScript existiert eine große Menge der Bibliotheken und Testframeworks. Es ist nämlich ganz wichtig das entsprechende Testframework fürs Projekt zu wählen. Die Testframeworks oder genauer gesagt die Konzepte der Testumgebung kann man nach drei Kategorien gliedern:

  • Clientseitige Frameworks – sind Frameworks, die nur den Webbrowser zum Funktionieren brauchen. Beispiele: Jasmine, QUnit
  • Serverseitige Frameworks – sind Frameworks, die die Serverkomponente zum Ausführen von Tests brauchen. Beispiel: JSTestDriver
  • Hybride Lösungen – ein clientseitiges Testframework und zusätzlich ein Testrunner, der als Serverkomponente dient. Beispiele: Jasmine oder QUnit mit Karma

Clientseitige Frameworks: QUnit und Jasmine

Zu den gängigsten clientseitigen Frameworks gehört QUnit und Jasmine. QUnit wurde von denselben Entwicklern gebaut, die JQuery implementiert haben. Am Anfang war also die Bibliothek ganz stark mit JQuery verbunden. Jetzt hat sich das geändert, und QUnit ist ein separates Framework für das Unit Testing. Die Tests werden so gebaut, wie in den anderen „klassischen“ Unit Tests Frameworks. Die Lernkurve sollte in diesem Fall ziemlich gering sein.

   1: test('Divide 4 by 2', function () {
   2:     var result = divide(2,2);
   3:     equal(result, 2, '4 divided by 2 equals 2');
   4: });

Der Testfall wird in der Funktion test() ausgeführt und die Prüfung findet in equal() statt.

Jasmine ist hingegen ein BDD (Behaviour Driven Development Framework) Testframework. Der Unterschied an den man sich gewöhnen sollte, ist die deskriptive Art und Weise, wie man die Tests schreibt.

   1: describe("calulates discount for tablets", function() {
   2:  
   3:     it("returns 10% discount for 2 items", function () {
   4:  
   5:         // Arrange
   6:         var product = new Product("Test", "Tablets", 10, 2);
   7:  
   8:         // Act
   9:         var result = App.Logic.dicountCalculator.calculateForProduct(product);
  10:  
  11:         // Assert
  12:         expect(result.hasDiscount()).toBe(true);
  13:         expect(result.newPrice).toBe(18);
  14:     });
  15:  

Describe() ist eine Funktion, die die Tests gruppiert. Ein bestimmter Testfall sollte in der it() Funktion implementiert werden.

Jasmine erfreut sich sehr großer Popularität und wird ständig um neue Funktionalitäten erweitert. Ein großer Vorteil von Jasmine ist, dass es sich gut für das Testen von AngularJS eignet. Der Umfang der Funktionalität ist bei beiden Frameworks fast gleich. Der Entwickler muss selber entscheiden, welche Art der Syntax für ihn mehr überzeugend ist.

Das Problem bei der Entwicklung von JavaScript ist, dass der Code in mehreren Browsern lauffähig sein sollte. Die clientseitigen Frameworks verfügen zwar über eine angenehme Syntax aber das Ausführen von Tests lässt sich alleine damit schwierig automatisieren.

Serverseitige Frameworks und Hybride Lösungen

Diese Möglichkeit wird zwar von den serverseitigen Frameworks angeboten, aber sehr oft müssen die Tests in demselben Framework geschrieben werden, das die Serverkomponente unterstützt. Wenn man aber von beiden Optionen profitieren möchte, dann kann ein clientseitiges Testframework und zusätzlich eine Serverkomponente benutzt werden, die nur zum Ausführen von Tests dient und gleichzeitig mehrere clientseitige Testframeworks unterstützt, wie zum Beispiel Karma. Karma ist ein Testrunner, der auf node.js basiert.

Die Funktionsweise der Testumgebung wurde in der folgenden Abbildung dargestellt.

image

Serverseitige Testumgebung für JavaScript

Das zentrale Element der Testumgebung ist der Server, der über die Config Datei konfiguriert ist. Der Server gibt eine Schnittstelle frei, mit der sich die Browser verbinden. Auf den verbundenen Browser werden die Tests ausgeführt. Der ganze Prozess wird mit der Kommandozeile gesteuert.

Von Installation bis zum Ausführen von Tests ist der Entwickler nur ein paar Schritte entfernt.

   1: # Installation von Karma:
   2: $ npm install karma --save-dev
   3:  
   4: # Installation von Plugins, die in der Testumgebung benutzt werden:
   5: $ npm install karma-jasmine karma-chrome-launcher --save-dev
   6:  
   7: # Starten von Karma mit einer bestimmten Konfigurationsdatei:
   8: $ karma start my.conf.js

imageAnzeige welche Browser mit dem Browser verbunden sind

image

Ergebnis der Ausführung von Tests

In Hinblick darauf, dass Karma sich ganz gut mit dem CI-Server verbinden lässt, scheint die gleichzeitige Anwendung des clientseitigen Testframeworks und der serverseitige Komponente eine vernünftige Lösung zu sein. Der nächste Schritt nach der Auswahl von Technologien ist die Erstellung einer testfreundlichen Architektur der Applikation.

Quellen:

[1] https://code.google.com/p/js-test-driver/

[2] http://karma-runner.github.io/0.12/index.html

[3] http://jasmine.github.io/

[4] https://qunitjs.com/

[5] Sebastian Springer, Testgetriebene Entwicklung mit JavaScript, dpunkt Verlag, 2015

Share |

yield return 19.08.2015

Alexander Kabisch
Alexander Kabisch, Chief eXpert

Dieses Sprachfeature gibt es zwar schon etwas länger, jedoch hat es bei mir etwas gedauert damit warm zu werden. Auch die Beispiele, die ich bisher gesehen habe, waren mir nicht immer einleuchtend. Deshalb habe ich es mal für mich verständlich zerlegt.

Starten möchte ich mit for-Schleifen, hierbei werden x und y durch die Methode Add einfach aufsummiert. Die Add Methode simuliert dabei eine langlaufende Methode, um später die Performance der jeweiligen Implementierung besser aufzeigen zu können.

   1: private static IEnumerable<int> CallSimpleLoop()
   2: {
   3:     IList<int> items = new List<int>(); 
   4:     for (int x = 0; x < 3; x++)
   5:     {
   6:         for (int y = 0; y < 3; y++)
   7:         {
   8:             items.Add(Add(x, y));
   9:         }
  10:     } 
  11:     return items;
  12: }
  13:  
  14: private static int Add(int x, int y)
  15: {
  16:     Thread.Sleep(100); //simulate long running worker
  17:     return x + y;
  18: }

Im Beispiel werden dann immer die ersten beiden Items übersprungen und dann die nächsten beiden herausgesucht.

   1: CallSimpleLoop().Skip(2).Take(2).ToList()

Als yield könnte das ganze so aussehen. Obwohl es sich beim Ergebnis wieder um ein IEnumerable<int> handelt, kann das Verhalten beider Lösungen nicht unterschiedlicher sein. Während beim SimpleLoop oben die Add Methode immer gleich ausgeführt wird und nur das Ergebnis in der Liste abgelegt wird, wird hier der Aufruf selbst übernommen. Er wird nur beim Auslesen ausgeführt.

   1: private static IEnumerable<int> CallYieldLoop()
   2: {
   3:     for (int x = 0; x < 3; x++)
   4:     {
   5:         for (int y = 0; y < 3; y++)
   6:         {
   7:             yield return Add(x, y);
   8:         }
   9:     }
  10: }

Heißt hier werden nur die ersten 4 Adds ausgeführt. 2-mal für Skip und 2-mal für Take.

   1: CallYieldLoop().Skip(2).Take(2).ToList()

Aber was heißt er wird nur beim Zugriff ausgeführt? Was versteckt sich hinter jedem Item des yield Iterators?

Ich werde im Folgenden nur das Innere des “on demand” Verhalten von yield betrachten. Den äußeren Teil, wie also der Enumerator durchlaufen und das Current (Item) ausgeführt wird lasse ich außen vor. Somit kann man sich jedes Item sehr vereinfacht als Functionpointer (Delegate) vorstellen.

   1: private static IEnumerable<Func<int>> CallSimpleFuncLoop()
   2: {
   3:     IList<Func<int>> items = new List<Func<int>>();
   4:     for (int x = 0; x < 3; x++)
   5:     {
   6:         for (int y = 0; y < 3; y++)
   7:         {
   8:             items.Add(() => Add(x, y));
   9:         }
  10:     }
  11:     return items;
  12: }

Hier iteriert man jedoch durch die Delegates und muss am Ende den Wert über den Aufruf im Select erst noch berechnen lassen.

   1: CallSimpleFuncLoop().Skip(2).Take(2).Select(i => i()).ToList()

Während jedoch beim YieldLoop neben den Takes auch die Skips ausgeführt werden, werden beim FuncLoop nur die Selects ausgeführt. Dies schlägt sich in der Performance nieder. Am längsten benötigt der SimpleLoop, hier werden auch alle Add Aufrufe ausgeführt, dafür liegen dann aber auch alle Ergebnisse vor. Da der YieldLoop die Add Methode 4-mal aufruft benötigt er ca. doppelt so viel Zeit wie der FuncLoop. Er führt die Add Methode nur 2-mal aus!

SimpleLoop start
925,4136
SimpleLoop end

CallYieldLoop start
405,3873
CallYieldLoop end

CallSimpleFuncLoop start
206,5743
CallSimpleFuncLoop end

Wie bei allen LINQ Features sollte man auf Performance achten, meist liegen die Ergebnisse nicht vor sondern werden erst für den einzelne (jeden!!!) Zugriff aufgebaut. Genau dafür ist LINQ konzipiert!

Hier eine kleine Erweiterung, die diesen Mechanismus aushebelt. Dabei wird um das Delegate ein Wrapper gestrickt, der sich merkt ob das Delegate schon ausgeführt wurde.

   1: public class Wrapper
   2: {
   3:     private object m_lockObject = new object();
   4:     private bool m_loaded = false;
   5:     private int m_data;
   6:  
   7:     private Func<int> m_getData;
   8:  
   9:     public Wrapper(Func<int> getData)
  10:     {
  11:         this.m_getData = getData;
  12:     }
  13:  
  14:     public int Data
  15:     {
  16:         get
  17:         {
  18:             lock (m_lockObject)
  19:             {
  20:                 if(!m_loaded)
  21:                 {
  22:                     m_data = m_getData();
  23:                     m_loaded = true;
  24:                 }
  25:                 return m_data;
  26:             }
  27:         }
  28:     }        
  29: }
  30:  
  31: private static IEnumerable<Wrapper> CallWrapperFuncLoop()
  32: {
  33:     IList<Wrapper> items = new List<Wrapper>();
  34:  
  35:     for (int x = 0; x < 3; x++)
  36:     {
  37:         for (int y = 0; y < 3; y++)
  38:         {
  39:             items.Add(new Wrapper(() => Add(x, y)));
  40:         }
  41:     }
  42:  
  43:     return items;
  44: }

Auch hier wird das Delegate erst im Select ausgeführt, also 2-mal.

   1: CallWrapperFuncLoop().Skip(2).Take(2).Select(i => i.Data).ToList()

Der Unterschied zwischen dem WrapperLoop und dem FuncLoop wird vor allem dann klar wenn man das Skip und Take mehrfach ausführt. Hier habe ich es einfach 10-mal laufen lassen. Der SimpleLoop benötigt immer 9 Add Aufrufe und damit bleibt er immer gleich. Beim YieldLoop werden 4 x 10 nötig und beim FuncLoop 2 x 10. Lediglich der WrapperLoop führt nur 2 Aufrufe durch.

SimpleLoop start
927,8073
SimpleLoop end

CallYieldLoop start
4025,9645
CallYieldLoop end

CallSimpleFuncLoop start
2022,8212
CallSimpleFuncLoop end

CallWrapperFuncLoop start
205,7986
CallWrapperFuncLoop end

Vieles hat LINQ mit sich gebracht, so auch yield return. Eigentlich gar nichts Neues wenn man sich das Feature vereinfacht als Delegate vorstellt. Wie bei jeder Technologie sollte man sich vor Augen welche Restriktionen sie mit sich bringt. Im Falle von LINQ ist es sehr oft der mehrfache Zugriff auf vermeintlich dasselbe Objekt, nur durch ein Statement kann die Performance sehr schnell ganz schlecht werden. Neben der Zeit gibt es auch andere Performancecounter, die negative beeinflusst werden könnten. Wären x und y beispielsweise große Businessobjekte oder Buffer, so würde beim SimpleLoop nur das Ergebnis übernommen aber x und y durch die Garbagecollection abgeräumt werden. Beim YieldLoop müssen jedoch für jedes Item (Delegate) x und y vorgehalten werden und können erst abgeräumt werden wenn die IEnumerable<int> nicht mehr benötigt wird. Beim WrapperLoop könnte man dies beispielsweise darüber lösen m_getData nach dem Laden auf null zu setzen.

Share |

MSBuild Solution (sln) 12.08.2015

Alexander Kabisch
Alexander Kabisch, Principal eXpert

Hier ein paar Tips und Tricks wie man den Build von Visual Studio und per MSBuild.exe vereinheitlicht.

Man kann zwar die Solution über eine MSBuild Task erstellen, dafür benötigt man aber ein separates Buildfile. Leider kennt aber Visual Studio dieses File nicht und wird es nicht ausführen.

<MSBuild Projects="abc.sln" Properties="Configuration=Debug;Plattform=Any CPU"/>

Heißt alle Besonderheiten werden nicht im Visual Studio Build durchgeführt. Um nun alle gewünschten Verarbeitungen zu integrieren kann man die Solution erweitern. Als erstes ruft man MSBuild mit der Solution auf:

msbuild.exe abc.sln /p:Configuration=Debug;Platform=Any CPU

Wählt man keine Configuration und Platform, so wird die im Solutionfile hinterlegte verwendet. Genauso kann man über /t:target noch den entsprechenden Build ausführen. Nun arbeiten der Build und Visual Studio auf den gleichen Dateien. Das Solutionfille ist aber kein MSBuildfile, es wird aber darin umgewandelt. Mit

set msbuildemitsolution=1
msbuild.exe abc.sln

wird dieses auch auf der Platte abgelegt. Wirft man einen Blick hinein, so findet man

<Import Projects="before.abc.sln.targets" Condition="exists(‘before.abc.sln.targets’)"/>
<Import Projects="after.abc.sln.targets" Condition="exists(‘after.abc.sln.targets’)"/>

Man kann also den Build anpassen indem man neben dem Solutionfile Dateien mit entsprechenden Namen anlegt. Diese werden auch von Visual Studio, ausgenommen von den Versionen 2010 und 2012, ausgeführt.

Ein Build über die Commandline und aus Visual Studio laufen damit auf den gleichen Dateien. Ein Fehler in den Buildfiles fällt somit nicht erst bei einem automatisierten Build auf. Leider konnte ich noch nicht prüfen ob auch im TFS die before und after target ausgeführt werden.

Share |

Verwaiste SQL Server Dateien finden 05.08.2015

Andrej Kuklin
Andrej Kuklin, Principal BI eXpert

Manchmal gibt's nach dem Aufräumen von nicht mehr benötigten SQL Server Datenbanken auf einer Entwicklungsumgebung nicht viel mehr freien Platz auf der Festplatte als vorher. Das kann daran liegen, dass SQL Server beim Droppen der Offline Datenbanken die dazugehörigen MDF/NDF/LDF-Dateien nicht löscht. Das Verhalten ist auch dokumentiert
Mit folgendem Skript findet man die MDF/NFD/LDF-Dateien, die im default Datenverzeichnis vorhanden sind, aber von keiner Datenbank auf der aktuellen Instanz verwendet werden*

DECLARE @BasePath NVARCHAR(1000) = CONVERT(NVARCHAR(1000), 
    SERVERPROPERTY('InstanceDefaultDataPath'));
 
IF OBJECT_ID('tempdb..#DirectoryTree') IS NOT NULL
    DROP TABLE #DirectoryTree;      
 
CREATE TABLE #DirectoryTree
(
     filename NVARCHAR(512)
    ,depth INT
    ,isfile BIT
); 
 
/*Eine Alternative zur standardmäßig mitgelieferten, aber 
undokumentierten xp_dirtree ist z.B. eine eigene SQLCLR Implementierung.
In der konstenpflichtigen Version von SQL# (http://www.sqlsharp.com/)
ist so eine Implementierung schon vorhanden.*/
INSERT  #DirectoryTree
        (filename
        ,depth
        ,isfile)
EXEC master.sys.xp_dirtree @BasePath, 1, 1;      
 
SELECT
    dt.filename
FROM
    #DirectoryTree dt
WHERE
    dt.isfile = 1
    AND RIGHT(dt.filename, 4) IN ('.mdf', '.ldf', '.ndf')
    AND NOT EXISTS 
        (SELECT 1 FROM sys.master_files mf
            WHERE mf.physical_name = @BasePath + dt.filename);

*lauffähig ab SQL Server 2012. Zum Bestimmen des Datenverzeichnisses vor SQL Server 2012 braucht man eine andere Lösung (z.B. den Umweg über Registry. Hier sind noch weitere Möglichkeiten beschrieben).

Share |