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:
Visualisierung der Testergebnisse mit der TEST GUI:
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.
Das Vorgehen in der Pragmatic Workbench ist ähnlich der Codeplex Variante.
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.