{"id":298,"date":"2007-08-20T00:00:00","date_gmt":"2007-08-20T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/close-those-loopholes-testing-stored-procedures\/"},"modified":"2021-09-29T16:22:16","modified_gmt":"2021-09-29T16:22:16","slug":"close-those-loopholes-testing-stored-procedures","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/close-those-loopholes-testing-stored-procedures\/","title":{"rendered":"Close those Loopholes &#8211; Testing Stored Procedures"},"content":{"rendered":"<div id=\"article\">\n<p class=\"author\">AK:<\/p>\n<p class=\"what\">In the previous article, <a href=\"http:\/\/www.simple-talk.com\/sql\/t-sql-programming\/close-these-loopholes-in-your-database-testing\/\">Close those Loopholes in your Database Testing<\/a>, I was discussing database testing in general. In this article Alex Styler and I will concentrate on database unit testing. Unit testing is becoming more and more common among C#, C++, and Java programmers, because it is very important in ensuring high quality of software. In the following article we shall demonstrate a simple way to test stored procedures that return result sets. In the next one we shall discuss a few more complex techniques used in testing database modifications.<\/p>\n<h3>Why use C# and NUnit for database unit testing?<\/h3>\n<p class=\"author\">AK:<\/p>\n<p class=\"what\">Although there are alternatives, such as Team Edition for Database Professionals and TSQLUnit, the choice of C# and NUnit was natural for me. The reason is simple: being an old hand, I wanted to go for a commonly used, well-known, widespread approach. This is where C# fits the bill perfectly &#8211; in Windows programming universe C# is as common as it goes. And NUnit is a very commonly used library for development of unit tests in C# &#8211; it has been around for quite a while too. More to the point, if you are working with SQL Server, you are quite likely to be already familiar with C# or at least Visual Studio.<\/p>\n<p class=\"what\">I am primarily a database developer. As such, I certainly prefer doing things in the database. For example, I am used to writing tests for my stored procedures in T-SQL &#8211; I have been doing that for years. However, I think that Visual Studio and NUnit are much better tools for the tedious task of thorough testing. Even though I am much more proficient in Transact SQL than in C#, I definitely prefer to write unit tests for my stored procedures using C# and NUnit.<\/p>\n<h3>Creating a unit test<\/h3>\n<p class=\"author\">AK:<\/p>\n<p class=\"what\">Consider the following table, initial data, and existing stored procedure:<\/p>\n<p><code>CREATE\u00a0TABLE\u00a0data.Employees( <br \/>\n\u00a0\u00a0EmployeeID\u00a0INT\u00a0NOT\u00a0NULL, <br \/>\n\u00a0\u00a0FirstName\u00a0VARCHAR(20)\u00a0NOT\u00a0NULL,\u00a0 <br \/>\n\u00a0\u00a0LastName\u00a0VARCHAR(20)\u00a0NOT\u00a0NULL, <br \/>\n\u00a0\u00a0Status\u00a0VARCHAR(6)\u00a0NOT\u00a0NULL\u00a0CONSTRAINT\u00a0CHK_Employees_Status\u00a0CHECK(Status\u00a0IN('Active',\u00a0'Quit')), <br \/>\n\u00a0\u00a0HireDate\u00a0DATETIME\u00a0NOT\u00a0NULL <br \/>\n) <br \/>\nGO <\/p>\n<p>INSERT\u00a0INTO\u00a0data.Employees(EmployeeID,\u00a0FirstName,\u00a0LastName,\u00a0Status,\u00a0HireDate)\u00a0VALUES(1,\u00a0'Sean',\u00a0'Hansen',\u00a0'Active',\u00a0'20070105') <br \/>\nINSERT\u00a0INTO\u00a0data.Employees(EmployeeID,\u00a0FirstName,\u00a0LastName,\u00a0Status,\u00a0HireDate)\u00a0VALUES(2,\u00a0'Don',\u00a0'Wang',\u00a0'Active',\u00a0'20070106') <br \/>\nGO <\/p>\n<p>CREATE\u00a0PROCEDURE\u00a0Readers.SelectEmployeesByLastName <br \/>\n\u00a0\u00a0@LastName\u00a0VARCHAR(20) <br \/>\nAS <br \/>\nSELECT\u00a0*\u00a0FROM\u00a0data.Employees\u00a0WHERE\u00a0LastName\u00a0=\u00a0@LastName <br \/>\nORDER\u00a0BY\u00a0FirstName <br \/>\nGO <br \/>\n<\/code><\/p>\n<p class=\"what\">Suppose you need to write a unit test for that stored procedure. You might want to verify that your result set has correct structure (correct column names and types) and that is contains correct data. Because this is a very common task, it definitely makes sense to implement all these comparisons once, in a C# class, and use the library many times. For any reasonable project you will have at least hundreds of such tests, so implementing the comparison once in a class is definitely worth the effort. Once all the comparison logic has been implemented, your typical unit test is very short and simple:<\/p>\n<p class=\"MsoNormal\">private const bool bSetupMode = true;<br \/>\n\u00a0<br \/>\n[Test]<br \/>\npublic void SelectEmployeesByLastName_Test()<br \/>\n{<br \/>\n\u00a0\u00a0 string filename = &#8220;C:\/Temp\/SelectEmployeesByLastNameTest.xml&#8221;;<br \/>\n\u00a0\u00a0 StoredProcedureTester tester = new<br \/>\n\u00a0\u00a0 StoredProcedureTester( connection, SelectEmployeesByLastName&#8221; );<br \/>\n\u00a0\u00a0 <br \/>\n\u00a0\u00a0 tester.SetInputParameter( &#8220;@lastname&#8221;, &#8220;Hansen&#8221; );<br \/>\n\u00a0\u00a0\u00a0 <br \/>\n\u00a0\u00a0 if (bSetupMode)<br \/>\n\u00a0\u00a0\u00a0 {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 tester.OutputToFile( filename );<br \/>\n\u00a0\u00a0\u00a0 }<br \/>\n\u00a0\u00a0\u00a0 else<br \/>\n\u00a0\u00a0\u00a0 {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0 Assert.IsTrue(tester.CompareToFile( filename ));<br \/>\n\u00a0\u00a0\u00a0 }<br \/>\n}<\/p>\n<p class=\"what\">When you set up your unit test, your result set (or result sets, if your stored procedure returns several ones) is saved into an XML file. All the information necessary for further comparison is saved &#8211; column names, column types, and the data. The XML file needs to be checked into your source control system, because it is part of your test harness. When you run your unit test, the result set is compared against the saved XML file. Everything is compared &#8211; column names, column types, and all the data Both OutputToFile and CompareToFile are methods of StoredProcedureTester class. We shall provide the implementation of the class in the next chapters.<\/p>\n<h3>Unit testing is not a substitute for other types of testing.<\/h3>\n<p class=\"author\">AK:<\/p>\n<p class=\"what\">Unit tests by definition must run consistently, always producing the same results. As such, they do not expose all the problems. For instance, in some cases concurrency may affect your selects &#8211; they may run perfectly in a single-connection test environment but give you problems in a multi-user production one. For example, you can silently get incorrect results because of your choice of isolation level. One possible scenario is described in this article:<br \/>\n<a href=\"http:\/\/www.devx.com\/dbzone\/Article\/32957\">When Snapshot Isolation Helps and When It Hurts<\/a><\/p>\n<p class=\"what\">In some cases a select can even blow up because of concurrency, as described in the following excellent blog entry in Craig Freedman&#8217;s WebLog: <a href=\"http:\/\/blogs.msdn.com\/craigfr\/archive\/2007\/06\/12\/query-failure-with-read-uncommitted.aspx\">Query Failure with Read Uncommitted<\/a><\/p>\n<p class=\"what\">Unfortunately, problems caused by concurrency may be intermittent &#8211; sometimes they can be reproduced, but not in exactly the same way every time you run your tests. In some cases if you stress test your selects properly, and your selects have problems, then your stress testing is likely to expose these problems sooner or later. This is why exposing problems in your selects caused by concurrency, however important it is, is mostly beyond the scope of unit testing and this article.<\/p>\n<h3>The Result Set Tester<\/h3>\n<p class=\"author\">AK:<\/p>\n<p class=\"what\">In the &#8220;Creating Unit Test&#8221; chapter I utilized StoredProcedureTester class. In this and the next chapter AS will demonstrate how to implement this class. <a>The following implementation <\/a>has been stripped of all the bells and whistles &#8211; we left just enough code to demonstrate the technique in its simplest form. For example, a fully functional library should be able to compare result sets that are ordered on a non-unique column, such as FirstName, in one of the preceding examples. While our actual library can do all this and more, our example has been stripped of all this functionality.<\/p>\n<p class=\"author\">AS:<\/p>\n<p class=\"what\">We included a more powerful version of the testing library in the attached archive. The archive version abandons datasets in favor of speed and greater control over data comparisons. It might be a little harder to understand at first, but if you need greater control and flexibility, it might be worth your time to check it out.<\/p>\n<p class=\"what\">Behind a seemingly simple unit test are the classes necessary to compare the result sets of your procedures. The first class, <b>ResultSetTester<\/b>, acts as an interface and provides most of the basic functionality for testing. It is constructed with a connection, implying a server and database, and the name of a stored procedure. You can then supply input and output parameters and either save the results to a file or compare the results to a saved file. Output parameters must be specified with a type and size and can be fetched after the procedure has run.<\/p>\n<p class=\"MsoNormal\">using System;<br \/>\nusing System.Collections.Generic;<br \/>\nusing System.Data;<br \/>\nusing System.Data.SqlClient;<br \/>\n\u00a0<br \/>\nnamespace UnitTestingTools<br \/>\n{<br \/>\n\u00a0\u00a0\u00a0 public class ResultSetTester<br \/>\n\u00a0\u00a0\u00a0 {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 private readonly DataSet results = new DataSet();<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 private readonly SqlCommand storedProcedure;<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public ResultSetTester(SqlConnection connection, string procedureName)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 storedProcedure = new SqlCommand(procedureName, connection);<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 storedProcedure.CommandType = CommandType.StoredProcedure;<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public bool CompareToFile(string filename)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 RunProcedure();<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 DataSet other = new DataSet();<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 other.ReadXml(filename);<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 other.AcceptChanges();<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return DataSetComparer.Compare(results, other);<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public void OutputToFile(string filename)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 RunProcedure();<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 results.WriteXml(filename, XmlWriteMode.WriteSchema);<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public IEnumerable&lt;KeyValuePair&lt;string, object&gt;&gt; OuputParameters<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 get<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Dictionary&lt;string, object&gt; outputParams = new Dictionary&lt;string, object&gt;();<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 foreach(SqlParameter p in storedProcedure.Parameters)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if(p.Direction == ParameterDirection.Output)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 outputParams.Add(p.ParameterName, p.Value);<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return outputParams;<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public void SetInputParameter(string parameterName, object parameterValue)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SqlParameterCollection parameters = storedProcedure.Parameters;<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if(parameters.Contains(parameterName))<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 parameters[parameterName] = new SqlParameter(parameterName, parameterValue);<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 else<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 parameters.AddWithValue(parameterName, parameterValue);<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 public void SetOutputParameter(string parameterName, SqlDbType type, int size)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SqlParameterCollection parameters = storedProcedure.Parameters;<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if(parameters.Contains(parameterName))<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 parameters.RemoveAt(parameterName);<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 parameters.Add(parameterName, type);<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 parameters[parameterName].Direction = ParameterDirection.Output;<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if(size &gt; 0)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 parameters[parameterName].Size = size;<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 private void RunProcedure()<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 results.Reset();<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 using(SqlDataAdapter da = new SqlDataAdapter(storedProcedure))<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 da.Fill(results);<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 for(int i = 0; i &lt; results.Tables.Count; i++)<br \/>\n\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0results.Tables[i].TableName = &#8220;Result Set &#8221; + i;<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 results.AcceptChanges();<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<br \/>\n\u00a0\u00a0\u00a0 }<br \/>\n}<\/p>\n<h3>The DataSet Comparer<\/h3>\n<p class=\"author\">AS:<\/p>\n<p class=\"what\">One of the most powerful of the ADO.NET objects is the <b>DataSet<\/b>. It is used in this example due to the ease of fetching results and the simplicity of storing the results in a file. However, due to varied opinions among developers on what defines the equality of data, the <b>DataSet<\/b> provides no means for data comparison.<\/p>\n<p class=\"what\">This, however, allows you to define a means to compare data, directly determining how result sets are compared. In the example class below, <b>DataSetComparer<\/b>, the data must be exactly the same with the same ordering of result sets, rows, and columns. This works for all common data types, but would need custom code to work with user defined types or <b>SqlVariant<\/b> types that cannot be converted to strings. You can alter this comparer to ignore certain column types, allow varying order of rows, or compare only general trends of the data to fit your needs.<\/p>\n<p class=\"MsoNormal\">using System;<br \/>\nusing System.Data;<br \/>\n\u00a0<br \/>\nnamespace UnitTestingTools<br \/>\n{<br \/>\n\u00a0\u00a0 internal static class DataSetComparer<br \/>\n\u00a0\u00a0 {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0 internal static bool Compare(DataSet one, DataSet two)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0 {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if(one.Tables.Count != two.Tables.Count)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return false;<br \/>\n\u00a0<br \/>\n\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0for(int i = 0; i &lt; one.Tables.Count; i++)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if(!CompareTables(one.Tables[i], two.Tables[i]))<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return false;<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return true;<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0 private static bool CompareTables(DataTable one, DataTable two)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0 {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0if(one.Rows.Count != two.Rows.Count)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return false;<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 for(int i = 0; i &lt; one.Rows.Count; i++)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if(!CompareRows(one.Rows[i], two.Rows[i]))<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return false;<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return true;<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0 }<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0 private static bool CompareRows(DataRow one, DataRow two)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0 {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if(one.ItemArray.Length != two.ItemArray.Length)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return false;<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 for(int i = 0; i &lt; one.ItemArray.Length; i++)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if(!CompareItems(one.ItemArray[i], two.ItemArray[i]))<br \/>\n\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return false;<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return true;<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0 }<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0 private static bool CompareItems(object value1, object value2)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0 {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if(value1.GetType() != value2.GetType())<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return false;<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if(value1 is DBNull)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return true;<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if(value1 is DateTime)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return ((DateTime) value1).CompareTo((DateTime) value2)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 == 0;<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if(value1 is byte[])<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if(((byte[]) value1).Length != ((byte[]) value2).Length)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return false;<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 for(int i = 0; i &lt; ((byte[]) value1).Length; i++)<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if(((byte[]) value1)[i] != ((byte[]) value2)[i])<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return false;<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return true;<br \/>\n\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0}<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 return value1.ToString().Equals(value2.ToString());<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0 }<br \/>\n\u00a0\u00a0 }<\/p>\n<p class=\"MsoNormal\">}<\/p>\n<h3>Conclusions<\/h3>\n<p class=\"author\">AS:<\/p>\n<p class=\"what\">If you are using C# and NUnit, you already have all the necessary tools to start covering your stored procedures with unit tests. Good luck!<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Alex and Alex continue their series of articles on &#8216;Unit Testing&#8217; database development work with some examples of unit testing stored procedures.&hellip;<\/p>\n","protected":false},"author":14235,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143531],"tags":[4168,4150,4252,4815],"coauthors":[48777],"class_list":["post-298","post","type-post","status-publish","format-standard","hentry","category-t-sql-programming-sql-server","tag-database","tag-sql","tag-t-sql-programming","tag-unit-test-nunit-stored-procedure"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/298","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/14235"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=298"}],"version-history":[{"count":3,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/298\/revisions"}],"predecessor-version":[{"id":74430,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/298\/revisions\/74430"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=298"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=298"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=298"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=298"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}