Domanda

Abbiamo trovato che le unità di test abbiamo scritto per il nostro C#/C++ codice hanno realmente pagato.Ma abbiamo ancora migliaia di linee di business logic in stored procedure, in realtà solo il test in rabbia quando il nostro prodotto è fatto un gran numero di utenti.

Ciò che rende questo peggio è che alcune di queste stored procedure finire per essere molto lungo, a causa del calo di prestazioni quando il passaggio di tabelle temporanee tra SPs.Questo ci ha impedito di refactoring per rendere il codice più semplice.

Abbiamo fatto diversi tentativi di costruzione di unità di test in giro per alcuni dei nostri punti di stored procedure (principalmente a testare le prestazioni), ma hanno trovato che l'impostazione dei dati di test per questi test è davvero difficile.Per esempio, abbiamo la copia per il test database.In aggiunta a questo, le prove finiscono per essere molto sensibile al cambiamento, e anche la più piccola modifica a una stored proc.o tabella richiede una grande quantità di modifiche per il test.Così, dopo molte generazioni di rottura a causa di questi database in mancanza di prove a intermittenza, abbiamo dovuto tirare fuori il processo di generazione.

Così, la parte principale della mia domande è:qualcuno ha mai scritto correttamente unit test per la loro stored procedure?

La seconda parte della mia domanda è se i test di unità sarebbe/è più facile con linq?

Stavo pensando che invece di dover impostare le tabelle di dati di test, si può semplicemente creare una collezione di oggetti di prova e testare il codice linq in un “linq to objects” situazione?(Io sono totalmente nuovo a linq, quindi non so se questo sarebbe anche funzionare in modo corretto)

È stato utile?

Soluzione

Mi sono imbattuto in questo problema un po ' indietro e ha scoperto che se ho creato una semplice classe di base astratta per l'accesso ai dati che mi ha permesso di iniettare una connessione e di transazione, ho potuto unità di testare il mio sprocs per vedere se hanno fatto il lavoro in SQL che ho chiesto loro di fare e poi rollback così nessuno dei dati del test di sinistra nel db.

Questo era meglio che la solita "esecuzione di uno script per l'installazione di prova del mio db, poi, dopo l'esecuzione dei test, fare una pulizia della spazzatura/dati di test".Questo anche sentito più vicino al test di unità, perché questi test può essere eseguito da solo w/fuori avere una grande quantità di "tutto il db deve essere 'solo cosi' prima di eseguire questi test".

Ecco un frammento della classe di base astratta utilizzato per l'accesso ai dati

Public MustInherit Class Repository(Of T As Class)
    Implements IRepository(Of T)

    Private mConnectionString As String = ConfigurationManager.ConnectionStrings("Northwind.ConnectionString").ConnectionString
    Private mConnection As IDbConnection
    Private mTransaction As IDbTransaction

    Public Sub New()
        mConnection = Nothing
        mTransaction = Nothing
    End Sub

    Public Sub New(ByVal connection As IDbConnection, ByVal transaction As IDbTransaction)
        mConnection = connection
        mTransaction = transaction
    End Sub

    Public MustOverride Function BuildEntity(ByVal cmd As SqlCommand) As List(Of T)

    Public Function ExecuteReader(ByVal Parameter As Parameter) As List(Of T) Implements IRepository(Of T).ExecuteReader
        Dim entityList As List(Of T)
        If Not mConnection Is Nothing Then
            Using cmd As SqlCommand = mConnection.CreateCommand()
                cmd.Transaction = mTransaction
                cmd.CommandType = Parameter.Type
                cmd.CommandText = Parameter.Text
                If Not Parameter.Items Is Nothing Then
                    For Each param As SqlParameter In Parameter.Items
                        cmd.Parameters.Add(param)
                    Next
                End If
                entityList = BuildEntity(cmd)
                If Not entityList Is Nothing Then
                    Return entityList
                End If
            End Using
        Else
            Using conn As SqlConnection = New SqlConnection(mConnectionString)
                Using cmd As SqlCommand = conn.CreateCommand()
                    cmd.CommandType = Parameter.Type
                    cmd.CommandText = Parameter.Text
                    If Not Parameter.Items Is Nothing Then
                        For Each param As SqlParameter In Parameter.Items
                            cmd.Parameters.Add(param)
                        Next
                    End If
                    conn.Open()
                    entityList = BuildEntity(cmd)
                    If Not entityList Is Nothing Then
                        Return entityList
                    End If
                End Using
            End Using
        End If

        Return Nothing
    End Function
End Class

poi si vedrà un campione di dati di accesso di classe, utilizzando la precedente base per ottenere un elenco dei prodotti

Public Class ProductRepository
    Inherits Repository(Of Product)
    Implements IProductRepository

    Private mCache As IHttpCache

    'This const is what you will use in your app
    Public Sub New(ByVal cache As IHttpCache)
        MyBase.New()
        mCache = cache
    End Sub

    'This const is only used for testing so we can inject a connectin/transaction and have them roll'd back after the test
    Public Sub New(ByVal cache As IHttpCache, ByVal connection As IDbConnection, ByVal transaction As IDbTransaction)
        MyBase.New(connection, transaction)
        mCache = cache
    End Sub

    Public Function GetProducts() As System.Collections.Generic.List(Of Product) Implements IProductRepository.GetProducts
        Dim Parameter As New Parameter()
        Parameter.Type = CommandType.StoredProcedure
        Parameter.Text = "spGetProducts"
        Dim productList As List(Of Product)
        productList = MyBase.ExecuteReader(Parameter)
        Return productList
    End Function

    'This function is used in each class that inherits from the base data access class so we can keep all the boring left-right mapping code in 1 place per object
    Public Overrides Function BuildEntity(ByVal cmd As System.Data.SqlClient.SqlCommand) As System.Collections.Generic.List(Of Product)
        Dim productList As New List(Of Product)
        Using reader As SqlDataReader = cmd.ExecuteReader()
            Dim product As Product
            While reader.Read()
                product = New Product()
                product.ID = reader("ProductID")
                product.SupplierID = reader("SupplierID")
                product.CategoryID = reader("CategoryID")
                product.ProductName = reader("ProductName")
                product.QuantityPerUnit = reader("QuantityPerUnit")
                product.UnitPrice = reader("UnitPrice")
                product.UnitsInStock = reader("UnitsInStock")
                product.UnitsOnOrder = reader("UnitsOnOrder")
                product.ReorderLevel = reader("ReorderLevel")
                productList.Add(product)
            End While
            If productList.Count > 0 Then
                Return productList
            End If
        End Using
        Return Nothing
    End Function
End Class

E ora nell'unità di test, si può anche ereditare da una semplice base di classe che fa il setup / rollback work - o tenere questa, per unità di test di base

qui di seguito è un semplice test della classe base che ho usato

Imports System.Configuration
Imports System.Data
Imports System.Data.SqlClient
Imports Microsoft.VisualStudio.TestTools.UnitTesting

Public MustInherit Class TransactionFixture
    Protected mConnection As IDbConnection
    Protected mTransaction As IDbTransaction
    Private mConnectionString As String = ConfigurationManager.ConnectionStrings("Northwind.ConnectionString").ConnectionString

    <TestInitialize()> _
    Public Sub CreateConnectionAndBeginTran()
        mConnection = New SqlConnection(mConnectionString)
        mConnection.Open()
        mTransaction = mConnection.BeginTransaction()
    End Sub

    <TestCleanup()> _
    Public Sub RollbackTranAndCloseConnection()
        mTransaction.Rollback()
        mTransaction.Dispose()
        mConnection.Close()
        mConnection.Dispose()
    End Sub
End Class

e, infine, il seguito è un semplice test, utilizzando il test di base di classe che mostra come prova dell'intera CRUD ciclo per assicurarsi che tutti i sprocs fare il loro lavoro e che il ado.net il codice fa la sinistra-destra mappatura correttamente

So che questo non prova la "spGetProducts" stored procedure utilizzati i dati di cui sopra di accesso campione, ma si dovrebbe vedere il potere alla base di questo approccio al testing di unità sprocs

Imports SampleApplication.Library
Imports System.Collections.Generic
Imports Microsoft.VisualStudio.TestTools.UnitTesting

<TestClass()> _
Public Class ProductRepositoryUnitTest
    Inherits TransactionFixture

    Private mRepository As ProductRepository

    <TestMethod()> _
    Public Sub Should-Insert-Update-And-Delete-Product()
        mRepository = New ProductRepository(New HttpCache(), mConnection, mTransaction)
        '** Create a test product to manipulate throughout **'
        Dim Product As New Product()
        Product.ProductName = "TestProduct"
        Product.SupplierID = 1
        Product.CategoryID = 2
        Product.QuantityPerUnit = "10 boxes of stuff"
        Product.UnitPrice = 14.95
        Product.UnitsInStock = 22
        Product.UnitsOnOrder = 19
        Product.ReorderLevel = 12
        '** Insert the new product object into SQL using your insert sproc **'
        mRepository.InsertProduct(Product)
        '** Select the product object that was just inserted and verify it does exist **'
        '** Using your GetProductById sproc **'
        Dim Product2 As Product = mRepository.GetProduct(Product.ID)
        Assert.AreEqual("TestProduct", Product2.ProductName)
        Assert.AreEqual(1, Product2.SupplierID)
        Assert.AreEqual(2, Product2.CategoryID)
        Assert.AreEqual("10 boxes of stuff", Product2.QuantityPerUnit)
        Assert.AreEqual(14.95, Product2.UnitPrice)
        Assert.AreEqual(22, Product2.UnitsInStock)
        Assert.AreEqual(19, Product2.UnitsOnOrder)
        Assert.AreEqual(12, Product2.ReorderLevel)
        '** Update the product object **'
        Product2.ProductName = "UpdatedTestProduct"
        Product2.SupplierID = 2
        Product2.CategoryID = 1
        Product2.QuantityPerUnit = "a box of stuff"
        Product2.UnitPrice = 16.95
        Product2.UnitsInStock = 10
        Product2.UnitsOnOrder = 20
        Product2.ReorderLevel = 8
        mRepository.UpdateProduct(Product2) '**using your update sproc
        '** Select the product object that was just updated to verify it completed **'
        Dim Product3 As Product = mRepository.GetProduct(Product2.ID)
        Assert.AreEqual("UpdatedTestProduct", Product2.ProductName)
        Assert.AreEqual(2, Product2.SupplierID)
        Assert.AreEqual(1, Product2.CategoryID)
        Assert.AreEqual("a box of stuff", Product2.QuantityPerUnit)
        Assert.AreEqual(16.95, Product2.UnitPrice)
        Assert.AreEqual(10, Product2.UnitsInStock)
        Assert.AreEqual(20, Product2.UnitsOnOrder)
        Assert.AreEqual(8, Product2.ReorderLevel)
        '** Delete the product and verify it does not exist **'
        mRepository.DeleteProduct(Product3.ID)
        '** The above will use your delete product by id sproc **'
        Dim Product4 As Product = mRepository.GetProduct(Product3.ID)
        Assert.AreEqual(Nothing, Product4)
    End Sub

End Class

So che questo è un lungo esempio, ma è aiutato ad avere una classe riutilizzabile per l'accesso ai dati di lavoro, e un'altra ancora riutilizzabili classe per il mio test così non ho dovuto fare l'installazione/teardown di lavoro più e più volte ;)

Altri suggerimenti

Hai provato DBUnit?È stato progettato per eseguire unit test del database, e proprio database, senza dover passare attraverso il vostro codice C#.

Se si pensa al tipo di codice di test di unità tende a promuovere:piccolo altamente coesivo e umile-accoppiato routine, allora si dovrebbe praticamente essere in grado di vedere dove, almeno in parte, il problema potrebbe essere.

Nel mio mondo cinico, stored procedure sono parte di RDBMS mondo di lunga data tentativo di convincerlo a spostare il vostro business di elaborazione nel database, il che ha senso se si considera che il server di licenza, i costi tendono ad essere correlati a cose come il numero di processori.La roba più si esegue all'interno del vostro database, più che fare da voi.

Ma ho l'impressione che sei realmente interessato, con prestazioni, che non è certo appannaggio di unit test a tutti.Unit test dovrebbero essere abbastanza atomica e sono destinati per controllare il comportamento, piuttosto che le prestazioni.E in quel caso è quasi certamente andando ad avere bisogno di produzione di classe carichi al fine di verificare i piani di query.

Penso che avete bisogno di una diversa classe di ambiente di test.Io suggerirei una copia di produzione come il più semplice, supponendo che la sicurezza non è un problema.Quindi per ogni release candidate, che si avvia con la versione precedente, la migrazione utilizzando proprio le procedure di rilascio (che potrà dare a coloro ottima la prova di come un effetto collaterale) ed eseguire i tuoi tempi.

Qualcosa di simile.

La chiave per il test di stored procedure è la scrittura di uno script che consente di compilare un database vuoto con i dati che è pianificato in anticipo per tradursi in un comportamento coerente quando la stored procedure sono chiamati.

Devo mettere il mio voto fortemente favorendo le stored procedure e mettendo la tua logica di business in cui io (e la maggior parte Dba) pensa che appartiene, nel database.

So che noi, come gli ingegneri del software vogliamo splendidamente refactoring di codice, scritto nella nostra lingua preferita, per contenere tutto il nostro importante la logica, ma la realtà di prestazioni ad alto volume i sistemi, e la natura critica di integrità dei dati, ci chiedono di fare qualche compromesso.Il codice Sql può essere brutto, ripetitivo e difficile prova, ma non riesco a immaginare la difficoltà di messa a punto del database senza avere il controllo completo sulla progettazione delle query.

Spesso sono costretto a ridisegnare completamente le query, per includere le modifiche al modello di dati, per ottenere le cose per l'esecuzione in un lasso di tempo accettabile.Con stored procedure, vi posso assicurare che le modifiche saranno trasparenti per il chiamante, poiché una stored procedure che fornisce eccellente incapsulamento.

Sto supponendo che si desidera che i test di unità in MSSQL.Guardando DBUnit ci sono alcune limitazioni di supporto per MSSQL.Non supporta il tipo NVarChar, per esempio. Qui ci sono alcuni utenti reali e i loro problemi con DBUnit.

Buona domanda.

Ho problemi simili, e ho preso il percorso di minor resistenza (per me, comunque).

Ci sono un sacco di altre soluzioni, altri hanno menzionato.Molti di loro sono migliori / più puro / più appropriato per gli altri.

Stavo già usando Testdriven.NET/MbUnit per testare il mio C#, così ho semplicemente aggiunto le prove, per ciascun progetto, di chiamare la stored procedure con l'app.

Lo so, lo so.Questo suona terribile, ma ciò di cui ho bisogno è quello di scendere a terra con alcuni il test, e passare da lì.Questo approccio significa che anche se la mia copertura è basso sto testando alcune stored procedure, allo stesso tempo, come sto testando il codice che verrà a chiamare.C'è una certa logica per questo.

Io sono esattamente nella stessa situazione come poster originale.Si tratta di prestazioni rispetto a testabilità.I miei pregiudizi verso la testabilità (farlo funzionare, fare bene, fare veloce), che suggerisce di mantenere la logica di business di database.I database non solo la mancanza del framework di test, il codice di factoring costrutti, e il codice di analisi e strumenti di navigazione trovato in linguaggi come Java, ma molto presi codice di database è anche lento (dove molto presi codice Java non è).

Tuttavia, io non riconoscere il potere di database, elaborazione di set.Quando utilizzati in modo appropriato, SQL può fare un po ' di incredibilmente potente roba con poco codice.Quindi, io sono ok con alcuni set di base di logica che vivono nel database, anche se io ancora fare tutto il possibile per unità di test.

In una nota correlata, sembra che molto lungo e procedurali codice di database è spesso un sintomo di qualcos'altro, e penso che tale codice può essere convertito in codice testabile senza incorrere in un calo di prestazioni.La teoria è che tale codice rappresenta spesso processi batch che periodicamente elaborare grandi quantità di dati.Se questi processi batch dovesse essere convertito in porzioni più piccole e in tempo reale la logica di business che viene eseguito ogni volta che i dati di input è cambiato, questa logica può essere eseguito sul livello intermedio (dove può essere testato) senza prendere un calo di prestazioni (dal momento che il lavoro è fatto in piccoli pezzi, in tempo reale).Come effetto collaterale, questo elimina anche la lunga feedback loop di processo batch di gestione degli errori.Naturalmente questo metodo non funziona in tutti i casi, ma può funzionare in alcuni casi.Anche se ci sono tonnellate di tali untestable l'elaborazione in batch database di codice nel vostro sistema, la strada per la salvezza può essere lungo e faticoso.YMMV.

Ma ho l'impressione che sei realmente interessato, con prestazioni, che non è certo appannaggio di unit test a tutti.Unit test dovrebbero essere abbastanza atomica e sono destinati per controllare il comportamento, piuttosto che le prestazioni.E in quel caso è quasi certamente andando ad avere bisogno di produzione di classe carichi al fine di verificare i piani di query.

Penso che ci sono due distinte aree di test qui:le prestazioni, e la vera logica della stored procedure.

Ho fatto l'esempio di test il db prestazioni in passato e, per fortuna, abbiamo raggiunto un punto in cui il rendimento è abbastanza buono.

Sono completamente d'accordo che la situazione con tutta la logica di business nel database è cattiva, ma è qualcosa che abbiamo ereditato da prima che la maggior parte dei nostri sviluppatori entrato in azienda.

Tuttavia, ora stiamo adottando il modello dei servizi web per le nostre nuove funzionalità, e abbiamo cercato di evitare le stored procedure per quanto possibile, mantenere la logica nel codice C# e la cottura SQLCommands al database (anche se linq ora sarebbe il metodo preferito).C'è ancora esistente SPs che era il motivo per cui stavo pensando a posteriori unità di test.

Si può anche provare Visual Studio for Database Professionals.Si tratta soprattutto di gestione dei cambiamenti, ma dispone anche di strumenti per la generazione di dati di test e test di unità.

E ' abbastanza costoso, tho.

Usiamo DataFresh per il ripristino di modifiche tra ogni prova, poi test sprocs è relativamente facile.

Quello che ancora manca è il codice di copertura di strumenti.

Faccio povero test di unità.Se io sono pigro, il test è solo un paio di validi invocazioni potenzialmente problematici valori di parametro.

/*

--setup
Declare @foo int Set @foo = (Select top 1 foo from mytable)

--test
execute wish_I_had_more_Tests @foo

--look at rowcounts/look for errors
If @@rowcount=1 Print 'Ok!' Else Print 'Nokay!'

--Teardown
Delete from mytable where foo = @foo
*/
create procedure wish_I_had_more_Tests
as
select....

LINQ permetterà di semplificare questo solo se si rimuove la logica delle stored procedure e reimplementare come le query linq.Il che sarebbe molto più robusto e più facile di testare, sicuramente.Tuttavia, sembra che i vostri requisiti preclude questo.

TL;DR:Il design ha problemi.

Noi di test delle unità di codice C# che chiama SPs.
Abbiamo script di build, la creazione di pulito database di test.
E più grandi sono quelle che abbiamo attaccare e staccare durante l'apparecchiatura di prova.
Queste prove possono durare ore e ore, ma penso che ne vale la pena.

Una opzione di ri-fattore di codice (devo ammettere che un brutto hack) sarebbe quello di generare, tramite CPP (il preprocessore C) M4 (mai provato) o simili.Ho un progetto che sta facendo proprio questo e in realtà è per lo più lavorabile.

L'unico caso penso che potrebbe essere valido per: 1) come alternativa per KLOC+ stored procedure e 2) e questo è il mio casi, quando il punto del progetto è quello di vedere quanto lontano (in folle) si può spingere una tecnologia.

Oh, ragazzo.sprocs non si prestano a (automatico) test di unità.Ho una sorta di "unit test" il mio complesso sprocs da prove di scrittura in t-sql e file batch mano controllato l'output di stampa, dichiarazioni e i risultati.

Il problema con i test di unità di dati di qualsiasi tipo relative programmazione, è che è necessario disporre di un affidabile set di dati di test per iniziare con.Molto dipende anche dalla complessità della stored proc e cosa fa.Sarebbe molto difficile per automatizzare i test di unità per una procedura molto complessa che ha modificato molte tabelle.

Alcuni degli altri manifesti come hanno rilevato alcuni semplici modi per automatizzare la verifica manuale, e anche alcuni strumenti che è possibile utilizzare con SQL Server.Sul server Oracle, PL/SQL guru Steven Feuerstein lavorato come free unità di strumento di test per la stored procedure PL/SQL chiamato utPLSQL.

Tuttavia, ha abbandonato il suo sforzo, e poi è andato commerciali con Quest Codice Tester PL/SQL.Quest offre gratuitamente una versione di prova scaricabile.Io sono sul punto di provarlo;la mia comprensione è che è bravo a prendersi cura di overhead nella creazione di un framework di test in modo che è possibile concentrarsi solo sui test stessi, e mantiene il test in modo da poterli riutilizzare in test di regressione, uno dei grandi vantaggi di test-driven-development.Inoltre, si dovrebbe essere buono oltre il controllo di una variabile di output e non prevedono, per la convalida dei cambiamenti, ma devo ancora prendere uno sguardo più da vicino a me.Ho pensato che questa info potrebbe essere di valore per gli utenti di Oracle.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top