gpt4 book ai didi

sql - 有人在单元测试 SQL 存储过程方面取得过成功吗?

转载 作者:行者123 更新时间:2023-12-03 07:40:30 24 4
gpt4 key购买 nike

我们发现为 C#/C++ 代码编写的单元测试确实得到了返回。但我们的存储过程中仍然有数千行业务逻辑,只有当我们的产品向大量用户推出时,它们才会真正受到愤怒的测试。

更糟糕的是,由于在 SP 之间传递临时表时性能受到影响,其中一些存储过程最终变得非常长。这导致我们无法重构以使代码更简单。

我们已经多次尝试围绕一些关键存储过程构建单元测试(主要测试性能),但发现为这些测试设置测试数据非常困难。例如,我们最终会复制测试数据库。除此之外,测试最终对更改非常敏感,甚至是对存储过程的最小更改。或表需要对测试进行大量更改。因此,在由于这些数据库测试间歇性失败而导致许多构建中断之后,我们不得不将它们从构建过程中删除。

所以,我的问题的主要部分是:是否有人成功地为其存储过程编写了单元测试?

我的问题的第二部分是使用 linq 进行单元测试是否会更容易?

我在想,您可以简单地创建测试对象的集合,并在“linq to object”情况下测试您的 linq 代码,而不是设置测试数据表? (我是 linq 的新手,所以不知道这是否有效)

最佳答案

不久前我遇到了同样的问题,发现如果我为数据访问创建一个简单的抽象基类,允许我注入(inject)连接和事务,我可以对我的存储过程进行单元测试,看看它们是否完成了工作我要求他们执行的 SQL,然后回滚,这样数据库中就不会留下任何测试数据。

这比通常的“运行脚本来设置我的测试数据库,然后在测试运行后清理垃圾/测试数据”感觉更好。这也感觉更接近于单元测试,因为这些测试可以单独运行,而无需大量“在运行这些测试之前,数据库中的所有内容都需要‘恰到好处’”。

这是用于数据访问的抽象基类的片段

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

接下来您将看到一个使用上述基础来获取产品列表的示例数据访问类

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

现在在单元测试中,您还可以从一个非常简单的基类继承来执行您的设置/回滚工作 - 或者将其保留在每个单元测试的基础上

下面是我使用的简单测试基类

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

最后 - 下面是一个使用该测试基类的简单测试,它展示了如何测试整个 CRUD 周期,以确保所有存储过程都完成其工作,并且您的 ado.net 代码执行左右操作正确映射

我知道这不会测试上述数据访问示例中使用的“spGetProducts”存储过程,但您应该看到这种单元测试存储过程方法背后的强大功能

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

我知道这是一个很长的示例,但它有助于为数据访问工作提供一个可重用的类,并为我的测试提供另一个可重用的类,因此我不必一遍又一遍地进行设置/拆卸工作;)

关于sql - 有人在单元测试 SQL 存储过程方面取得过成功吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12374/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com