Сведения о вопросе

Getthesound

05:49, 18th August, 2020

Теги

Есть ли у кого-нибудь успехи в модульном тестировании SQL хранимых процедур?

Просмотров: 506   Ответов: 16

Мы обнаружили, что модульные тесты, которые мы написали для нашего кода C#/C++, действительно окупились. Но у нас все еще есть тысячи линий бизнес-логики в хранимых процедурах, которые действительно тестируются в гневе, когда наш продукт развертывается для большого числа пользователей.

Хуже всего то, что некоторые из этих хранимых процедур оказываются очень длинными из-за снижения производительности при передаче временных таблиц между SPs. Это помешало нам провести рефакторинг для упрощения кода.

Мы предприняли несколько попыток построить модульные тесты вокруг некоторых наших ключевых хранимых процедур (в первую очередь тестирования производительности), но обнаружили, что настройка тестовых данных для этих тестов действительно трудна. Например, мы в конечном итоге копируем вокруг тестовых баз данных. В дополнение к этому, тесты в конечном итоге оказываются действительно чувствительными к изменениям, и даже самым маленьким изменениям в сохраненном проке. или таблица требует большого количества изменений в тестах. Поэтому после того, как многие сборки ломаются из-за того, что эти тесты базы данных периодически проваливаются, нам просто нужно было вытащить их из процесса сборки.

Итак, основная часть моих вопросов заключается в следующем: кто-нибудь когда-нибудь успешно писал модульные тесты для своих хранимых процедур?

Вторая часть моих вопросов заключается в том, будет ли модульное тестирование проще/легче с linq?

Я подумал, что вместо того, чтобы создавать таблицы тестовых данных, вы можете просто создать коллекцию тестовых объектов и протестировать свой код linq в ситуации “linq to objects”? (Я совершенно новичок в linq, так что не знаю, сработает ли это вообще)



  Сведения об ответе

ASER

10:24, 5th August, 2020

Я столкнулся с этой же проблемой некоторое время назад и обнаружил, что если бы я создал простой абстрактный базовый класс для доступа к данным, который позволял бы мне вводить соединение и транзакцию, я мог бы юнит-тестировать свои sprocs, чтобы увидеть, выполнили ли они работу в SQL, которую я попросил их сделать, а затем откатить, чтобы ни один из тестовых данных не остался в БД.

Это было лучше, чем обычный "запуск скрипта для настройки моей тестовой БД, а затем после запуска тестов выполните очистку от мусора/тестовых данных". Это также чувствовалось ближе к модульному тестированию, потому что эти тесты можно было бы запускать в одиночку без большого количества "все в БД должно быть 'just so', прежде чем я выполню эти тесты".

Вот фрагмент абстрактного базового класса, используемого для доступа к данным

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, чтобы убедиться, что все sprocs выполняют свою работу и что ваш код ado.net правильно выполняет сопоставление слева направо

Я знаю, что это не тестирует "spGetProducts" sproc, используемый в приведенном выше примере доступа к данным, но вы должны увидеть силу этого подхода к модульному тестированию sproc

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

Я знаю, что это длинный пример, но он помог иметь многоразовый класс для работы с доступом к данным и еще один многоразовый класс для моего тестирования, поэтому мне не нужно было снова и снова выполнять установку/демонтаж ;)


  Сведения об ответе

lourence

00:27, 7th August, 2020

Вы пробовали DBUnit ? Он предназначен для модульного тестирования вашей базы данных, и только вашей базы данных, без необходимости проходить через ваш код C#.


  Сведения об ответе

padenie

18:46, 7th August, 2020

Если вы подумаете о типе кода, который модульное тестирование обычно продвигает: небольшие высокосвязанные и слабо связанные подпрограммы, то вы должны в значительной степени быть в состоянии увидеть, где может быть хотя бы часть проблемы.

В моем циничном мире хранимые процедуры являются частью давней попытки мира RDBMS убедить вас перенести обработку вашего бизнеса в базу данных, что имеет смысл, если учесть, что стоимость лицензии сервера, как правило, связана с такими вещами, как количество процессоров. Чем больше материала вы запускаете в своей базе данных, тем больше они делают из вас.

Но у меня сложилось впечатление, что вы на самом деле больше озабочены производительностью, которая на самом деле вовсе не является прерогативой юнит-тестирования. Модульные тесты должны быть достаточно атомарными и предназначены для проверки поведения, а не производительности. И в этом случае вам почти наверняка понадобятся нагрузки производственного класса, чтобы проверить планы запросов.

Я думаю, что вам нужен другой класс среды тестирования. Я бы предложил копию производства как самую простую, предполагая, что безопасность не является проблемой. Затем для каждого выпуска-кандидата вы начинаете с предыдущей версии, мигрируете с помощью своих процедур выпуска (что даст им хорошее тестирование в качестве побочного эффекта) и запускаете свои тайминги.

Что-то вроде того.


  Сведения об ответе

P_S_S

21:06, 1st October, 2020

Ключом к тестированию хранимых процедур является написание сценария, который заполняет пустую базу данных данными, заранее запланированными для обеспечения согласованного поведения при вызове хранимых процедур.

Я должен отдать свой голос за то, чтобы сильно поддержать хранимые процедуры и поместить вашу бизнес-логику туда, где я (и большинство DBAs) думаю, что она принадлежит, в базу данных.

Я знаю, что мы, инженеры-программисты, хотим, чтобы красиво переработанный код, написанный на нашем любимом языке, содержал всю нашу важную логику, но реалии производительности в системах большого объема и критическая природа целостности данных требуют от нас некоторых компромиссов. Sql код может быть уродливым, повторяющимся и трудным для тестирования, но я не могу представить себе трудности настройки базы данных без полного контроля над дизайном запросов.

Я часто вынужден полностью переделывать запросы, включать изменения в модель данных, чтобы заставить вещи работать в приемлемое время. С помощью хранимых процедур я могу гарантировать, что изменения будут прозрачны для вызывающего объекта, поскольку хранимая процедура обеспечивает такую превосходную инкапсуляцию.


  Сведения об ответе

screen

06:37, 13th August, 2020

Я предполагаю, что вы хотите провести модульное тестирование в MSSQL. Глядя на DBUnit, есть некоторые ограничения в его поддержке для MSSQL. Например, он не поддерживает NVarChar. Вот некоторые реальные пользователи и их проблемы с DBUnit.


  Сведения об ответе

SEEYOU

12:10, 4th August, 2020

Лучший вопрос.

У меня есть похожие проблемы, и я пошел по пути наименьшего сопротивления (во всяком случае, для меня).

Есть куча других решений, о которых уже упоминали другие. Многие из них лучше / более чистые / более подходящие для других.

Я уже использовал Testdriven.NET/MbUnit для тестирования своего C#,, поэтому я просто добавил тесты в каждый проект, чтобы вызвать хранимые процедуры, используемые этим приложением.

Я знаю, я знаю. Это звучит ужасно, но что мне нужно, так это оторваться от Земли с некоторыми испытаниями и идти оттуда. Этот подход означает, что, хотя мой охват невелик, я тестирую некоторые сохраненные процессы одновременно с тестированием кода, который будет их вызывать. В этом есть определенная логика.


  Сведения об ответе

DAAA

10:24, 14th August, 2020

Я нахожусь в той же самой ситуации, что и оригинальный плакат. Все сводится к производительности и тестируемости. Я склоняюсь к тестируемости (сделать его работоспособным, сделать его правильным, сделать его быстрым), что предполагает сохранение бизнес-логики вне базы данных. В базах данных не только отсутствуют тестовые платформы, конструкции факторинга кода, а также инструменты анализа кода и навигации, найденные в таких языках, как Java, но и высокофакторизованный код базы данных также медленен (там, где высокофакторизованный код Java отсутствует).

Тем не менее, я признаю силу обработки набора баз данных. При правильном использовании SQL может делать некоторые невероятно мощные вещи с очень небольшим количеством кода. Итак, я согласен с некоторой логикой, основанной на множестве, живущей в базе данных, хотя я все равно сделаю все возможное, чтобы ее проверить.

В связи с этим, похоже, что очень длинный и процедурный код базы данных часто является симптомом чего-то другого, и я думаю, что такой код можно преобразовать в тестируемый код без ущерба для производительности. Теория заключается в том, что такой код часто представляет собой пакетные процессы, которые периодически обрабатывают большие объемы данных. Если бы эти пакетные процессы были преобразованы в более мелкие фрагменты бизнес-логики реального времени, которая выполняется всякий раз, когда входные данные изменяются, эта логика могла бы выполняться на среднем уровне (где она может быть протестирована) без снижения производительности (поскольку работа выполняется небольшими фрагментами в реальном времени). В качестве побочного эффекта это также устраняет длинные обратные циклы обработки ошибок пакетного процесса. Конечно, этот подход не будет работать во всех случаях, но он может сработать в некоторых. Кроме того, если в вашей системе есть тонны такого непроверяемого кода базы данных пакетной обработки, путь к спасению может быть долгим и трудным. YMMV.


  Сведения об ответе

ЯЯ__4

18:47, 1st August, 2020

Вы также можете попробовать Visual Studio для специалистов по базам данных . Это в основном управление изменениями, но также есть инструменты для генерации тестовых данных и модульных тестов.

Это довольно дорого, Тхо.


  Сведения об ответе

Chhiki

17:14, 3rd August, 2020

Но у меня сложилось впечатление, что вы на самом деле больше озабочены производительностью, которая на самом деле вовсе не является прерогативой юнит-тестирования. Модульные тесты должны быть достаточно атомарными и предназначены для проверки поведения, а не производительности. И в этом случае вам почти наверняка понадобятся нагрузки производственного класса, чтобы проверить планы запросов.

Я думаю, что здесь есть две совершенно разные области тестирования: производительность и фактическая логика хранимых процедур.

Я привел пример тестирования производительности БД в прошлом, и, к счастью, мы достигли точки, где производительность достаточно хороша.

Я полностью согласен с тем, что ситуация со всей бизнес-логикой в базе данных является плохой, но это то, что мы унаследовали от большинства наших разработчиков, прежде чем они присоединились к компании.

Однако теперь мы используем модель веб-служб для наших новых функций, и мы стараемся максимально избегать хранимых процедур, сохраняя логику в коде C# и запуская SQLCommands в базе данных (хотя теперь предпочтительным методом будет linq). Существует еще некоторое использование существующего SPs, поэтому я думал о ретроспективном юнит-тестировании их.


  Сведения об ответе

SEEYOU

21:07, 20th August, 2020

Мы используем DataFresh для отката изменений между каждым тестом, тогда тестирование sprocs относительно легко.

Чего все еще не хватает, так это инструментов покрытия кода.


  Сведения об ответе

PIRLO

06:22, 15th August, 2020

Я провожу модульное тестирование бедняги. Если я ленив, то тест-это всего лишь пара допустимых вызовов с потенциально проблемными значениями параметров.

/*

--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....


  Сведения об ответе

PAGE

13:47, 16th August, 2020

О, боже. хранимые процедуры не поддаются (автоматизированное) тестирование устройства. Я сортирую "unit test" моих сложных sprocs, записывая тесты в пакетные файлы t-sql и вручную проверяя вывод инструкций печати и результаты.


  Сведения об ответе

LAST

03:35, 10th August, 2020

Проблема модульного тестирования любого вида программирования, связанного с данными, заключается в том, что для начала необходимо иметь надежный набор тестовых данных. Многое также зависит от сложности сохраненного прока и того, что он делает. Было бы очень трудно автоматизировать модульное тестирование для очень сложной процедуры, которая изменила бы многие таблицы.

Некоторые из других плакатов отметили несколько простых способов автоматизации ручного тестирования, а также некоторые инструменты, которые вы можете использовать с SQL Server. На стороне Oracle гуру PL/SQL Стивен Фойерштейн работал над бесплатным инструментом модульного тестирования для хранимых процедур PL/SQL под названием utPLSQL.

Тем не менее, он отказался от этой попытки, а затем пошел на коммерческий рынок с тестером кода Quest для PL/SQL. Quest предлагает бесплатную загружаемую пробную версию. Я нахожусь на грани того, чтобы опробовать его; я понимаю, что он хорошо справляется с накладными расходами при настройке тестовой платформы, чтобы вы могли сосредоточиться только на самих тестах, и он сохраняет тесты, чтобы вы могли повторно использовать их в регрессионном тестировании, что является одним из больших преимуществ test-driven-development. Кроме того, предполагается, что он хорош не только для проверки выходной переменной, но и для проверки изменений данных, но я все равно должен сам посмотреть поближе. Я подумал, что эта информация может быть полезна для пользователей Oracle.


  Сведения об ответе

Chhiki

23:02, 1st August, 2020

LINQ упростит это только в том случае, если вы удалите логику из хранимых процедур и повторно примените ее в виде запросов linq. Что было бы намного надежнее и легче проверить, определенно. Однако, похоже, что ваши требования исключат это.

TL;DR: ваш дизайн имеет проблемы.


  Сведения об ответе

qwerty101

19:33, 3rd August, 2020

Мы модульно тестируем код C#, который вызывает SPs.
У нас есть сценарии сборки, создания чистых тестовых баз данных.
И более крупные мы прикрепляем и отсоединяем во время тестового приспособления.
Эти тесты могут занять несколько часов, но я думаю, что это того стоит.


  Сведения об ответе

padenie

16:59, 17th August, 2020

Одним из вариантов пересчета кода (я признаю, что это уродливый Хак) было бы сгенерировать его через CPP (препроцессор C) M4 (никогда не пробовал) или что-то подобное. У меня есть проект, который делает именно это, и он в основном работоспособен.

Единственный случай, который я думаю, что это может быть допустимо, - это 1) как альтернатива KLOC+ хранимым процедурам и 2) и это мои случаи, когда смысл проекта заключается в том, чтобы увидеть, как далеко (в безумие) вы можете продвинуть технологию.


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться