//=============================================================================== // Copyright © Microsoft Corporation and Mark Seemann. All rights reserved. // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS FOR A PARTICULAR PURPOSE. //=============================================================================== using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Diagnostics; using System.Globalization; using System.IO; using System.Security.Permissions; namespace Ploeh.Common.DBTest { /// /// Enables integration testing against SQL Server. /// /// /// /// SqlServerIntegrationTester contains helper methods to create and delete SQL server /// databases for testing purposes. To create a transient database for testing purposes, use /// the . To delete it again, use the /// method. /// /// /// The and collections are /// used to define T-SQL scripts for defining the database schema, etc. /// /// public class SqlServerIntegrationTester { private string server_; private string testDatabaseName_; private List initializationScripts_; private List cleanUpScripts_; /// /// Initializes a new instance of the class. /// /// /// /// Before a new instance of the class is used, /// the and properties must be set. /// /// public SqlServerIntegrationTester() { this.initializationScripts_ = new List(); this.cleanUpScripts_ = new List(); } /// /// Gets or sets the name of the database server. /// public string Server { get { return this.server_; } set { this.server_ = value; } } /// /// Gets or sets the name of the test database that will be created by /// . /// public string TestDatabaseName { get { return this.testDatabaseName_; } set { this.testDatabaseName_ = value; } } /// /// Gets the connection string for the test database. /// public string TestDatabaseConnectionString { get { SqlConnectionStringBuilder csb = new SqlConnectionStringBuilder(); csb.DataSource = this.Server; csb.InitialCatalog = this.TestDatabaseName; csb.IntegratedSecurity = true; csb.Pooling = false; return csb.ConnectionString; } } /// /// Gets the connection string for the master database. /// public string MasterDatabaseConnectionString { get { SqlConnectionStringBuilder csb = new SqlConnectionStringBuilder(); csb.DataSource = this.Server; csb.InitialCatalog = "master"; csb.IntegratedSecurity = true; csb.Pooling = false; return csb.ConnectionString; } } /// /// Gets the list of initialization scripts. /// public IList InitializationScripts { get { return this.initializationScripts_; } } /// /// Gets the list of clean-up scripts. /// public IList CleanUpScripts { get { return this.cleanUpScripts_; } } /// /// Creates the test database and database objects. /// [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] public void CreateTestDatabase() { this.DeleteTestDatabase(); // Create the database. SqlServerIntegrationTester.ExecuteNonQuery(this.MasterDatabaseConnectionString, "CREATE DATABASE {0}", this.TestDatabaseName); // Create the objects in the database. foreach(string scriptFile in this.InitializationScripts) { this.RunSqlScript(scriptFile); } } /// /// Deletes the test database. /// public void DeleteTestDatabase() { SqlServerIntegrationTester.ExecuteNonQuery(this.MasterDatabaseConnectionString, "IF EXISTS (SELECT [name] FROM sys.databases WHERE [name] = '{0}') BEGIN DROP DATABASE {0} END", this.TestDatabaseName); } /// /// Executes a SQL statement with no return values. /// /// The SQL statement to execute. /// Arguments for string formatting, if any. public void ExecuteNonQuery(string sqlStatement, params object[] args) { SqlServerIntegrationTester.ExecuteNonQuery(this.TestDatabaseConnectionString, sqlStatement, args); } /// /// Executes a SQL statement and returns a . /// /// The T-SQL statement to execute. /// Arguments for string formatting, if any. /// /// A over the result set for . /// /// /// /// The client is responsible for closing the returned after /// use. Closing the reader will automatically close the underlying connection as well. /// /// public SqlDataReader ExecuteReader(string sqlStatement, params object[] args) { SqlConnection con = new SqlConnection(this.TestDatabaseConnectionString); SqlCommand cmd = new SqlCommand(string.Format(sqlStatement, args), con); con.Open(); return cmd.ExecuteReader(CommandBehavior.CloseConnection); } /// /// Executes a SQL statement and returns a single return value. /// /// The SQL statement to execute. /// Arguments for string formatting, if any. /// The return value from the query. public object ExecuteScalar(string sqlStatement, params object[] args) { using (SqlConnection conn = new SqlConnection(this.TestDatabaseConnectionString)) { using (SqlCommand cmd = new SqlCommand(string.Format(CultureInfo.CurrentCulture, sqlStatement, args), conn)) { conn.Open(); return cmd.ExecuteScalar(); } } } /// /// Resets all tables after each test. /// [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] public void ResetTables() { foreach (string scriptFile in this.CleanUpScripts) { this.RunSqlScript(scriptFile); } } /// /// Executes a non-query /// /// The connection string used for the query. /// The T-SQL statement to execute. /// Any parameters that will be applied to the statement. private static void ExecuteNonQuery(string connectionString, string sqlStatement, params object[] args) { using (SqlConnection con = new SqlConnection(connectionString)) { using (SqlCommand cmd = new SqlCommand(string.Format(CultureInfo.CurrentCulture, sqlStatement, args), con)) { con.Open(); cmd.ExecuteNonQuery(); } } } /// /// Runs a SQL script file. /// /// The file name of the script file. [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] private void RunSqlScript(string scriptFile) { using (Process p = new Process()) { p.StartInfo.FileName = "sqlcmd.exe"; p.StartInfo.Arguments = string.Format(CultureInfo.CurrentCulture, "-b -S {0} -d {1} -i \"{2}\"", this.Server, this.TestDatabaseName, Path.GetFullPath(scriptFile)); p.StartInfo.UseShellExecute = false; p.StartInfo.CreateNoWindow = true; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.RedirectStandardError = true; p.Start(); string output = p.StandardOutput.ReadToEnd(); string error = p.StandardError.ReadToEnd(); p.WaitForExit(); if (p.ExitCode != 0) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "An error occurred while executing a SQL script file. \nScript output: {0}\nScript error: {1}", output, error)); } } } } }