August 2013

Volume 28 Number 08

SQL Server - Einheit und Integrationstests von SSIS-Paketen

Pavle Guduric | August 2013

Ich arbeitete an einem Projekt, wo wir extrahieren gebaut, transformieren und laden (ETL) Prozesse mit mehr als 150 Pakete.Viele von ihnen enthalten komplexe Transformationen und Geschäftslogik, so waren nicht "einfach Daten verschieben von Punkt A nach Punkt B" Pakete.Geringfügige Änderungen war nicht einfach und die Ergebnisse waren oft unberechenbar.Um Pakete zu testen, wir Eingabetabellen oder Dateien mit Testdaten füllen, führen Sie das Paket oder die Aufgabe in Microsoft Business Intelligence Development Studio (BIDS), eine SQL-Abfrage schreiben und vergleichen die Ausgabe durch das Paket erzeugt mit was wir dachten, war die korrekte Ausgabe.Öfter wir lief den gesamten ETL-Prozess auf eine Beispieldatenbank und nur die Ausgabedaten am Ende des Prozesses der Stichprobe — ein zeitaufwändiger und unzuverlässige Verfahren.Leider ist dies eine gängige Praxis bei SQL Server Integration Services (SSIS)-Entwickler.Noch schwieriger ist, um zu bestimmen, welche Auswirkungen die Ausführung eines Pakets auf nachfolgende Pakete hat.Wie Sie Ihren ETL-Prozess erstellen, erstellen Sie ein Netz von angeschlossenen Pakete und andere Ressourcen.Es ist schwierig, einen vollständigen Überblick über die zahlreichen Abhängigkeiten zwischen all diesen jederzeit aufrechtzuerhalten.

Dieser Artikel beschreibt die Vorgehensweise beim Ausführen von Einheit und Integrationstests von SSIS-Paketen durch die Einführung einer Bibliothek mit dem Namen SSISTester, das auf der verwalteten SSIS-API aufbaut.Nach der Lektüre dieses Artikels sollten Sie die beschriebenen Techniken und Tools zum Automatisieren von Einheit und Integrationstests Ihrer bestehenden und neuen SSIS-Projekte verwenden können.Um den Artikel zu verstehen, haben Sie Erfahrung mit SSIS und c#.

SSISTester

Als ich ein Testframework für SSIS-Pakete zu denken anfing, fand ich drei Aspekte wichtig.Als erstes wollte ich haben eine ähnliche UX zum Schreiben von Tests mit das Visual Studio -Test-Framework, also die typischen Methodik mit Einrichtung, Überprüfung und Bereinigung (aka Abrüsten) Schritte angewendet werden musste.Zweitens wollte ich bestehende und bewährte Tools verwenden, schreiben, ausführen und Verwalten von Tests.Visual Studio war wieder einmal die offensichtliche Wahl.Und drittens wollte ich Code-Prüfungen in c# können.In diesem Sinne schrieb ich SSISTester, eine .NET-Bibliothek, befindet sich oberhalb der SSIS-Laufzeit und stellt eine API, mit der Sie schreiben und Ausführen von Tests für SSIS-Pakete.Die wichtigsten logischen Komponenten der Bibliothek werden dargestellt Abbildung1.

logische Komponenten der SSISTester Library
Abbildung 1 logische Komponenten der SSISTester Library

Die Paketquelle wird verwendet, um die unformatierte XML-Darstellungen von Ziel-Pakete zu speichern.Jedes Mal, wenn ein Test ausgeführt wird, wird eine neue Instanz der Klasse Microsoft.SqlServer.Dts.Runtime.Package aus XML deserialisiert, alle Felder und Eigenschaften auf ihre Standardwerte festgelegt.Dies ist wichtig, weil Sie verschiedene Tests, die auf einer Packung versehentlich Wiederverwendung nicht wollen die Werte vom vorherigen Tests festgelegt.

Instanzen von Testklassen werden in dem Test Repository gespeichert.Diese Klassen enthalten Methoden, die Ihre Testfälle zu implementieren.Wenn ein Test ausgeführt wird, werden diese Methoden von der Test-Engine aufgerufen.Die speziellen Regeln, die befolgt werden müssen, beim Erstellen von Testklassen werden später im Detail beschrieben.

Metadaten enthält die Attribute erforderlich, um eine Testklasse zu schmücken, damit es als Test Implementierung erkannt werden kann.Das Testmodul sieht für diese Attribute beim Laden von Prüfungen in der Test-Repository.

Zusammenhang mit Test stellt einen Satz von Klassen, die Zugriff auf die Common Language Runtime Informationen während der verschiedenen Phasen der Testausführung.Diese Klassen können Sie beispielsweise verschiedene Aspekte eines Pakets getestet, wie Variablen, Eigenschaften, vorstehenden Einschränkungen, Verbindungs-Manager, die derzeit ausgeführte Aufgabe, Paket-Störungen usw. zugreifen.

Das Testmodul bezieht sich auf die Kernklassen und Schnittstellen der SSISTester-API, die die verwaltete SSIS-Laufzeit direkt nutzen.Sie werden verwendet, um Pakete zu laden und Testen von Klassen in ihre jeweiligen Repositories sowie Ausführen von Tests und Testergebnisse zu erstellen.

Mini ETL

Zum Erstellen von Paketen und Klassen zu testen, ich verwende Visual Studio 2012 und SQL Server 2012, und ich werde drei Pakete verwenden, um ein einfaches ETL-Szenario zu illustrieren, in welcher, das Kunde als Textdatei, gelieferten Daten transformiert und in einer Datenbank gespeichert.Die Pakete sind CopyCustomers.dtsx, LoadCustomers.dtsx und Main.dtsx.CopyCustomers.dtsx kopiert die Customers.txt-Datei von einem Ort zum anderen und auf dem Weg wandelt alle Kundennamen Text in Großbuchstaben.Customers.txt ist eine einfache CSV-Datei enthält Ids und Namen des Kunden, etwa so:

id,name
1,company1
5,company2
11,company3

LoadCustomers.dtsx lädt die umgewandelten Namen in der Demo-Datenbank. Vor dem Laden von Daten in eine Zieltabelle namens Customers­Inszenierung, es schneidet alle zuvor gespeicherte Daten. Am Ende des Prozesses speichert es die Anzahl der Kunden in eine Variable. Hier ist das Skript zum Erstellen der Demo-Datenbank und der CustomersStaging-Tabelle:

CREATE
DATABASE [Demo]
GO
USE [Demo]
GO
CREATE TABLE [dbo].[CustomersStaging](
  [Id] [int] NULL,
  [Name] [nvarchar](255) NULL
) ON [PRIMARY]
GO

Das Main.dtsx-Paket enthält zwei Tasks Paket ausführen, die die Unterverpackungen ausführen CopyCustomers.dtsx und Load­Customers.dtsx, beziehungsweise.Verbindungs-Manager in CopyCustomers.dtsx und LoadCustomers.dtsx werden mit Ausdrücken und Paketvariablen konfiguriert.Die gleichen Paketvariablen aus der übergeordneten Paket-Konfiguration, wenn aus einem anderen Paket ausgeführt abgerufen werden.

Erstellen von Komponententests

Beginnen, erstellen Sie eine Konsolenprojekt und fügen Sie Assemblyverweise an SSIS.Test.dll und SSIS.Test.Report.dll.Ich werde zunächst erstellen einen Komponententest für das CopyCustomers.dtsx-Paket.Abbildung 2 zeigt die Ablaufsteuerung (links) und Datenfluss (rechts) für CopyCustomers.dtsx.

Ablaufsteuerung und Datenfluss (rechts) des CopyCustomers.dtsx-Pakets
Abbildung 2 (links) Ablaufsteuerung und Datenfluss (rechts) des CopyCustomers.dtsx-Pakets

Jede Unit-Test wird in einer einzelnen Klasse implementiert, die von der BaseUnitTest-Klasse abgeleitet und mit der UnitTest-Attribut werden müssen:

[UnitTest("CUSTOMERS", "CopyCustomers.dtsx")]
public class CopyCustomersTest : BaseUnitTest{
  protected override void Setup(SetupContext context){}
  protected override void Verify(VerificationContext context){}
  protected override void Teardown(TeardownContext context){}
}

Die UnitTest-Attribut markiert eine Klasse als eine Einheit testen Implementierung, damit es von der Test-Engine gefunden werden kann.Der erste Parameter entspricht der Paket-Repository, dem ein Zielpaket während der Testausführung, geladen wird, Kunden, die in diesem Beispiel.Der zweite Parameter kann der Name eines Ziel-Pakets, den Pfad zu einem Vorgang in der Ablaufsteuerung, den Pfad eines Event-handlers oder den Pfad zum vorhergehenden Einschränkung sein.In diesem Beispiel ist es der Name des CopyCustomers.dtsx-Pakets, da ich das gesamte Paket testen möchten.Im Grunde teilt das UnitTest-Attribut der testen-Motor für das CopyCustomers.dtsx-Paket im Repository Kunden suchen und führen Sie es während der CopyCustomersTest-Prüfung.

Die Basisklasse BaseUnitTest alle Unit-Test-Implementierungen, die von abgeleitet werden müssen enthält drei Methoden, die umgesetzt werden müssen: Setup, überprüfen und Teardown.

Diese drei Methoden werden in verschiedenen Testphasen ausgeführt.Die Setup-Methode ausgeführt wird, bevor ein Zielpaket vom Testmodul ausgeführt wird.Setup bereitet das Paket und alle ein- und Ausgänge, von denen das Paket abhängt, so dass es erfolgreich validiert und ausgeführt werden kann.Im folgenden Beispiel legen ich Pfade auf die Paketvariablen, die als Verbindungszeichenfolgen in der Verbindungs-Manager verwendet werden:

protected override void Setup(SetupContext context){
  if(File.Exists(@"C:\TestFiles\Archive\Customers.txt"))
    File.Delete(@"C:\TestFiles\Archive\Customers.txt");
  if(File.Exists(@"C:\TestFiles\Converted\Customers.txt"))
    File.Delete(@"C:\TestFiles\Converted\Customers.txt");
  DtsVariable sourceFile = context.Package.GetVariable("SourcePath");
  sourceFile.SetValue(@"\\nc1\Customers\Customers.txt");
  DtsVariable destinationFile = 
    context.Package.GetVariable("DestinationPath");
  destinationFile.SetValue(@"C:\TestFiles\Archive\Customers.txt");
  DtsVariable convertedFile = 
    context.Package.GetVariable("ConvertDestinationPath");
  convertedFile.SetValue(@"C:\TestFiles\Converted\Customers.txt");
}

Wenn die Setup-Methode erfolgreich ausgeführt wurde, führt zu prüfende Motor das Zielpaket. Wenn das Paket ausgeführt wurde, Testmodul die Verify-Methode aufruft, und ich kann überprüfen, ob meine Behauptungen wahr sind:

protected override void Verify(VerificationContext context){
  Assert.AreEqual(true, 
    context.Package.IsExecutionSuccess);
  Assert.AreEqual(true, 
    File.Exists(@"C:\TestFiles\Archive\Customers.txt"));
  Assert.AreEqual(true, 
    File.Exists(@"C:\TestFiles\Converted\Customers.txt"));
  string[] lines = 
    File.ReadAllLines(@"C:\TestFiles\Converted\Customers.txt");
  Assert.AreEqual("COMPANY2", lines[2].Split(',')[1]);
}

Die erste Assert überprüft, ob das Paket erfolgreich ausgeführt wurde. Im zweiten Beispiel bestimmt, ob die FST-Kopie-Quelldatei Datei Systemaufgabe die \\nc1\Customers\Customers.txt-Datei in den Ordner C:\TestFiles\Archive\ kopiert. Die letzten beiden behauptet überprüfen, ob die DFT konvertieren Kundennamen Datenfluss Aufgabe korrekt konvertiert Firmennamen in Großbuchstaben. Früher, beschrieben ich kurz testen Kontext. Hier können Sie sehen, wie ich den Context-Parameter verwendet, um ein Paketobjekt in das Setup und "bestätigen" Methoden zugreifen.

Am Ende des Tests verwende ich die Teardown Methode, um die Dateien zu löschen, die kopiert oder durch das Paket erstellt wurden:

protected override void Teardown(TeardownContext context){
  File.Delete(@"C:\TestFiles\Archive\Customers.txt");
  File.Delete(@"C:\TestFiles\Converted\Customers.txt");
}

Control Flow Prüfaufgaben

Prüfungen können bestimmte Aufgaben in der Ablaufsteuerung sowie Ziel. Beispielsweise um den DFT laden Kunden-Datenfluss in das LoadCustomers.dtsx-Paket zu testen, habe ich einen zusätzlichen Parameter des Attributs UnitTest, genannt ExecutableName, die Engine testen sagen, das ich diese Aufgabe testen möchten:

[UnitTest("CUSTOMERS", "LoadCustomers.dtsx",ExecutableName =
  @"\[LoadCustomers]\[SEQC Load]\[DFT Load customers]"))]
public class LoadCustomersTest : BaseUnitTest{
}

ExecutableName steht für den Pfad, der Namen von alle geschachtelte Container beginnt mit einem Paketnamen kombiniert.

Ablaufsteuerung und Datenfluss für LoadCustomers.dtsx werden angezeigt, Abbildung3.

Ablaufsteuerung und Datenfluss (rechts) des LoadCustomers.dtsx-Pakets
Abbildung 3 (links) Ablaufsteuerung und Datenfluss (rechts) des LoadCustomers.dtsx-Pakets

Wenn ein Test eine bestimmte Aufgabe ausgerichtet ist, wird nur diese Aufgabe von der Test-Engine ausgeführt.Wenn die erfolgreiche Ausführung des Vorgangs Ziel von vorhergehenden Aufgaben abhängt, müssen die Ergebnisse der Ausführung dieser Aufgaben manuell generiert werden.Der Datenfluss DFT laden Kunden erwartet die Zieltabelle vom Task SQL Truncate CustomersStaging abgeschnitten werden.Darüber hinaus erwartet der Datenfluss die umgewandelte Customers.txt-Datei an einer bestimmten Position.Da diese Datei durch das CopyCustomers.dtsx-Paket erstellt wird, muss ich es manuell kopieren.Hier ist die Setup-Methode, die all dies tut:

protected override void Setup(SetupContext context){
  string dbConStr = @"Data Source=.;Integrated Security=SSPI;Initial Catalog=Demo";
  string ssisConStr = @"Provider=SQLNCLI11;" + dbConStr;
  File.Copy(@"\\nc1\Customers\Customers.txt", 
    @"C:\TestFiles\Converted\Customers.txt");
  context.DataAccess.OpenConnection(dbConStr);
  context.DataAccess.ExecuteNonQuery("truncate table [dbo].[CustomersStaging]");
  context.DataAccess.CloseConnection();
  DtsConnection conn = context.Package.GetConnection("CustomerDB");
  conn.SetConnectionString(ssisConnStr);
  conn = context.Package.GetConnection("CustomersSrc");
  conn.SetConnectionString(@"C:\TestFiles\Converted\Customers.txt");
}

Mithilfe von File.Copy kopiere ich die Customers.txt an die Stelle von Datenfluss erwartet. Dann benutze ich die DataAccess-Eigenschaft der SetupContext, eine truncate-Anweisung auf die Zieltabelle auszuführen. Diese Eigenschaft macht einen leichtgewichtiger ADO.NET Wrapper, mit dem Sie SQL-Befehle auszuführen, ohne mithilfe von SqlConnection und SqlCommand-Klasse verwendet werden kann, jedes Mal, wenn Sie auf die Datenbank zugreifen möchten verfügbar. Am Ende verwende ich die Eigenschaft die Verbindungszeichenfolgen auf die zugrunde liegenden Verbindungs-Manager festgelegt.

Vorstehenden Einschränkungen testen

Es ist auch möglich, Schreiben von Tests, die vorstehenden Einschränkungen abzielen. Beispielsweise hat die CountConstraint, die den SCR-CheckCount-Skripttask in das LoadCustomers.dtsx-Paket vorangestellt ist einen Ausdruck, der überprüft, ob die Variable CustomerCount größer als NULL ist. Wenn dieser Ausdruck True ergibt, und der SEQC Load-Task erfolgreich ausgeführt wird, wird der Skripttask ausgeführt. Abbildung 4 zeigt den komplette Einheit-Test.

Abbildung 4 die komplette Einheit-Test

[UnitTest("CUSTOMERS", "LoadCustomers.dtsx",
    PrecedenceConstraintsTestOnly = true))]
public class LoadCustomersConstraintsTest : BaseUnitTest{
  private DtsPrecedenceConstraint _countConstraint;
  protected override void Setup(SetupContext context){
    DtsVariable variable = context.Package.GetVariable("CustomerCount");
    variable.SetValue(0);
    _countConstraint =
      context.Package.GetPrecedingConstraintForPath(
      @"\[LoadCustomers]\[SCR    CheckCount].[CountConstraint]");
    _countConstraint.SetExecutionResult(DtsExecutionResult.Success);
  }
  protected override void Verify(VerificationContext context)
  {
    Assert.AreEqual(false, _countConstraint.Evaluate());
  }
  protected override void Teardown(TeardownContext context){}
}

Ich muss zwei Dinge tun, für die Vorbereitung die Rangfolgeneinschränkung getestet werden. Zuerst habe ich die CustomerCount-Variable auf einen Wert festgelegt, weil der Ausdruck in der Rangfolgeneinschränkung darauf verweist. In diesem Fall wähle ich 0. Als nächstes legen Sie das Ausführungsergebnis des vorhergehenden Vorgangs, um Erfolg, Misserfolg oder Beendigung. Dies geschieht durch Verwendung der SetExecutionResult-Methode zum Erfolg der vorhergehenden Aufgabe zu simulieren. Dies bedeutet, dass CountConstraint zu False evaluieren sollte und dies ist, was ich in der Verify-Methode erwarten. Sie können nur eine Klasse haben, wo Sie Komponententests für alle vorstehenden Einschränkungen in einem Paket implementieren. Es gibt deshalb keine Zielpfad für die bestimmten Einschränkung im UnitTest-Attribut nur eine Bool-Flag, das dem Motor sagt, dass dies ein Unit-Test-Klasse für Rangfolgeneinschränkungen ist. Der Grund dafür ist, dass mit Rangfolgeneinschränkungen, besteht keine Notwendigkeit, das Paket oder Aufgabe auszuführen, bevor die Verify-Methode aufgerufen wird.

Ausführen von Komponententests

Bevor ich meine Tests ausführen können, muss ich deiner Pakete und Tests in ihre Repositories zu laden. Dazu brauche ich einen Verweis auf das Testmodul. Öffnen Sie die Datei "Program.cs", und ersetzen Sie die leere Main-Methode mit diesem:

static void Main{
  IUnitTestEngine engine = 
    EngineFactory.GetClassInstance<IUnitTestEngine>();
  engine.LoadPackages("CUSTOMERS", @"C:\TargetPackages\");
  engine.LoadUnitTests();
  engine.ExecuteUnitTestsWithGui();
}

Die erste Zeile erstellt einen Verweis auf das Testmodul.Um alle Pakete aus dem Ordner C:\TargetPackages\ in das Kunden-Repository laden, verwende ich die LoadPackages-Methode.Die LoadUnitTests-Methode lädt alle Klassen in der aufrufenden Assembly, die in der angegebenen Test Repository mit dem UnitTest-Attribut versehen sind.Schließlich fordere ExecuteUnitTestsWithGui um die Durchführung von Tests zu starten und die Überwachung GUI zu öffnen, die in angezeigt wird Abbildung5.

der überwachen GUI während der Ausführung von Tests
Abbildung 5 der überwachen GUI während der Ausführung von Tests

Die GUI in Abbildung5 ist praktisch, wenn Sie lokal auf Ihrem Rechner testen möchten und Sie nicht Visual Studiostarten möchten.Wenn Sie, um Pakete auf einem Server zu testen möchten, konnte machen kleine Änderungen an der Programm und planen Sie es zum Ausführen von Tests direkt auf einem Buildserver, zum Beispiel:

static void Main{
  IUnitTestEngine engine = 
    EngineFactory.GetClassInstance<IUnitTestEngine>();
  engine.LoadPackages("CUSTOMERS", @"C:\TargetPackages\");
  engine.LoadUnitTests();
  engine.ExecuteUnitTests();
  engine.UnitTestResults.SaveAsHtml(@"C:\TestResults\");
}

Die IUnitTestEngine-Schnittstelle verfügt die UnitTestResults-Eigenschaft, die können Sie aufrufen Testergebnisse und speichern Sie sie als einen HTML-Bericht. Ich ersetzte ExecuteUnitTestsWithGui mit ExecuteUnitTests, die die Überwachung GUI nicht angezeigt wird. Sie könnten auch Tests in Visual Studio oder ReSharper zu verwenden, so brauchen Sie nicht das Console-Programm zu starten. Hierzu erstellte ich die neue Klasse mit dem Namen SSISUnitTestAdapter, gezeigt Abbildung 6.

Abbildung 6 die SSISUnitTestAdapter-Klasse

[TestClass]
public class SSISUnitTestAdapter{
  IUnitTestEngine Engine {get;set;}
  [AssemblyInitialize]
  public static void Prepare(TestContext context){
    Engine = EngineFactory.GetClassInstance<IUnitTestEngine>();
    Engine.LoadPackages("CUSTOMERS", @"C:\TargetPackages\");
    Assembly testAssembly =
      Assembly.GetAssembly(typeof(CopyCustomersTest));
    Engine.LoadRepositoryUnitTests(testAssembly, "CUSTOMERS");
  }
  [TestMethod]
  public void CopyCustomersTest(){
    Engine.ExecuteUnitTest(typeof(CopyCustomersTest));
  }
  [TestMethod]
  public void LoadCustomersTest(){
    Engine.ExecuteUnitTest(typeof(LoadCustomersTest));
  }
  [TestMethod]
  public void LoadCustomersConstraintsTest(){
    Engine.ExecuteUnitTest(typeof(LoadCustomersConstraintsTest));
  }
}

Wenn Sie mit der Microsoft-Komponententestframework vor gearbeitet haben, erkennen Sie die Attribute TestClass, AssemblyInitialize und TestMethods.Die drei Testmethoden, CopyCustomersTest, LoadCustomersTest und LoadCustomersConstraintsTest, wickeln Sie den Aufruf der ExecuteUnitTest-Methode, die wiederum die Setup, überprüfen und Teardown Methoden der Klasse ausführt, die als Parameter übergeben wird.Die Prepare-Methode das Testmodul-Objekt erstellt und lädt Pakete und Unit Tests in ihre jeweiligen Repositories.Ich habe etwas andere Methoden namens LoadRepositoryUnitTests Tests verpflichtet, nur die Kunden-Repository laden.Dies ist nützlich, wenn Sie nicht, dass alle Tests zu laden möchten.Sie können alle Tests ausführen, indem Sie auf Tests | Ausführen | Alle Tests in Visual Studio.

Erstellen von Integrationstests

Die Grundidee von Unit-Tests besteht darin, alle möglichen Effekte Andere Pakete zu isolieren oder Aufgaben einerseits und getestet haben.Es kann manchmal schwierig werden, um einen realistischen Test-Setup zu erstellen und die ersten Voraussetzungen für einen Komponententest um sicherzustellen das Paket oder die Aufgabe, die zu testenden verhält sich wie ein Teil eines kompletten ETL-Prozesses.Weil Sie in der Regel ETL-Prozesse mit einer Anzahl von Paketen implementieren, müssen Sie Integrationstests durchführen sicher sein, dass jedes Paket funktioniert gut beim Ausführen als Teil dieses Prozesses.Die Idee ist es, die gesuchten Punkte in Ihrem ETL-Prozess zu definieren, wo Sie Prüfungen auszuführen, ohne den ganzen Prozess beenden möchten.Wie der Prozess schreitet fort und den gesuchten Punkt erreicht, Ihre Tests ausgeführt werden, und Sie können überprüfen, dass einen "live" work-in-Progress-ETL-Prozess; daher der Name, "live-Test."

Ein live-Test ist im Grunde eine Nachbedingung — für ein Paket, Task oder Event-Handler definiert — das muß zufrieden sein nach dem Paket, Task oder Event-Handler ausgeführt wurde.Diese Nachbedingung entspricht der Überprüfungsschritt eines Komponententests.Live-Tests unterscheiden sich von Unit-Tests, denn es nicht möglich ist, den Test vor der Ausführung des Pakets vorzubereiten oder einen clean-Up-Schritt durchführen.Dies ist da anders als bei ein Unit-Test, ein live-Test das Paket ausführen nicht; Es ist genau umgekehrt: Ein Paket führt einen Test, wenn es darum geht den gesuchten Punkt für die eine Nachbedingung definiert ist.

Abbildung 7 veranschaulicht diesen Unterschied.Beachten Sie die Position des Pakets in beiden Zahlen.Beim laufenden Komponententests, führt die Test-Engine explizit einen Komponententest durch seine Setup, überprüfen und Teardown-Methoden aufrufen.Ein Paket wird als ein Teil dieser Setup-überprüfen-Teardown-Sequenz ausgeführt.

Sequenz-Diagramme für Einheit testen (links) und Leben (rechts) Testausführung
Abbildung 7-Sequenz-Diagramme für Einheit testen (links) und Leben (rechts) Testausführung

Auf der anderen Seite führt, wenn live Tests ausgeführt, das Testmodul ein Paket explizit, das löst wiederum die Ausführung der Action-Methoden, die die Nachbedingungen für ein Paket und seine Aufgaben zu implementieren.

Um einen live-Test für das CopyCustomers.dtsx-Paket zu erstellen, ich habe die neue Klasse mit dem Namen CopyCustomers, dargestellt Abbildung 8.

Abbildung 8 die CopyCustomers-Klasse

[ActionClass("CUSTOMERS", "CopyCustomers.dtsx")]
public class CopyCustomers : BaseLiveTest{ 
  [ActionMethod(@"\[CopyCustomers]")]
  public void TestWholePackage(ActionContext context){
    Assert.AreEqual(true, context.Package.IsExecutionSuccess);
  }
  [ActionMethod(@"\[CopyCustomers]\[FST Copy Source File]")]
  public void TestCopySourceFile(ActionContext context){
    Assert.AreEqual(true, 
        context.ActiveExecutable.IsExecutionSuccess);
    Assert.AreEqual(true, 
        File.Exists(@"C:\TestFiles\Archive\Customers.txt"));
  }
  [ActionMethod(@"\[CopyCustomers]\[DFT Convert customer names]")]
  public void TestConvertCustomersNames(ActionContext context){
    Assert.AreEqual(true, context.ActiveExecutable.IsExecutionSuccess);
    string[] lines = 
        File.ReadAllLines(@"C:\TestFiles\Converted\Customers.txt");
    Assert.AreEqual("COMPANY2", lines[2].Split(‘,’)[1]);
  }
}

Jede live-Test-Klasse muss von der BaseLiveTest-Klasse, ein großer Unterschied im Vergleich zu einen Komponententest abgeleitet werden.Die BaseLiveTest-Klasse wird intern vom Testmodul zum Ausführen von live-Tests verwendet und hat keine Methoden, die überschrieben werden müssen.Das ActionClass-Attribut markiert diese Klasse als ein live-Test.Die Parameter sind die gleichen wie bei Verwendung des Attributs UnitTest-Paket-Repository und Ziel.Beachten Sie, dass im Gegensatz zu Unit-Tests, wo jede Prüfung in einer einzelnen, separaten Klasse implementiert wird, nur eine Klasse benötigt wird, um alle Nachbedingungen für ein Paket zu implementieren.Live-Test-Klassen können eine beliebige Anzahl von Nachbedingungen aufweisen, die ausgewertet werden sollen.Diese Nachbedingungen entsprechen der Verify-Methode in einem Komponententest und als Methoden mit dem ActionMethod-Attribut implementiert sind.In dem Beispiel im Abbildung 8, ich habe eine Nachbedingung für jede Aufgabe im Paket und eine für das Paket selbst.ActionMethod akzeptiert einen Pfad zu den Ziel-Task, der dasselbe wie die ExecutableName im UnitTest-Attribut ist.Dadurch wird das Testmodul zum Ausführen dieser Methode, wenn der Ziel-Task ausgeführt hat.Im Gegensatz zu der Verify-Methode, die immer ausgeführt wird, können diese Nachbedingungen nicht aufgerufen werden, wenn z. B. der Ziel-Task nicht erfolgreich ausgeführt oder die vorstehenden Einschränkung zu False ausgewertet wird.Der ActionContext-Parameter stellt die gleiche Funktionalität wie die VerificationContext.

Ausführen von Live-Tests

Die Schritte zum Ausführen von live-Tests sind etwas anders als beim Ausführen von Komponententests.Um live Tests auszuführen, ersetzen Sie die Main-Methode in der Datei "Program.cs" durch den Code in Abbildung 9.

Abbildung 9 die Hauptmethode für die Ausführung von Live-Tests

static void Main{
  string dbConStr = @"Data Source=.;Integrated Security=SSPI;Initial Catalog=Demo";
  string ssisConStr = @"Provider=SQLNCLI11;" + dbConStr;
  ILiveTestEngine engine = 
    EngineFactory.GetClassInstance<ILiveTestEngine>();
  engine.LoadPackages("CUSTOMERS", @"C:\TargetPackages\");
  engine.LoadActions();
  ExecutionParameters params = new ExecutionParameters();
  params.AddVariable(@"\[Main].[ConnectionString]", ssisConStr);
  params.AddVariable(@"\[Main].[CopyCustomersPath]", 
    @"C:\TargetPackages\CopyCustomers.dtsx");
  params.AddVariable(@"\[Main].[LoadCustomersPath]", 
    @"C:\TargetPackages\LoadCustomers.dtsx");
  params.AddVariable(@"\[Main].[ConvertDestinationPath]",
    @"C:\TestFiles\Converted\Customers.txt");
  params.AddVariable(@"\[Main].[DestinationPath]", 
    @"C:\TestFiles\Archive\Customers.txt");
  params.AddVariable(@"\[Main].[SourcePath]", 
    @"\\nc1\Customers\Customers.txt");
  engine.SetExecutionParameters(parameters);
  engine.ExecuteLiveTestsWithGui("CUSTOMERS", "Main.dtsx");
}

Ich brauche eine Instanz von ILiveTestEngine, die ich mit EngineFactory erstellen.Laden von Paketen ist dasselbe wie bei IUnitTestEngine Verwendung.Die LoadActions-Methode lädt alle Aktionen, die in der aufrufenden Assembly definiert und ist praktisch ein Äquivalent von Load­UnitTests.Zu diesem Zeitpunkt jedoch testet die Ähnlichkeit mit Haltestellen.Statt Ausführen von Komponententests, sage ich das Testmodul, das Main.dtsx-Paket auszuführen, durch Aufrufen der ExecuteLiveTestsWithGui.

Wenn das Main.dtsx-Paket startet, wird der CopyCustomers.dtsx durch Ausführen der EPT CopyCustomers Aufgabe ausgeführt.Jeder hat erfolgreich Aufgabe in den CopyCustomers.dtsx-Triggern eines entsprechenden Action-Methoden in der CopyCustomersLiveTests-Klasse.Es ist wichtig zu beachten, dass dieser Test implizit die Konfigurationseinstellungen des CopyCustomers.dtsx Paket testet.

Konfigurierte Variablen erben ihre Werte aus dem Main.dtsx-Paket.Bitte beachten Sie, dass diese Variablen als Verbindungszeichenfolgen in der Flatfile-Verbindungs-Manager des CopyCustomers.dtsx-Pakets verwendet werden.Dies bedeutet im Grunde, dass Ausführung Erfolg der Aufgaben im CopyCustomers.dtsx Paket hängt davon ab, ob die Wert Übergabe zwischen diese beiden Pakete korrekt funktioniert.Dies ist ein einfaches Beispiel wie Wechselwirkungen und Abhängigkeiten zwischen Paketen getestet werden, aber Sie können sich vorstellen, komplexere Szenarien wo isoliert Komponententests genug wäre, um den Testfall zu decken.

Test Engine Interna

Die Hauptklasse, die die wichtigsten Funktionen der SSISTester-Bibliothek implementiert ist TestEngine.Es ist eine interne Klasse, die durch die IUnitTestEngine und ILiveTestEngine-Schnittstelle verfügbar gemacht wird.Die zwei Methoden, die meisten von der inneren Logik zu offenbaren, sind LoadUnitTests (gezeigt Abbildung 10) und ExecuteUnitTests.

Abbildung 10 die LoadUnitTests-Methode

public void LoadUnitTests(){
  Assembly assembly = Assembly.GetCallingAssembly();
  IEnumerable<Type> types = assembly.GetTypes().Where(t => t.GetCustomAttributes(false).OfType<UnitTestAttribute>().Any() && 
    t.BaseType != null && t.BaseType.Name.Equals("BaseUnitTest"));
  foreach (Type t in types)
  {
    var attribute =
      t.GetCustomAttributes(false).OfType<UnitTestAttribute>().Single();
    DtsPackage package =
      _packages[attribute.Repository].GetForName(attribute.PackageName);
    string executable = attribute.ExecutableName;
    bool precedenceTestOnly = attribute.PrecedenceConstraintsTestOnly;
    var test = (BaseUnitTest)Activator.CreateInstance(t);
    test.TestClass = t;
    test.SetTestTargets(package, executable, precedenceTestOnly);
    test.Started += BaseUnitTestStarted;
    test.Finished += BaseUnitTestFinished;
    _unitTests.Add(test);
  }
}

LoadUnitTests durchläuft im Grunde alle Klassen mit dem UnitTest-Attribut dekoriert und erstellt eine Instanz für jeden. Diese Instanzen sind dann in BaseUnitTest umgewandelt und das Zielpaket, das zuvor aus dem Paket-Repository geladen zugeordnet sind. Am Ende werden alle Instanzen in der _unitTests-Liste gespeichert. Die ExecuteUnitTests-Methode durchläuft alle Instanzen von BaseUnitTest und jedes ExecuteTests fordert:

public void ExecuteUnitTests(){
  foreach (BaseUnitTest t in _unitTests){
    t.ExecuteTest();
  }
}

Die tatsächliche Ausführung von Unit-Tests in der ExecuteTest-Methode implementiert ist (siehe Abbildung 11) in der BaseUnitTest-Klasse.

Abbildung 11 die ExecuteTest-Methode

public void ExecutTest(){
  Result = new UnitTestResult(Package, Executable) { TestOutcome =
    TestOutcome.InProgress, StartedAt = DateTime.Now };
  ExecuteSetup(CreateSetupContext());
  if (!Result.IsSetupSuccess)
    ExecuteTeardown(CreateTeardownContext());
  else{
    if(!PrecedenceOnly)
      Executable.Execute();
    ExecuteVerify(CreateVerifyContext());
    ExecuteTeardown(CreateTeardownContext());
    Result.FinishedAt = DateTime.Now;
  }
}

Der wichtigste Aspekt dieser Methode ist, dass er die Setup, überprüfen und Teardown-Methoden als auch die Zielpaket ausgeführt wird.

Zusammenfassung

Die Beispiele, die hier vorgestellte zusammen mit dem begleitenden Projekt sollten Sie testen Ihre SSIS-Projekte starten zulassen.Automatisieren die Prüfung von SSIS-Paketen sparen Sie eine Menge Zeit.Was mehr ist wichtig, automatisierte Tests zuverlässiger, da es kontinuierlich erfolgt und Sie können weitere Pakete abdecken.Sobald Sie Prüfungen geschrieben haben, können Sie sie immer während automatisierte Buildprozesse ausführen.Letztendlich bedeutet dies weniger Fehler und bessere Qualität.

Pavle Gudurić befindet sich ein Software-Ingenieur in Deutschland. Er hat einen Master in e-Business und mehrere technische Zertifizierungen und Business Intelligence (BI) Lösungen in der Finanzindustrie entwickelt. Sie erreichen ihn am pavgud@gmail.com.

Unser Dank gilt den folgenden technischen Experten für die Durchsicht dieses Artikels: Christian Landgrebe (LPA) und Andrew Oakley (Microsoft)
Christian Landgrebe leitet das Datenbankteam bei GFB, konzentrierte sich auf die Bereitstellung von BI-Lösungen für Kunden in der Finanz- und Kreditwirtschaft.
Andrew Oakley ist Senior Program Manager für die Muster & Praxis-Team.Vor immer Programmmanager, verbrachte Andrew zwei Jahre als ein Technical Evangelist für Visual Studio und die .NET-Plattform.Sein aktuelle Projekt konzentriert sich auf die Daten Zugriff Anleitung um Gebäude polyglot permanente Systeme mit relationalen und NoSQL-Datenspeicher.