SQL Unit Testing – dynamic connection configuration

I have recently joined the SQL unit testing bandwagon and love it, especially the pretty instant feedback when a code change impacts some other area in an unexpected way. Until now I have liked to believe I developed quality code, however in the face of a plethora of tests, I have to accept that was not the case, and this is the other unexpected aspect of TDD; increased confidence and productivity from the simple fact code passes lots of tests.

For testing we use a blend of tSQLt and SQL Server Database Unit Testing (TSQL Unit Testing) in SSDT, with Jenkins the CI engine and powershell scripts as the glue. Jenkins is currently configured with two slaves, and in one sense this was the cause of one of the first problems encountered when promoting code through environments. However, really the problem is the configuration options limitation of TSQL Unit Testing. Dave Ballantyne (B|T) has blogged about this, as has Jamie Thomson (B|T) also referenced in that post, and probably others.

The way I see things in a CI / CD environment properties should be dynamic, especially those relating to connections. Limiting oneself to the app.conf model, there is the standard mode of only one app.config for all environments. Alternatively the overridden mode potentially allows a whopping two additional overriding config files to be specified, and these are selected in the following order:

  1. hostname.sqlunittest.config
    if a config file is found using the above format of build server hostname suffixed by “.sqlunittest.config” then it will be selected
  2. buildusername.sqlunittest.config
    if a config file is found using the above format of build user name suffixed by “.sqlunittest.config” then it will be selected, and onlyif a hostname one is not found.
  3. app.config
    fallback to the standard configuration file if no override file is found. As Dave Ballantyne points out and I repeat, the override filename format is very specific.

No matter how one configures this the bottom line expectation as I see it, is one build sever per environment. I have yet to see any such configuration, and this is certainly not the case in our Jenkins set up where with the exception of Live jobs, jobs relating to lower environments are not tied to a slave hosts.

Did I forget to mentioned these config files are hard coded!?

So what to do

I am not shy about writing C#, however it does not really help as one still has to load some configuration from somewhere – another app.config perhaps?

Fortunately I happened upon Dave Ballantyne’s post, and latched on to the idea of transform, in particular implementing a deploy-time xslt transform to inject environment specific values. Xslt is admittedly a bit old fashioned and a sledge hammer of a solution, indeed clunky to code. Turning once again to my colleague Jagjit Thind (LI) we agreed the following steps as a workable solution:

  1. In SSDT enable app config overide
  2. In SSDT include a simple overriding config template with string substitution tokens
  3. Post build and prior to executing TSQL Unit Testing, run a poweershell script to load the template, inject deploy-time values and save using the correct filename format
  4. execute TSQL Unit Testing

Enable app config overide

The key feature of the app.config specified in the TSQL Unit Testing project is that configuration override is enabled. The file does not have to be copied to output as it is consumed at compile time and regenerated as assemblyname.dll.config.

app.config.override

Implement overriding config template

As shown in the image below the template definition is very simple, with the connection string as %connection_string%. The template name is not relevant, however its sensible to choose a name that is both transparent and meaningful.

sqlunittest.config.template

Other key points to note are:

  1. In SSDT the Copy to Output Directory must be Copy Always
  2. The configuration xml document element, in this case SqlUnitTesting_VS2013, must correlate to overridden section in the app.config. For example, the element name count be SqlUnitTesting.

Powershell script(s)

We push environment configuration into aptly named settings files, one per environment and these are loaded dynamically using the $environment parameter. An example DEV.settings.ps1 is provided. I think this is a neat way to manage environment configuration and again thanks to Jagit for this implementation.

1
2
3
4
5
6
# DEV.settings.ps1
 
$global:environmentSettings = @{
    targetServer="localhost\sql2012"
    targetDatabase="master"
}

The following are excerpts from the powershell scripts I’ve put together for this post, it is kind of how we do things but not quite. For example, post project build the desired contents are moved from the build output location to another common directory. This is not only useful to avoid ambiguity with the auto generated obj directory, it also makes clear what build artifacts are of interest.

The first excerpt relates to configuring an environment specific configuration file, the second relates to running tests using VSTestConsole. Complete source is available for download via github link at end of this post.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# relating to TSQL Unit Testing config file building
$global:testSettings += @{
    testingAppConfigTemplate = "sqlunittest.config.template"
    testingAppConfigFile = "{0}.sqlunittest.config"
    testingConnectionString="Data Source={0};Initial Catalog={1};Integrated Security=True;Pooling=False"
}
 
function ConfigureTestingAppConfig
{
[CmdletBinding()]
Param
(
    [Parameter(Mandatory=1, ValueFromPipeline=$true)][String]$templateFile
)
    # sql unit tests are not per se deployed.
    # instead the testing project is built and the tests executed from that location
    # this script builds an environment specific app.config from a template.
    # the custom script has naming format .sqlunittest.config
    $template = [System.IO.File]::ReadAllText($templateFile);
    $template = $template.Replace("%connection_string%", ($global:testSettings.testingConnectionString -f $global:environmentSettings.targetServer, $global:environmentSettings.targetDatabase));
    $configFile = Join-Path (Split-Path $templateFile) ($global:testSettings.testingAppConfigFile -f ($env:computername));
 
    Write-Host ("Creating test config file: {0}" -f $configFile) -foregroundcolor Cyan;
 
    [System.IO.File]::WriteAllText($configFile, $template);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# relating to dependencies
$global:testSettings += @{
    vsTestConsole = "${Env:ProgramFiles(x86)}\Microsoft Visual Studio 12.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console"
}
 
function RunTestingDll
{
[CmdletBinding()]
Param
(
    [Parameter(Mandatory=1, ValueFromPipeline=$true)][String]$testingDll
)
    &$global:testSettings.vsTestConsole $testingDll /UseVsixExtensions:false /Logger:trx;
 
}

And some output

This first output is the environment specific configuration generated.

1
2
3
4
5
6
7
8
9
<SqlUnitTesting_VS2013>
   <DataGeneration ClearDatabase="true" />
   <ExecutionContext Provider="System.Data.SqlClient"
                     ConnectionString="Data Source=localhost\sql2012;Initial Catalog=master;Integrated Security=True;Pooling=False"
                     CommandTimeout="30" />
   <PrivilegedContext Provider="System.Data.SqlClient" 
                      ConnectionString="Data Source=localhost\sql2012;Initial Catalog=master;Integrated Security=True;Pooling=False" 
                      CommandTimeout="30" />
</SqlUnitTesting_VS2013>

Following is the output from executing the test. Please ignore the fact the test fails (regardless of whether one deploys the SSDT project or not), the important point is the generated template is selected as the overriding configuration.

test.run.results

In Summary

While implementing a template with string substitution tokens is not the most elegant solution, it does get around the static nature of app config files using the tools to hand, and powershell scripts are in my opinion more flexible and transparent than embedding the solution as C# in the SQL Unit Testing framework.

All the code presented is available here in Github.

Enjoy.