SSIS Unit Testing mit SSIS 2012/2014

13. Januar 2015

Um Unittests für SSIS-Pakete zu implementieren, gibt es aktuell nicht viele sinnvolle Möglichkeiten. Leider bietet Microsoft keine Standardmöglichkeiten im VisualStudio bzw. SSIS. Man ist auf 3rd Party Anbieter angewiesen die Tools dafür bereitstellen. Auf einige dieser Tools möchte ich hier eingehen.

 

 

Historie/manuelle Test

Bevor wir auf die verschiedenen Tools kommen, ein kurzer Rückblick wo wir bezüglich SSIS Unittesting herkommen. Da es, wie gesagt, keine out-of-the-box -Möglichkeiten gibt, behelfen sich viele Projekte damit ganz simple “Unittests” mit den SSIS Bordmitteln zu erstellen. Unittests kann man diese nicht wirklich nennen, da immer ganze Pakete ausgeführt werden. Je nachdem wie groß so ein Paket ist, wird das schnell unübersichtlich. Dies ist sowohl von der Implementierung als auch von der Handhabung umfangreich und kompliziert. Als Beispiel wird ein Testpaket definiert, das das Setup, die Ausführung des eigentlichen Pakets, die Assertion und das Teardown übernimmt. Dabei treten diverse Probleme auf:

  • Es wird immer das ganze zu testende Paket ausgeführt, man kann nicht einzelne Teile (Units) ausführen, was für richtige Unittests  eigentlich unbrauchbar ist
  • Man muss dafür sorgen, dass die Parameter, Variablen und Connections für den Unittest korrekt gesetzt werden. Oft behilft man sich damit, dass die Parameter und Connection fest für die Unittest-Umgebung eingetragen sind.
  • Die Nachvollziehbarkeit bei Fehlern im Test ist mühsam, da man im Fehlerfall nicht weiß ob es am Test oder am zu testenden Paket gelegen hat.
  • usw.

Dabei sei gesagt, dass diese manuellen Tests besser sind als gar keine Test. Komfortabel und effizient ist aber etwas anderes.

Custom Unittest Frameworks

Als Alternative zu den manuellen Tests, entwickeln einige Projekte/Kunden eigene Unittest Frameworks, die dann oft auch schon ansehnliche Ergebnisse liefern. Ein komplettes Unittest-Framework zu entwickeln, was projektübergreifend verwendet werden kann, kostet aber viel Zeit und Aufwand. Meist decken diese auch nicht alle Anforderungen an Unittests ab.

3rd Party Tools

Es gibt einige 3rd party Tool die sich dem Thema SSIS Unittesting angenommen haben und daraus sind einige brauchbare Ansätze entstanden.

SSISTester
http://www.bytesoftwo.com/

Eines der genannten Tools ist der SSISTester. Es ist ein Framework von bytesoftwo was sowohl Unittest als auch Integrationstest für SSIS-Pakete ermöglicht. SSISTester integriert sich hervorragend in Visual Studio 2008/2010/2012/2013 und kann für SSIS Projekt in SQL 2008, SQL 2012 und SQL 2014 eingesetzt werden.
Unittest können auf Task Ebene im Control-Fluss definiert werden. Die Prüfung von Ergebnissen kann jedoch zwischen jeder Komponente im Data-Flow-Taks gesetzt werden. Somit hat man eine extrem granulare Möglichkeit Pakete zu testen. 
Es ist ebenfalls möglich Datenquellen zu mocken, um die Logik ohne der Abhängigkeit zu Datenbanken/Files usw. zu testen. Der Test von Constraints im Control-Flow ist ebenfalls möglich. Die zu testenden Pakete müssen für die Ausführung der Unittest nicht verändert oder angepasst werden und enthalten demzufolge keinen Testcode oder sonstige Modifikationen für die Unittests.

Folgendes Snippet zeigt den Beispielhaften Aufbau eines einfachen Unittest mit dem SSISTester:

   1: using System.Collections.ObjectModel;

   2: using Microsoft.VisualStudio.TestTools.UnitTesting;

   3: using SSIS.Test;

   4: using SSIS.Test.Dts;

   5: using SSIS.Test.Metadata;

   6: using System.IO;

   7:  

   8: namespace SDX.PB.BI.ETL.Unittest.SSISTester

   9: {

  10:     [TestClass]

  11:     [UnitTest("SDX.PB.BI.ETL", "DimUser.dtsx", ExecutableName = @"[DimUser][SEQC_Info][DFT_Info_User_Load]")]

  12:     [DataTap(@"[DimUser][SEQC_Info][DFT_Info_User_Load][CSPL_UserFound]", @"[DimUser][SEQC_Info][DFT_Info_User_Load][OLE_DST_InfoUser_Load]")]

  13:     class DimUserLoadDataTaps : BaseUnitTest

  14:     {

  15:         protected override void Setup(SetupContext context)

  16:         {

  17:             context.Package.GetConnection("PrivatbilanzBI").SetConnectionString(Constants.PrivatbilanzBIDbConnectionString);

  18:             context.Package.GetConnection("PrivatbilanzDev").SetConnectionString(Constants.PrivatbilanzDevDbConnectionString);

  19:             context.Package.GetConnection("PrivatbilanzBI").SetConnectionString(Constants.SsisDbConnectionString);

  20:  

  21:             // truncate destination table

  22:             context.DataAccess.OpenConnection(Constants.PrivatbilanzBIDbConnectionString);

  23:             context.DataAccess.ExecuteNonQuery("truncate table [info].[User]");

  24:  

  25:             string query = @"  INSERT INTO [info].[User] (UserID, UserName, LaufendeBilanzID, Vorname, Nachname, Geburtstag, Geschlecht, Einkommen, Verheiratet, Kinder) 

  26:                                     VALUES (-1, 'TestUser', -1, 'Test', 'User', '1999-01-01', 'm' ,100000, 1, 2)";

  27:  

  28:             context.DataAccess.ExecuteNonQuery(query);

  29:  

  30:  

  31:             // truncate source table dbo.UpdatedUser 

  32:             context.DataAccess.ExecuteNonQuery("truncate table [info].[UpdatedUsers]");

  33:  

  34:             // truncate source table dbo.User and fill with test data

  35:             context.DataAccess.ExecuteNonQuery("truncate table [stage].[User]");

  36:  

  37:             query = @"  INSERT INTO [stage].[User] (ID, UserName, LaufendeBilanzID, Vorname, Nachname, Geburtstag, Geschlecht, Einkommen, Verheiratet, Kinder) 

  38:                                     VALUES (2, 'NeuerUser', -1, 'Test', 'NeuerName', '1999-01-01', 'm' ,100000, 1, 2)";

  39:  

  40:             context.DataAccess.ExecuteNonQuery(query);

  41:             context.DataAccess.CloseConnection();

  42:         }

  43:  

  44:         protected override void Verify(VerificationContext context)

  45:         {

  46:             Assert.AreEqual(true, context.Package.IsExecutionSuccess);

  47:             Assert.AreEqual(true, context.ActiveExecutable.IsExecutionSuccess);

  48:  

  49:             context.DataAccess.OpenConnection(Constants.PrivatbilanzBIDbConnectionString);

  50:             var rowCount = (int)context.DataAccess.ExecuteScalar(@"select count(*) from [info].[User]");

  51:             context.DataAccess.CloseConnection();

  52:  

  53:             Assert.AreEqual(2, rowCount);

  54:  

  55:             ReadOnlyCollection<DataTap> dataTaps = context.DataTaps;

  56:  

  57:             DataTap dataTap = dataTaps[0];

  58:             foreach (DataTapSnapshot snapshot in dataTap.Snapshots)

  59:             {

  60:                 string data = snapshot.LoadData();

  61:  

  62:                 Assert.IsFalse(string.IsNullOrEmpty(data));

  63:             }

  64:         }

  65:  

  66:         protected override void Teardown(TeardownContext context)

  67:         {

  68:             context.DataAccess.OpenConnection(Constants.PrivatbilanzBIDbConnectionString);

  69:             context.DataAccess.ExecuteNonQuery("truncate table [info].[User]");

  70:             context.DataAccess.CloseConnection();

  71:         }

  72:     }

  73: }

Die Tests können mit einer eigenen Test GUI, mit dem VS Testexplorer oder auch mit dem Resharper ausgeführt und visualisiert werden.

Visualisierung der Testergebnisse mit VS Testexplorer:

image

Visualisierung der Testergebnisse mit der TEST GUI:

image

SSISUnit/Pragmatic Workbench BIxPress (SSISUnit)

https://ssisunit.codeplex.com/

http://pragmaticworks.com/Products/BI-xPress/Features/SSISUnitTesting.aspx

SSISUnit von Pragmatic Work verfolgt einen etwas anderen Ansatz. Hierbei werden die Unittests über eine GUI erstellt um möglichst wenig Code schreiben zu müssen. Die Definition der Unittest wird als XML abgelegt.

Es gibt zum einen die kostenlose Codeplex Variante und dann die Luxus-Variante als Teil von BIxPress oder der Pragmatic Workbench. Beide Varianten setzen auf die SSISUnit-Engine auf. Die Workbench kann über Unittest hinaus noch sehr viel mehr. Darauf gehe ich hier aber nicht weiter ein.

Folgendes Beispiel zeigt die Konfiguration und das Ergebnis eines Unittest in der SSISUnit-GUI.

imageimage

Das Vorgehen in der Pragmatic Workbench ist ähnlich der Codeplex Variante.

image

image

Fazit: Die genannten Tools bieten tolle Möglichkeiten echte “Unittest” zu implementieren, wobei der SSISTester noch einen Schritt weiter geht und Assertions zwischen allen Komponenten im Dataflow-Task ermöglicht. Je nachdem welche BI-Entwickler in einem Projekt eingesetzt werden, kann eine dieser Varianten eingesetzt werden. Besonders die Integrationsmöglichkeiten in Builds, Autotest oder Check-In Vorgänge bringen viel Qualitäspotential für eine BI-Solution mit.