A avut cineva vreun succes în unitatea de testare SQL proceduri stocate?

Am constatat că unitățile de testare pe care le-am scris pentru codul nostru C #/C ++ au plătit cu adevărat. Dar avem în continuare mii de linii de logică de afaceri în procedurile stocate, care se testează cu adevărat în furie atunci când produsul nostru este rulat pentru un număr mare de utilizatori.

Ceea ce face acest lucru mai rău este faptul că unele dintre aceste proceduri stocate sunt foarte lungi, din cauza performanței lovite atunci când se transmit tabele temporare între SP. Acest lucru ne-a împiedicat să refactorizăm pentru a face codul mai simplu.

Am făcut câteva încercări de a construi teste unitare în jurul unor proceduri stocate cheie (testarea în principal a performanței), dar am constatat că setarea datelor de testare pentru aceste teste este foarte dificilă. De exemplu, vom termina copierea în jurul bazelor de date de testare. În plus, testele ajung să fie foarte sensibile la schimbare și chiar și cea mai mică schimbare la un proc stocat. sau tabelul necesită o schimbare mare a testelor. Deci, după ce mulți construiesc ruperea din cauza că aceste teste de baze de date au eșuat intermitent, am trebuit să le scoatem din procesul de construire.

Deci, cea mai importantă parte a întrebărilor mele este: a reușit cineva să scrie teste unitare pentru procedurile stocate?

Cea de a doua parte a întrebărilor mele este dacă testarea unității va fi/este mai ușoară cu linq?

Mă gândeam că, mai degrabă decât să fiți nevoit să creați tabele de date de testare, ați putea crea o colecție de obiecte de testare și să vă testați codul linq într-un? Linq la obiecte? situatie? (Sunt un lucru nou pentru linq, așa că nu știu dacă acest lucru ar funcționa chiar deloc)

0
fr hi bn

16 răspunsuri

Ați încercat DBUnit ? Este conceput pentru a testa baza de date și doar baza dvs. de date, fără a fi nevoie să treceți prin codul dvs. C #.

0
adăugat

LINQ va simplifica acest lucru numai dacă eliminați logica din procedurile memorate și o reimplementați ca interogări linq. Care ar fi mult mai robust și mai ușor de testat, cu siguranță. Cu toate acestea, suna ca cerințele dvs. ar împiedica acest lucru.

TL; DR: Designul dvs. are probleme.

0
adăugat

O opțiune de a re-factoriza codul (admit un hack urât) ar fi să o genereze prin CPP (preprocesorul C) M4 (nu a încercat niciodată) sau asemenea. Am un proiect care face exact acest lucru și de fapt este în cea mai mare parte viabil.

Singurul caz pe care cred că ar putea fi valabil pentru este 1) ca o alternativă la procedurile stocate KLOC + și 2) și acesta este cazul meu, atunci când punctul proiectului este de a vedea cât de departe (în nebunie) puteți împinge o tehnologie.

0
adăugat

Folosim DataFresh pentru a face schimbări între fiecare test, apoi testele de testare sunt relativ ușoare.

Ceea ce lipsește încă este un instrument de acoperire a codului.

0
adăugat

Am fugit in aceeasi problema cu un timp inapoi si am constatat ca daca am creat o clasa simpla de baza abstracta pentru accesul la date care mi-a permis sa inject o conexiune si o tranzactie, as putea sa-mi testez unitatea pentru a vedea daca au facut lucrul in SQL le-a cerut să facă și apoi răsturnarea, astfel încât niciunul dintre datele de testare nu este lăsat în db.

Acest lucru sa simtit mai bine decat cel obisnuit "rulati un script pentru a seta testul meu db, apoi dupa ce testele ruleaza faceti o curatare a datelor junk/test". Acest lucru sa simțit, de asemenea, mai aproape de testarea unității, deoarece aceste teste ar putea fi executate singure fără a avea o mulțime de "totul în db trebuie să fie" doar așa "înainte de a rula aceste teste".

Aici este un fragment din clasa de bază abstractă utilizată pentru accesul la date

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

în continuare, veți vedea o clasă de acces de date pentru eșantion utilizând baza de mai sus pentru a obține o listă de produse

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

Și acum, în testul dvs. de unitate, puteți, de asemenea, să moșteniți dintr-o clasă de bază foarte simplă, care funcționează la configurare/revocare - sau păstrați-o pe bază de test pe unitate

de mai jos este clasa simplu de bază de testare pe care am folosit-o

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

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

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

și în final - mai jos este un test simplu care utilizează acea clasă de bază de testare care arată cum să testeze întregul ciclu CRUD pentru a vă asigura că toate lanțurile își fac treaba și că codul dvs. ado.net face cartografia stânga-dreapta corectă

Știu că acest lucru nu testează sprog-ul "spGetProducts" utilizat în proba de acces de date de mai sus, dar ar trebui să vedeți puterea din spatele acestei abordări pentru testarea unităților

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

 _
Public Class ProductRepositoryUnitTest
    Inherits TransactionFixture

    Private mRepository As ProductRepository

     _
    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

Știu că acesta este un exemplu lung, dar a ajutat la o clasă reutilizabilă pentru munca de acces la date și încă o clasă reutilizabilă pentru testarea mea, astfel încât nu am avut de a face munca de setare/teardon de mai multe ori;)

0
adăugat

Dar am impresia că sunteți de fapt mai preocupat de performanță, care nu este de fapt păstrarea testării unității. Testele de unitate ar trebui să fie destul de atomice și sunt destinate să controleze mai degrabă comportamentul decât performanța. Și în acest caz, aproape sigur veți avea nevoie de sarcini de producție pentru a verifica planurile de interogare.

Cred că există două zone de testare foarte diferite: performanța și logica reală a procedurilor stocate.

Am dat exemplul de a testa performanța db în trecut și, din fericire, am ajuns la un punct în care performanța este suficient de bună.

Sunt complet de acord că situația cu toată logica de afaceri în baza de date este una rea, însă este ceva pe care am moștenit-o înainte ca majoritatea dezvoltatorilor noștri să se alăture companiei.

Cu toate acestea, noi adoptăm modelul de servicii web pentru noile noastre funcții și am încercat să evităm procedurile memorate cât mai mult posibil, păstrând logica în codul C# și aruncând SQLCommands la baza de date (deși linq ar fi acum metoda preferată). Există încă o utilizare a SP existente, de aceea m-am gândit să le testez unitatea retrospectiv.

0
adăugat

I am assuming that you want unit testing in MSSQL. Looking at DBUnit there are some limitations in it's support for MSSQL. It doesn't support NVarChar for instance. Here are some real users and their problems with DBUnit.

0
adăugat

Cheia pentru testarea procedurilor stocate este scrierea unui script care stochează o bază de date necompletată cu date care sunt planificate în avans pentru a determina un comportament consistent atunci când se apelează procedurile stocate.

Trebuie să vă votez pentru a favoriza în mare măsură procedurile stocate și pentru a plasa logica dvs. de afaceri în care eu (și majoritatea DBA) cred că aparține, în baza de date.

Știu că noi, ca ingineri de software, dorim ca codul frumos refactat, scris în limba noastră preferată, să conțină toată logica noastră importantă, dar realitățile performanței în sistemele cu volum mare și caracterul critic al integrității datelor ne obligă să facem niște compromisuri . Codul Sql poate fi urât, repetitiv și greu de testat, dar nu-mi pot imagina dificultatea de a regla o bază de date fără a avea un control complet asupra designului interogărilor.

Sunt adesea obligat să reproiectez complet interogările, să includ modificări ale modelului de date, pentru a face ca lucrurile să se desfășoare într-o perioadă acceptabilă de timp. Cu procedurile stocate, pot să vă asigur că modificările vor fi transparente pentru apelant, deoarece o procedură stocată asigură o astfel de încapsulare excelentă.

0
adăugat
Te-ai purtat cu pofta ta, nu? Este logica de afaceri utilizată într-un sens supraîncărcat? Deci, cum ai putea testa logica ta, cu teste de integrare sau stivuire mai mare? Am avut mult mai mult succes păstrând sprocks simplu și făcând logic de ridicare în cod. Am avut experiente oribile cu sprockuri si functii ridicol imbricate, pentru a asigura ca logica de afaceri ramane in DB. Nu pentru mine, mulțumesc.
adăugat autor brumScouse, sursa

Fac testarea unității omului sărac. Dacă sunt leneș, testul este doar câteva invocări valide cu valori potențial problematice ale parametrilor.

/*

--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....
0
adăugat

Dacă vă gândiți la tipul de cod pe care testarea unității tinde să îl promoveze: rutine mici, foarte coezive și cu cuplaj scăzut, atunci ar trebui să vedeți cel puțin o parte a problemei.

În lumea mea cinică, procedurile stocate fac parte din încercarea de lungă durată a RDBMS de a vă convinge să mutați procesarea afacerii dvs. în baza de date, ceea ce are sens atunci când considerați că costurile licenței de server tind să fie legate de lucruri precum numărul de procesoare. Cele mai multe lucruri pe care le executați în baza dvs. de date, cu atât mai mult vor face de la dvs.

Dar am impresia că sunteți de fapt mai preocupat de performanță, care nu este deloc conservarea testării unității. Testele de unitate ar trebui să fie destul de atomice și sunt destinate să controleze mai degrabă comportamentul decât performanța. Și în acest caz, aproape sigur veți avea nevoie de sarcini de producție pentru a verifica planurile de interogare.

Cred că aveți nevoie de o clasă diferită de medii de testare. Aș sugera o copie a producției ca fiind cea mai simplă, presupunând că securitatea nu este o problemă. Apoi, pentru fiecare versiune de candidat, începeți cu versiunea anterioară, migrați folosind procedurile de lansare (ceea ce le va oferi o bună testare ca efect secundar) și veți executa calendarul.

Ceva de genul.

0
adăugat

Problema cu unitatea de testare a oricărui tip de programare legată de date este că trebuie să aveți la dispoziție un set fiabil de date de testare. Multe depind de complexitatea procesului stocat și de ceea ce face. Ar fi foarte greu să automatizezi testarea unităților pentru o procedură foarte complexă care a modificat multe mese.

Unele dintre celelalte postere au notat câteva modalități simple de a automatiza manual testarea acestora, precum și unele instrumente pe care le puteți utiliza cu SQL Server. Pe partea Oracle, guru-ul PL/SQL Steven Feuerstein a lucrat la un instrument gratuit de testare a unităților pentru procedurile stocate PL/SQL numite utPLSQL.

Cu toate acestea, el a renunțat la acest efort și apoi a devenit comercial cu Quest's Code Tester pentru PL/SQL. Quest oferă o versiune de încercare gratuită pentru descărcare. Sunt pe punctul de a încerca; înțelegerea mea este că este bine să ai grijă de gestiunea pentru a stabili un cadru de testare, astfel încât să te poți concentra doar pe testele în sine și păstrează testele astfel încât să le reutilizezi în teste de regresie, unul dintre avantajele mari ale test-driven dezvoltare. În plus, se presupune că este bun la mai mult decât verificarea unei variabile de ieșire și are prevederi pentru validarea modificărilor de date, dar eu trebuie să mă uit la mine mai atent. Am crezut că aceste informații ar putea fi de valoare pentru utilizatorii Oracle.

0
adăugat

Oh baiete. țesăturile nu se supun testării unității (automate). Am un fel de "unitate de testare" sprocs meu complex prin scrierea de teste în fișiere t-sql lot și de mână de control de ieșire a declarațiilor de imprimare și a rezultatelor.

0
adăugat

Puteți încerca, de asemenea, Visual Studio pentru profesioniști baze de date . Este vorba în principal de gestionarea schimbărilor, dar are, de asemenea, instrumente pentru generarea de date de testare și teste unitare.

E destul de scump.

0
adăugat

Buna intrebare.

Am probleme similare și am luat calea cea mai mică rezistență (pentru mine, oricum).

Există o grămadă de alte soluții, pe care alții le-au menționat. Multe dintre ele sunt mai bune/mai pure/mai potrivite pentru ceilalți.

Utilizaam deja Testdriven.NET/MbUnit pentru a-mi testa C#, așa că am adăugat teste pentru fiecare proiect pentru a apela procedurile stocate utilizate de acea aplicație.

Știu, știu. Sună groaznic, dar ceea ce am nevoie este să cobor la pământ cu testele unii și să plec de acolo. Această abordare înseamnă că, deși acoperirea mea este scăzută, eu testez câteva procs stocate în același timp cu testarea codului care le va fi chemat. Există o logică în acest sens.

0
adăugat

Ne unitate de testare a codului C# care solicită SP-uri Am construit scripturi, creând baze de date curate Și cele mai mari pe care le atașăm și se detașează în timpul încercării Aceste teste ar putea dura câteva ore, dar cred că merită.

0
adăugat

Sunt în exact aceeași situație ca posterul original. Se reduce la performanță față de testabilitate. Înclinația mea se îndreaptă spre testabilitate (face-o să funcționeze, face bine, fă-o repede), ceea ce sugerează păstrarea logicii de afaceri din baza de date. Bazele de date nu numai că nu au cadre de testare, construcții de factoring de cod și analize de cod și instrumente de navigare găsite în limbi precum Java, dar și codul de baze de date este foarte lent (în cazul în care codul Java nu este foarte fiabil).

Cu toate acestea, recunosc puterea procesării setului de baze de date. Atunci când este folosit în mod corespunzător, SQL poate face unele lucruri incredibil de puternice, cu foarte puține coduri. Deci, eu am o ordine bazată pe logică bazată pe baze de date, chiar dacă voi face tot ce pot pentru a încerca unitatea.

Pe o notă asemănătoare, se pare că codul bazei de date foarte lungi și procedurale este adesea un simptom al altui lucru și cred că un astfel de cod poate fi transformat într-un cod care poate fi testat fără să se producă o lovitură de performanță. Teoria este că un astfel de cod reprezintă deseori procese care procesează periodic cantități mari de date. Dacă aceste procese batch vor fi transformate în bucăți mai mici de logică de afaceri în timp real, care se execută ori de câte ori se schimbă datele de intrare, această logică ar putea fi rulată pe nivelul intermediar (unde poate fi testat) lucrarea se face în bucăți mici în timp real). Ca efect secundar, acest lucru elimină și buclele de feedback lungi ale manipulării erorilor de proces în șarje. Desigur, această abordare nu va funcționa în toate cazurile, dar poate funcționa și în unele. De asemenea, dacă există tone de astfel de coduri de bază de prelucrare a lotului netestabil în sistemul dvs., drumul către mântuire poate fi lung și greoi. YMMV.

0
adăugat