SQL Server

Unit Test e test di integrazione dei pacchetti SSIS

Pavle Guduric

Scarica il codice di esempio

Ho lavorato su un progetto dove abbiamo costruito extract, trasformazione e caricamento (ETL) processi con più di 150 pacchetti.Molti di loro conteneva trasformazioni complesse e logica di business, così erano pacchetti non semplice "spostare i dati dal punto al punto B".Apportare modifiche minori non era semplice e risultati sono stati spesso imprevedibili.Per pacchetti di test, abbiamo utilizzato per compilare tabelle di input o file con dati di prova, eseguire il pacchetto o l'attività in Microsoft Business Intelligence Development Studio (BIDS), scrivere una query SQL e confrontare l'output prodotto dal pacchetto con quello che abbiamo pensato è stato l'output corretto.Più spesso abbiamo appena eseguito l'intero processo ETL su un database di esempio e basta campionare i dati in uscita alla fine del processo, una procedura che richiede tempo e inaffidabile.Purtroppo, questa è una pratica comune tra gli sviluppatori di SQL Server Integration Services (SSIS).Ancora più difficile è quello di determinare quali effetti ha l'esecuzione di un pacchetto su pacchetti successivi.Come si costruisce il vostro processo di ETL, si crea una rete di pacchetti collegati e risorse differenti.È difficile mantenere una panoramica completa delle numerose dipendenze tra tutti questi in ogni momento.

Questo articolo spiega come eseguire unità e test di integrazione di pacchetti SSIS introducendo una libreria chiamata SSISTester, che è costruito sopra l'API gestita di SSIS.Dopo la lettura di questo articolo si dovrebbe essere in grado di utilizzare le tecniche descritte e strumenti per automatizzare le unità e test di integrazione di progetti esistenti e nuovi SSIS.Per capire l'articolo, si dovrebbe avere esperienza precedente con SSIS e C#.

SSISTester

Quando ho iniziato a pensare a un framework di test per i pacchetti SSIS, ho trovato tre aspetti importanti.In primo luogo, volevo avere una UX simile alla scrittura di test utilizzando il framework di test Visual Studio , così la metodologia tipica che coinvolgono impostazione, verifica e pulizia (aka teardown) passi dovevano essere applicato.In secondo luogo, ho voluto utilizzare gli strumenti esistenti e provati a scrivere, eseguire e gestire i test.Ancora una volta, Visual Studio fu la scelta ovvia.E in terzo luogo, ho voluto essere in grado di test del codice in c#.Con questo in mente, ho scritto SSISTester, una libreria .NET che si siede in cima al runtime SSIS ed espone un'API che consente di scrivere ed eseguire test per pacchetti SSIS.I principali componenti logici della biblioteca sono raffigurati Figura1.

Logical Components of the SSISTester LibraryFigura 1 componenti logici della libreria SSISTester

Il Repository di pacchetti viene utilizzato per archiviare rappresentazioni XML raw dei pacchetti di destinazione.Ogni volta che viene eseguito un test di una nuova istanza della classe Microsoft.SqlServer.Dts.Runtime.Package viene deserializzata da XML con tutti i campi e le proprietà sono impostate sui valori predefiniti.Questo è importante perché non volete diversi test che porta lo stesso pacchetto di riutilizzo accidentalmente uno qualsiasi dei valori impostato da prove precedenti.

Le istanze di classi di test sono memorizzate all'interno del Repository di prova.Queste classi contengono metodi che implementano i test case.Quando viene eseguito un test, questi metodi vengono chiamati dal motore di prova.Le regole specifiche che devono essere seguite durante la creazione di classi di test saranno descritte nel dettaglio in seguito.

Metadati contengono gli attributi necessari per decorare una classe di test, quindi può essere riconosciuto come un'implementazione di test.Il motore di Test sembra per questi attributi quando prove di carico nel Repository Test.

Il contesto di prova rappresenta un insieme di classi che forniscono l'accesso alle informazioni di runtime nelle diverse fasi dell'esecuzione del test.Ad esempio, è possibile utilizzare queste classi per accedere ai diversi aspetti di un pacchetto testato, quali variabili, proprietà, vincoli precedenti, gestioni, attualmente l'esecuzione di task, pacchetto errori e così via.

Il motore di Test si riferisce alle classi principali e le interfacce API SSISTester che utilizzano direttamente il runtime gestito SSIS.Essi vengono utilizzati per caricare pacchetti e testare le classi nel loro rispettivi archivi, nonché per eseguire prove e creare i risultati del test.

Mini ETL

Per creare pacchetti e classi di prova, io uso Visual Studio 2012 e SQL Server 2012 e userò tre pacchetti per illustrare un semplice scenario ETL in cui cliente dati, forniti come file di testo, sono trasformati e memorizzati all'interno di un database.I pacchetti sono CopyCustomers.dtsx, LoadCustomers.dtsx e Main.dtsx.CopyCustomers.dtsx copia il file clienti. txt da una posizione a altra e sulla strada si converte tutti i nomi di clienti in maiuscolo il testo.Customers. txt è un semplice file CSV contenente gli ID e i nomi dei clienti, in questo modo:

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

LoadCustomers.dtsx carica i nomi convertiti in database Demo. Prima che carica i dati in una tabella di destinazione chiamata clienti­messa in scena, Tronca tutti i dati memorizzati in precedenza. Alla fine del processo, memorizza il numero di clienti in una variabile. Ecco lo script per creare il database di Demo e la tabella CustomersStaging:


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

Il pacchetto Main.dtsx contiene due attività Esegui pacchetto che eseguire il sub-packages carico e CopyCustomers.dtsx­Customers.dtsx, rispettivamente. Gestioni in CopyCustomers.dtsx e LoadCustomers.dtsx vengono configurati utilizzando le espressioni e le variabili di package. Le stesse variabili di package sono Estratto dalla configurazione del pacchetto padre quando eseguita all'interno di un altro pacchetto.

Creazione di unit test

Per iniziare, creare un progetto console e aggiungere riferimenti agli assembly di SSIS.Test e SSIS.Test.Report.dll. Ho intenzione di creare unit test per il pacchetto CopyCustomers.dtsx prima. Figura 2 Mostra il flusso di controllo (a sinistra) e flusso di dati (a destra) per CopyCustomers.dtsx.

Control Flow (Left) and Data Flow (Right) of the CopyCustomers.dtsx Package
Figura 2 controllo flusso (a sinistra) e flusso di dati (a destra) del pacchetto di CopyCustomers.dtsx

Ogni unit test è implementato in una classe singola che deriva dalla classe BaseUnitTest e deve essere decorata con l'attributo UnitTest:

[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){}
}

I marchi di attributo UnitTest una classe come unità test implementazione così può essere trovato dal motore di prova. Il primo parametro corrisponde al pacchetto Repository dove un pacchetto di destinazione verrà caricato durante l'esecuzione del test, clienti in questo esempio. Il secondo parametro può essere il nome di un pacchetto di destinazione, il percorso di un'attività del flusso di controllo, il percorso di un gestore eventi o il percorso di un precedente vincolo. In questo esempio è il nome del pacchetto CopyCustomers.dtsx perché voglio testare l'intero pacchetto. Fondamentalmente, l'attributo UnitTest racconta il motore prova a cercare il pacchetto CopyCustomers.dtsx nell'archivio clienti ed eseguirlo durante il test di CopyCustomersTest.

BaseUnitTest che tutte le implementazioni di unit test è necessario derivare dalla classe base contiene tre metodi che devono essere implementati: Installazione, verifica ed eliminazione.

Questi tre metodi vengono eseguiti durante le fasi di test diversi. Il metodo di installazione viene eseguito prima di un pacchetto di destinazione viene eseguito dal motore di Test. Installazione prepara il pacchetto e tutti gli ingressi e uscite che il pacchetto dipende quindi può essere correttamente convalidato e giustiziato. Nell'esempio seguente, impostare percorsi per le variabili di package sono utilizzate come stringhe di connessione nelle gestioni:

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");
}

Dopo che il metodo di installazione ha eseguito con successo, prova motore esegue il pacchetto di destinazione. Quando ha eseguito il pacchetto, il motore di prova chiama il metodo Verify e posso controllare se le mie affermazioni sono vere:

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]);
}

L'asserzione prima controlla se il pacchetto ha eseguito con successo. Il secondo si determina se l'attività di sistema del file FST copia File sorgente copiato il file \\nc1\Customers\Customers.txt nella cartella C:\TestFiles\Archive\. Gli ultimi due asserzioni convalidare se del flusso di dati DFT convertire clienti nomi nomi società attività correttamente convertiti in caratteri maiuscoli. In precedenza, descrive brevemente il contesto del test. Qui si può vedere come ho utilizzato il parametro context per accedere a un oggetto pacchetto all'interno dei metodi di installazione e verifica.

Alla fine del test, utilizzare il metodo di eliminazione per eliminare i file che sono stati copiati o creati dal pacchetto:

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

Test di controllo flusso attività

Test può target specifici compiti nel flusso di controllo. Ad esempio, per verificare il flusso di dati DFT carico clienti nel pacchetto LoadCustomers.dtsx, ho usato un ulteriore parametro dell'attributo UnitTest, chiamato ExecutableName, per indicare al motore di Test che voglio testare questo compito:

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

ExecutableName rappresenta il percorso che unisce i nomi dei contenitori nidificati tutti cominciando con un nome del pacchetto.

Flusso di controllo e dati per LoadCustomers.dtsx sono indicati Figura3.

Control Flow (Left) and Data Flow (Right) of the LoadCustomers.dtsx Package
Figura 3 controllo flusso (a sinistra) e flusso di dati (a destra) del pacchetto di LoadCustomers.dtsx

Quando un test destinato a un compito specifico, solo quell'operazione viene eseguita dal motore di prova. Se la corretta esecuzione dell'attività di destinazione dipende l'esecuzione delle attività precedenti, i risultati dell'esecuzione di tali compiti devono essere generati manualmente. Il flusso di dati DFT carico clienti prevede che la tabella di destinazione per essere troncata dall'attività SQL troncare CustomersStaging. Ulteriormente, il flusso di dati si aspetta clienti. txt file trasformato in una posizione specifica. Poiché questo file viene creato con il pacchetto CopyCustomers.dtsx, ho bisogno di copiare manualmente. Ecco il metodo di installazione che fa tutto questo:

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");
}

Utilizzando Copy, copiare il Customers. txt nel percorso previsto dal flusso di dati. Quindi utilizzare la proprietà DataAccess della SetupContext per eseguire un'istruzione truncata sulla tabella di destinazione. Questa proprietà espone un wrapper ADO.NET leggero che consente di eseguire comandi SQL senza dover utilizzare classi SqlConnection e SqlCommand ogni volta che si desidera accedere al database. Alla fine, utilizzare la proprietà del pacchetto per impostare le stringhe di connessione per i manager di connessione sottostante.

Test precedenti vincoli

È anche possibile scrivere test che precedenti i vincoli di destinazione. Ad esempio, il CountConstraint che precede l'attività script SCR CheckCount nel pacchetto LoadCustomers.dtsx è un'espressione che verifica se la variabile CustomerCount è maggiore di zero. Se l'espressione restituisce true e il compito di SEQC carico viene eseguito correttamente, viene eseguita l'attività script. Figura 4 Mostra la prova completa della macchina.

Figura 4 la prova completa della macchina

[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){}
}

Per preparare il vincolo di precedenza per essere testati, ho bisogno di fare due cose. In primo luogo, devo impostare la variabile CustomerCount su qualche valore, perché l'espressione del vincolo di precedenza si riferisce ad esso. In questo caso, scegliere 0. Successivamente, impostare il risultato dell'esecuzione dell'attività precedente di successo, di fallimento o di completamento. Lo faccio utilizzando il metodo SetExecutionResult per simulare il successo dell'attività precedente. Questo significa che CountConstraint dovrebbe restituire false e questo è ciò che mi aspetto nel metodo Verify. Si può avere una sola classe dove si implementa unit test per tutti i vincoli precedenti in un pacchetto. Pertanto, non non c'è nessun percorso di destinazione per il particolare vincolo nell'attributo UnitTest, solo un flag Boolean che indica al motore che questa è una classe di test di unità per vincoli di precedenza. La ragione di questo è che con vincoli di precedenza, non è necessario eseguire il pacchetto o l'attività prima che venga chiamato il metodo Verify.

L'esecuzione di Unit test

Prima che posso eseguire il mio test, ho bisogno di caricare pacchetti di destinazione e le prove nel loro repository. Per fare questo, ho bisogno di un riferimento al motore di Test. Aprire il file Program. cs e sostituire il metodo Main vuoto con questo:

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

La prima riga crea un riferimento al motore di Test. Per caricare tutti i pacchetti dalla cartella C:\TargetPackages\ nell'archivio clienti, utilizzare il metodo LoadPackages. Il metodo LoadUnitTests carica tutte le classi dell'assembly chiamante che sono decorate con l'attributo UnitTest nel repository test specificato. Infine, chiamare ExecuteUnitTestsWithGui per avviare l'esecuzione di prove e di aprire la GUI di monitoraggio, che è indicato nella Figura5.

The Monitoring GUI During the Execution of Tests
Nella figura 5 la GUI di monitoraggio durante l'esecuzione del test

La GUI in Figura5 è pratico se si desidera testare localmente sul computer e non volete iniziare Visual Studio. Se volete testare pacchetti su un server, si potrebbe apportare piccole modifiche al programma e pianificarlo per eseguire prove direttamente su un server di build, ad esempio:

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

L'interfaccia di IUnitTestEngine ha la proprietà di UnitTestResults che consente di accedere ai risultati dei test e salvarli come un report HTML. Ho sostituito ExecuteUnitTestsWithGui con ExecuteUnitTests, che non mostra la GUI di monitoraggio. Si potrebbe anche eseguire prove all'interno di Visual Studio o utilizzare ReSharper, quindi non è necessario avviare il programma di console. Per fare questo, ho creato la nuova classe denominata SSISUnitTestAdapter, mostrato Figura 6.

Figura 6 la classe SSISUnitTestAdapter

[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));
  }
}

Se hai lavorato con l'unità di Microsoft testing framework prima, riconoscerai gli attributi TestClass, AssemblyInitialize e metodi. I tre metodi di prova, CopyCustomersTest, LoadCustomersTest e LoadCustomersConstraintsTest, avvolgono la chiamata del metodo ExecuteUnitTest, che a sua volta esegue i metodi di installazione, verifica ed eliminazione della classe che viene passato come parametro. Il metodo Prepare crea l'oggetto di prova motore e carichi di pacchetti e unità di test nel loro rispettivi archivi. Ho usato metodi leggermente diversi chiamati LoadRepositoryUnitTests per caricare il test associato all'archivio clienti solo. Questo è utile se non volete tutti i test di carico. È possibile eseguire tutti i test cliccando su test | Eseguire | Tutti i test in Visual Studio.

Creazione di test di integrazione

L'idea di base di unit test è quello di isolare tutti gli effetti possibili altri pacchetti o attività può avere su quella in fase di test. A volte può essere difficile creare un setup di prova realistici e le condizioni iniziali necessarie per uno unit test garantire il pacchetto o attività in fase di test si comporta come una parte di un completo processo ETL. Perché è solitamente implementare processi ETL con un numero di pacchetti, è necessario eseguire test di integrazione per essere sicuri che ogni pacchetto funziona bene quando viene eseguito come parte di quel processo. L'idea è di definire punti di sondaggio nel vostro processo ETL dove si desidera eseguire il test, senza dover interrompere l'intero processo. Come il processo progredisce e raggiunge il punto di sondaggio, vengono eseguiti i test e si può verificare un processo ETL di work-in-progress "dal vivo"; da qui il nome, "prova diretta".

Una prova diretta è fondamentalmente una post-condizione — definito per un gestore di pacchetti, attività o evento — che ha bisogno di essere soddisfatto dopo il pacchetto, ha eseguito il compito o del gestore eventi. Questa postcondizione corrisponde alla fase di verifica di uno unit test. Live test sono diversi da unit test perché non è possibile preparare il test prima dell'esecuzione del pacchetto o per eseguire un passaggio di clean-up dopo. Questo è perché a differenza di uno unit test, un test dal vivo non esegue il pacchetto; è il contrario: Un pacchetto viene eseguito un test quando arriva al punto di sondaggio per il quale è definita una postcondizione.

Figura 7 viene illustrata questa differenza. Notare la posizione del pacchetto in entrambe le figure. Quando l'esecuzione di unit test, il motore di Test esegue in modo esplicito uno unit test chiamando i metodi di installazione, verifica ed eliminazione. Un pacchetto viene eseguito come parte di questa sequenza di installazione-verificare-Teardown.

Sequence Diagrams for Unit Test (Left) and Live Test (Right) Execution
Figura 7 diagrammi di sequenza per unità di prova (a sinistra) e vivono l'esecuzione del Test (a destra)

D'altra parte, quando si esegue il test dal vivo, il motore di Test esegue un pacchetto in modo esplicito, che a sua volta attiva l'esecuzione dei metodi di azione che implementano le successive per un pacchetto e i suoi compiti.

Al fine di creare un test dal vivo per il pacchetto CopyCustomers.dtsx, ho creato la nuova classe denominata CopyCustomers, mostrato Figura 8.

Figura 8 la classe CopyCustomers

[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]);
  }
}

Ogni classe di test dal vivo deve derivare dalla classe BaseLiveTest, delle principali differenze rispetto ad un test di unità. La classe BaseLiveTest viene utilizzata internamente dal motore di Test per eseguire test dal vivo e non ha metodi che devono essere sottoposti a override. L'attributo comandi contrassegna questa classe come un test dal vivo. I parametri sono le stesse di quando si utilizza l'attributo UnitTest — pacchetto repository e destinazione. Si noti che a differenza di unit test dove ogni test è implementato in una classe singola, separata, una sola classe è necessaria per implementare tutte le successive per un pacchetto. Classi di test dal vivo possono avere un numero arbitrario di successive che dovrebbero essere valutati. Queste successive corrispondono al metodo Verify in uno unit test e vengono implementati come metodi decorati con l'attributo ActionMethod. Nell'esempio in Figura 8, ho una post-condizione per ogni compito nel pacchetto e uno per il pacchetto stesso. ActionMethod accetta un percorso dell'attività di destinazione, che è lo stesso come il ExecutableName nell'attributo UnitTest. Questo indica al motore di Test per eseguire questo metodo quando ha eseguito il compito di destinazione. A differenza del metodo di verifica, che viene sempre eseguito, queste successive potrebbero non essere chiamati quando, ad esempio, non eseguire con successo l'attività di destinazione o il vincolo precedente restituisce false. Il parametro ActionContext fornisce la stessa funzionalità come il VerificationContext.

L'esecuzione di prove dal vivo

I passi necessari per eseguire il test dal vivo sono leggermente differenti rispetto a quando l'esecuzione di unit test. Per eseguire il test dal vivo, sostituire il metodo Main del file Program. cs con il codice in Figura 9.

Figura 9 il metodo principale per l'esecuzione di prove dal vivo

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");
}

Ho bisogno di un'istanza di ILiveTestEngine, che a creare utilizzando EngineFactory. Pacchetti di caricamento è lo stesso di quando si utilizza IUnitTestEngine. Il metodo LoadActions carica tutte le azioni definite nell'assembly chiamante e praticamente è un equivalente del carico­UnitTests. A questo punto, tuttavia, la somiglianza con unità di test si ferma. Invece l'esecuzione di unit test, dico il motore prova a eseguire il pacchetto Main.dtsx chiamando il ExecuteLiveTestsWithGui.

Quando si avvia il pacchetto Main.dtsx, viene eseguito il CopyCustomers.dtsx eseguendo il compito CopyCustomers EPT. Ciascuno con successo concluso il compito nei trigger CopyCustomers.dtsx uno dei metodi di azione corrispondente nella classe CopyCustomersLiveTests. È importante notare che questo test verifica implicitamente le impostazioni di configurazione del pacchetto CopyCustomers.dtsx.

Configurato variabili ereditano i valori dal pacchetto Main.dtsx. Si prega di notare che queste variabili sono usate come le stringhe di connessione nelle gestioni connessioni flat file del pacchetto CopyCustomers.dtsx. Questo significa fondamentalmente che il successo di esecuzione dei compiti nel pacchetto CopyCustomers.dtsx dipende se il valore handover tra questi due pacchetti funziona correttamente. Questo è un semplice esempio di come le interazioni e le dipendenze tra pacchetti sono testate, ma si possono immaginare scenari più complessi dove isolato unit test non sarebbe sufficiente a coprire il test case.

Prova motore Internals

La classe base che implementa le principali funzioni della libreria SSISTester è TestEngine. È una classe interna che viene esposto tramite le interfacce IUnitTestEngine e ILiveTestEngine. I due metodi che rivelano la maggior parte della logica interna sono LoadUnitTests (mostrato Figura 10) ed ExecuteUnitTests.

Figura 10 il metodo LoadUnitTests

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 scorre fondamentalmente tutte le classi decorata con l'attributo UnitTest e crea un'istanza per ciascuno. Queste istanze sono quindi in grado di eseguire il cast di BaseUnitTest e sono assegnate il pacchetto di destinazione precedentemente caricato dal repository pacchetto. Alla fine, tutte le istanze vengono salvate nell'elenco _unitTests. Il metodo ExecuteUnitTests scorre tutte le istanze di BaseUnitTest e chiama ExecuteTests su ciascuna:

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

L'effettiva esecuzione di unit test è implementata nel Metodo ExecuteTest (mostrato Figura 11) nella classe BaseUnitTest.

Figura 11 il Metodo ExecuteTest

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;
  }
}

L'aspetto più importante di questo metodo è che esegue i metodi di impostazione, verifica e Teardown, come pure il pacchetto di destinazione.

Conclusioni

Gli esempi presentati qui, insieme con il progetto di accompagnamento, dovrebbero consentire di iniziare a testare i vostri progetti SSIS. Automatizzare i test dei vostri pacchetti SSIS può risparmiare un sacco di tempo. Che cosa è più automatizzata, importante test è più affidabile, perché è fatto continuamente e si possono coprire più pacchetti. Una volta che avete scritto test, è possibile eseguire sempre li durante i processi di compilazione automatica. Alla fine, ciò significa meno errori e migliore qualità.

Pavle Gudurić è un ingegnere del software si trova in Germania. Ha un Master in e-business e di diverse certificazioni tecniche e sviluppa soluzioni di business intelligence (BI) nel settore della finanza. Contattarlo al pavgud@gmail.com.

Grazie ai seguenti esperti tecnici per la revisione di questo articolo: Christian Landgrebe (LPA) e Andrew Oakley (Microsoft)
Christian Landgrebe conduce la squadra di database a LPA, focalizzata sulla fornitura di soluzioni di BI ai clienti nel finanziario e settore bancario.
Andrew Oakley è un Senior Program Manager sui modelli & squadra di pratiche. Prima di diventare un Program Manager, Andrew trascorse due anni come un Technical Evangelist per la piattaforma .NET e Visual Studio . Il suo progetto attuale si concentra sull'orientamento accesso dati intorno edificio polyglot persistenti sistemi relazionali e archivi dati NoSQL.