Skip to content

Commit d1d8ed3

Browse files
authored
Merge pull request #1098 from segovoni/master
Create sample about SQL Server unit testing automation
2 parents 2256e90 + f69e44d commit d1d8ed3

10 files changed

+725
-1
lines changed

samples/containers/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
__[replication](replication/)__
22

3-
Example of SQL Server Replication in containers. This demo uses docker-compose to start two SQL Server containers; one that acts as the publisher and distributor, and the other as the subscriber in a push snapshot configuration.
3+
Example of SQL Server Replication in containers. This demo uses docker-compose to start two SQL Server containers; one that acts as the publisher and distributor, and the other as the subscriber in a push snapshot configuration.
4+
5+
__[unit testing/tsqlt-docker](unit-testing/tsqlt-docker/)__
6+
7+
Example describes how to automate the testing for one or more SQL Server objects using tSQLt, Docker, and GitHub Actions.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# This workflow installs tSQLt and runs the test units
2+
3+
name: tSQLt installer and unit testing runner
4+
5+
# Controls when the workflow will run
6+
on:
7+
# Triggers the workflow on push or pull request events but only for the "master" branch
8+
push:
9+
branches: [ "master" ]
10+
pull_request:
11+
branches: [ "master" ]
12+
13+
# Allows you to run this workflow manually from the Actions tab
14+
workflow_dispatch:
15+
16+
jobs:
17+
windows-auth-tsqlt:
18+
name: Installting tSQLt with SQL Auth
19+
# The type of runner that the job will run on
20+
runs-on: ubuntu-latest
21+
22+
services:
23+
sqlserver:
24+
image: chriseaton/adventureworks:latest
25+
ports:
26+
- 1433:1433
27+
env:
28+
ACCEPT_EULA: Y
29+
SA_PASSWORD: 3uuiCaKxfbForrK
30+
31+
steps:
32+
- uses: actions/checkout@v2
33+
34+
- name: Install tSQLt with SQL auth on tempdb
35+
uses: lowlydba/tsqlt-installer@v1
36+
with:
37+
sql-instance: localhost
38+
database: tempdb
39+
version: latest
40+
user: sa
41+
password: 3uuiCaKxfbForrK
42+
43+
#- name: Get the containers list unformatted
44+
# run: docker ps
45+
46+
#- name: Get the containers list formatted
47+
# run: docker ps --all --filter status=running --no-trunc --format "{{.ID}}"
48+
49+
#- name: Set the container ID
50+
# run: echo '::set-output name=CONTAINER_ID::$(docker ps --all --filter status=running --no-trunc --format "{{.ID}}")'
51+
# id: ContainerID
52+
53+
#- name: Get container ID
54+
# run: echo "The container ID is ${{ steps.ContainerID.outputs.CONTAINER_ID }}"
55+
56+
- name: Set environment variable ENV_CONTAINER_ID
57+
run: echo "ENV_CONTAINER_ID=$(docker ps --all --filter status=running --no-trunc --format "{{.ID}}")" >> $GITHUB_ENV
58+
59+
- name: Test environment variable ENV_CONTAINER_ID
60+
run: echo $ENV_CONTAINER_ID
61+
62+
#- name: Print environment variables
63+
# run: env
64+
65+
- name: Restore AdventureWorks2017
66+
run: docker exec -i $ENV_CONTAINER_ID /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "3uuiCaKxfbForrK" -Q "RESTORE DATABASE [AdventureWorks2017] FROM DISK = '/adventureworks.bak' WITH MOVE 'AdventureWorks2017' TO '/var/opt/mssql/data/AdventureWorks.mdf', MOVE 'AdventureWorks2017_log' TO '/var/opt/mssql/data/AdventureWorks_log.ldf'"
67+
68+
- name: Get the database list
69+
run: docker exec -i $ENV_CONTAINER_ID /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "3uuiCaKxfbForrK" -Q "SELECT Name FROM sys.databases"
70+
71+
- name: Install tSQLt with SQL auth on AdventureWorks2017
72+
uses: lowlydba/tsqlt-installer@v1
73+
with:
74+
sql-instance: localhost
75+
database: AdventureWorks2017
76+
version: latest
77+
user: sa
78+
password: 3uuiCaKxfbForrK
79+
80+
- name: Create sp usp_Raiserror_SafetyStockLevel
81+
run: docker exec -i $ENV_CONTAINER_ID /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "3uuiCaKxfbForrK" -d AdventureWorks2017 -b < ./source/usp-raiserror-safetystocklevel.sql
82+
83+
- name: Create system under test (SUT) TR_Product_SafetyStockLevel
84+
run: docker exec -i $ENV_CONTAINER_ID /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "3uuiCaKxfbForrK" -d AdventureWorks2017 -b < ./source/tr-product-safetystocklevel.sql
85+
86+
- name: Create test class UnitTestTRProductSafetyStockLevel
87+
run: docker exec -i $ENV_CONTAINER_ID /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "3uuiCaKxfbForrK" -d AdventureWorks2017 -b < ./unit-test/test-class-trproductsafetystocklevel.sql
88+
89+
- name: Create and run test case try to insert one wrong row
90+
run: docker exec -i $ENV_CONTAINER_ID /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "3uuiCaKxfbForrK" -d AdventureWorks2017 -b < ./unit-test/test-case-try-to-insert-one-wrong-row.sql
91+
92+
- name: Create and run test case try to insert one right row
93+
run: docker exec -i $ENV_CONTAINER_ID /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "3uuiCaKxfbForrK" -d AdventureWorks2017 -b < ./unit-test/test-case-try-to-insert-one-right-row.sql
94+
95+
- name: Create and run test case try to insert multiple rows
96+
run: docker exec -i $ENV_CONTAINER_ID /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "3uuiCaKxfbForrK" -d AdventureWorks2017 -b < ./unit-test/test-case-try-to-insert-multiple-rows.sql
97+
98+
- name: Create and run test case try to insert multiple rows ordered
99+
run: docker exec -i $ENV_CONTAINER_ID /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "3uuiCaKxfbForrK" -d AdventureWorks2017 -b < ./unit-test/test-case-try-to-insert-multiple-rows-ordered.sql
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
<!-- Always leave the MS logo -->
2+
![](https://github.com/microsoft/sql-server-samples/blob/master/media/solutions-microsoft-logo-small.png)
3+
4+
# SQL Server unit testing with tSQLt, Docker, and GitHub Actions!
5+
6+
This sample describes how to automate the testing for one or more SQL Server objects using tSQLt, Docker, and GitHub Actions!
7+
8+
### Contents
9+
10+
[About this sample](#about-this-sample)<br/>
11+
[Before you begin](#before-you-begin)<br/>
12+
[Case history](#case-history)<br/>
13+
[Run this sample](#run-this-sample)<br/>
14+
[Sample details](#sample-details)<br/>
15+
[Disclaimers](#disclaimers)<br/>
16+
[Related links](#related-links)<br/>
17+
18+
<a name=about-this-sample></a>
19+
20+
## About this sample
21+
22+
- **Applies to:** SQL Server 2016 (or higher)
23+
- **Key features:** Run SQL Server in Docker containers
24+
- **Workload:** Unit tests executed on [AdventureWorks](https://github.com/Microsoft/sql-server-samples/releases/tag/adventureworks)
25+
- **Programming Language:** T-SQL, YAML
26+
- **Authors:** [Sergio Govoni](https://www.linkedin.com/in/sgovoni/) | [Microsoft MVP Profile](https://mvp.microsoft.com/it-it/PublicProfile/4029181?fullName=Sergio%20Govoni) | [Blog](https://segovoni.medium.com/) | [GitHub](https://github.com/segovoni) | [Twitter](https://twitter.com/segovoni)
27+
28+
<a name=before-you-begin></a>
29+
30+
## Before you begin
31+
32+
To run this example, the following basic concepts are required.
33+
34+
[tSQLt](https://tsqlt.org/) is a unit testing framework for SQL Server. It provides the APIs to create and execute test cases, as well as integrates them with continuous integration servers. The power of the tSQLt framework has been described in my article [The tSQLt framework and the execution of a test](https://segovoni.medium.com/unit-testing-the-tsqlt-framework-and-the-execution-of-a-test-e4d135c3e343).
35+
36+
[Docker](https://www.docker.com/) is one of the most popular systems for running applications in isolable, minimal and easily deployable environments called containers. Since SQL Server 2017, the SQL Server Engine can run in a Docker container, a typical usage of running SQL Server in a Docker container concerns the automation of software tests.
37+
38+
[GitHub Actions](https://github.com/features/actions/) is a continuous integration and continuous delivery (CI/CD) platform that allows you to automate your build, test, and deployment pipeline. You can create workflows that build and test every pull request to your repository, or deploy merged pull requests to production. GitHub provides Linux, Windows, and macOS virtual machines to run your workflows, or you can host your own self-hosted runners in your own data center or cloud infrastructure such as Microsoft Azure.
39+
40+
**Software prerequisites:**
41+
42+
1. A GitHub account. If you don't already have a GitHub account, you can get one for free [here](https://github.com/signup)!
43+
44+
<a name=case-history></a>
45+
46+
## Case history
47+
48+
The AdventureWorks database contains the `Production.Product` table that stores products managed and sold by the fake company Adventure Works LTD.
49+
50+
The trigger we have wrote is to prevent the insertion of new products with values less than 10 as a “safety stock”. The Company wishes to always have a warehouse stock of no less than 10 units for each product. The safety stock level is a very important value for the automatic procedures: it allows to re-order materials. The creation of new purchase orders and production orders are based on the safety stock level. To make our trigger simple, it will only respond to the `OnInsert` event, for `INSERT` commands.
51+
52+
<a name=run-this-sample></a>
53+
54+
## Run this sample
55+
56+
<!-- Step by step instructions -->
57+
58+
1. [Sign in](https://github.com/login) to GitHub. If you don't already have an account, [sign up for a new GitHub account](https://docs.github.com/get-started/signing-up-for-github/signing-up-for-a-new-github-account)
59+
2. Create your sample repository on GitHub, if you've never done it, you can find the guide [here](https://docs.github.com/get-started/quickstart/create-a-repo)
60+
3. Create a `.github/workflows` directory in your GitHub repository if this directory does not already exist
61+
4. Copy the [automated-tests.yml](https://github.com/microsoft/sql-server-samples/tree/master/samples/containers/unit-testing/tsqlt-docker/.github/workflows) inside the directory `.github/workflows` you created in the previous step in your repository. The `automated-tests.yml` describes the process that will execute one or more jobs
62+
5. Create the `source` and `unit-test` directories in the root of your sample repository
63+
6. Copy all the files located in the [source](https://github.com/microsoft/sql-server-samples/tree/master/samples/containers/unit-testing/tsqlt-docker/source) and [unit-test](https://github.com/microsoft/sql-server-samples/tree/master/samples/containers/unit-testing/tsqlt-docker/unit-test) directories to their respective directories in your repository
64+
7. View and run the workflow as described [here](https://docs.github.com/actions/quickstart)
65+
8. Have fun with the solution details outlined below
66+
67+
**The challenge**
68+
69+
The implementation of the trigger and related unit tests has been done, all files are ready in your repository!
70+
71+
The challenge is to automate the execution of the tests at each commit on the main branch of the repository. GitHub Actions is our CI/CD platform, it supports the use of Docker containers and it is intimately integrated into GitHub, the source control that manages our source code.
72+
73+
**Understand and manage your first workflow**
74+
75+
Workflows are defined with a YAML file stored in the same repository which holds the source code. The workflows will be triggered when an event occurs in the repository. Anyway, a workflow can also be activated manually or according to a defined schedule.
76+
77+
A sample YAML file that implements the test automation workflow is already in your sample repository, the fundamental steps of the process are:
78+
79+
1. Definition of activation events
80+
2. Creating a Docker container from a SQL Server image on Linux
81+
3. AdventureWorks database recovery
82+
4. Installation of the tSQLt framework
83+
5. Creating the database objects to be tested (SUT)
84+
6. Creation and execution unit tests
85+
86+
**1. Definition of activation events**
87+
88+
The definition of the activation events is typically done at the beginning of the YAML script with a code snippet similar to the one shown below. The workflow is activated when push or pull request events occur on the `master` branch. The `workflow_dispatch` specification allows you to run the workflow manually from the actions tab.
89+
90+
```
91+
# Controls when the workflow will run
92+
on:
93+
# Triggers the workflow on push or pull request events but only for the "master" branch
94+
push:
95+
branches: [ "master" ]
96+
pull_request:
97+
branches: [ "master" ]
98+
99+
# Allows you to run this workflow manually from the Actions tab
100+
workflow_dispatch:
101+
```
102+
103+
**2. Creating a Docker container from a SQL Server image on Linux**
104+
105+
Creating a Docker container from a SQL Server image on Linux can be done by requesting the `sqlserver service` accompanied by the path to the Docker image you want to use.
106+
107+
The official images provided by Microsoft for SQL Server on Linux are available [here](https://hub.docker.com/_/microsoft-mssql-server).
108+
109+
We will not use an official image downloaded from the Microsoft registry. We will use a Docker image of SQL Server with the AdventureWorks database installed, this image is published by [chriseaton](https://hub.docker.com/u/chriseaton), you can find it at this [link](https://hub.docker.com/r/chriseaton/adventureworks).
110+
111+
The following YAML code snippet sets up the SQL Server service.
112+
113+
```
114+
jobs:
115+
windows-auth-tsqlt:
116+
name: Installting tSQLt with SQL Auth
117+
# The type of runner that the job will run on
118+
runs-on: ubuntu-latest
119+
120+
services:
121+
sqlserver:
122+
image: chriseaton/adventureworks:latest
123+
ports:
124+
- 1433:1433
125+
env:
126+
ACCEPT_EULA: Y
127+
SA_PASSWORD: 3uuiCaKxfbForrK
128+
```
129+
130+
In order to reference the newly created Docker container it is important to save its identifier in an environment variable.
131+
132+
The following snippet of YAML code sets the `ENV_CONTAINER_ID` variable with the ID of the container created.
133+
134+
```
135+
- name: Set environment variable ENV_CONTAINER_ID
136+
run: echo "ENV_CONTAINER_ID=$(docker ps --all --filter status=running --no-trunc --format "{{.ID}}")" >> $GITHUB_ENV
137+
```
138+
139+
**3. AdventureWorks database recovery**
140+
141+
The AdventureWorks database recovery can be performed using the following docker exec command.
142+
143+
```
144+
- name: Restore AdventureWorks
145+
run: docker exec -i $ENV_CONTAINER_ID /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "3uuiCaKxfbForrK" -Q "RESTORE DATABASE [AdventureWorks2017] FROM DISK = '/adventureworks.bak' WITH MOVE 'AdventureWorks2017' TO '/var/opt/mssql/data/AdventureWorks.mdf', MOVE 'AdventureWorks2017_log' TO '/var/opt/mssql/data/AdventureWorks_log.ldf'"
146+
```
147+
148+
**4. Installation of the tSQLt framework**
149+
150+
The installation of the latest version of tSQLt framework in the AdventureWorks database is done using the GitHub Actions tSQLt Installer published by [lowlydba](https://github.com/lowlydba), you can find more details [here](https://github.com/lowlydba/tsqlt-installer) and on the [GitHub Actions marketplace](https://github.com/marketplace/actions/tsqlt-installer).
151+
152+
The snippet of YAML code used for the installation of the tSQLt framework in the AdventureWorks database is the following.
153+
154+
```
155+
steps:
156+
- uses: actions/checkout@v2
157+
- name: Install tSQLt with SQL auth on AdventureWorks2017
158+
uses: lowlydba/tsqlt-installer@v1
159+
with:
160+
sql-instance: localhost
161+
database: AdventureWorks2017
162+
version: latest
163+
user: sa
164+
password: 3uuiCaKxfbForrK
165+
```
166+
167+
**5. Creating the database objects to be tested (SUT)**
168+
169+
The test environment is ready, we have a SQL Server instance on Linux inside a Docker container; the AdventureWorks database has been restored and it is ready for use.
170+
171+
Let's go ahead with the creation of the trigger and the stored procedure (that manages errors), they represent our [System Under Test (SUT)](https://en.wikipedia.org/wiki/System_under_test).
172+
173+
The TR_Product_SafetyStockLevel trigger creation script and the usp_Raiserror_SafetyStockLevel stored procedure creation script are saved in the source directory of this sample.
174+
175+
Triggers and stored procedures are created in the AdventureWorks database attached to the SQL Server instance, the YAML code snippet that performs this operation is the following.
176+
177+
```
178+
- name: Create sp usp_Raiserror_SafetyStockLevel
179+
run: docker exec -i $ENV_CONTAINER_ID /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "3uuiCaKxfbForrK" -d AdventureWorks2017 -b < ./source/usp-raiserror-safetystocklevel.sql
180+
- name: Create TR_Product_SafetyStockLevel
181+
run: docker exec -i $ENV_CONTAINER_ID /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "3uuiCaKxfbForrK" -d AdventureWorks2017 -b < ./source/tr_product_safetystocklevel.sql
182+
```
183+
184+
**6. Creation and execution of test units**
185+
186+
The last phase of this workflow is represented by the creation and execution of the unit tests.
187+
188+
The test class and the unit tests creation scripts are contained in the unit-test subfolder of this sample.
189+
190+
Let's go ahead with the creation of the test class dedicated to the TR_Product_SafetyStockLevel trigger, we called it UnitTestTRProductSafetyStockLevel.
191+
192+
The following docker exec command, that uses sqlcmd, executes the TSQL commands contained in the test-class-trproductsafetystocklevel.sql script.
193+
194+
```
195+
- name: Create test class UnitTestTRProductSafetyStockLevel
196+
run: docker exec -i $ENV_CONTAINER_ID /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "3uuiCaKxfbForrK" -d AdventureWorks2017 -b < ./unit-test/test-class-trproductsafetystocklevel.sql
197+
```
198+
199+
Let's go ahead with the creation and execution of the unit tests. Each .sql file of the test case family contains the TSQL commands for creating and running the related unit test. Each store procedure tests one and only one test case.
200+
201+
The following snippet of YAML code creates and runs the test units.
202+
203+
```
204+
- name: Create and run test case try to insert one wrong row
205+
run: docker exec -i $ENV_CONTAINER_ID /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "3uuiCaKxfbForrK" -d AdventureWorks2017 -b < ./unit-test/test-case-try-to-insert-one-wrong-row.sql
206+
- name: Create and run test case try to insert one right row
207+
run: docker exec -i $ENV_CONTAINER_ID /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "3uuiCaKxfbForrK" -d AdventureWorks2017 -b < ./unit-test/test-case-try-to-insert-one-right-row.sql
208+
- name: Create and run test case try to insert multiple rows
209+
run: docker exec -i $ENV_CONTAINER_ID /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "3uuiCaKxfbForrK" -d AdventureWorks2017 -b < ./unit-test/test-case-try-to-insert-multiple-rows.sql
210+
- name: Create and run test case try to insert multiple rows ordered
211+
run: docker exec -i $ENV_CONTAINER_ID /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "3uuiCaKxfbForrK" -d AdventureWorks2017 -b < ./unit-test/test-case-try-to-insert-multiple-rows-ordered.sql
212+
```
213+
214+
The YAML script for our workflow is complete, you can find it here.
215+
216+
<a name=sample-details></a>
217+
218+
## Sample details
219+
220+
Unit tests developed for a SQL Server solution are not just meant to verify that requirements have been met once, prior to release; the real game changer is represented by the possibility of repeating the checks during the development of new code and during the bug fixing process.
221+
222+
The repeatability of the tests provides the ability to automate them, an essential condition for integrating automatic tests within a Continuous Integration platform. In this article we described how to automate the testing of SQL Server objects using tSQLt, Docker and GitHub Actions!
223+
224+
<a name=disclaimers></a>
225+
226+
## Disclaimers
227+
228+
The code included in this sample is not intended to be a set of best practices on how to build scalable enterprise grade unit testing or CI/CD system. This is beyond the scope of this quick start sample.
229+
230+
<a name=related-links></a>
231+
232+
## Related Links
233+
<!-- Links to more articles. Remember to delete "en-us" from the link path. -->
234+
235+
For more information, see these articles:
236+
237+
- [Unit testing: What it is and why it is important for T-SQL code!](https://medium.com/@segovoni/unit-testing-what-it-is-and-why-it-is-important-for-t-sql-code-7e9df7ca8bfe)
238+
- [Unit testing: The tSQLt framework and the execution of a test!](https://segovoni.medium.com/unit-testing-the-tsqlt-framework-and-the-execution-of-a-test-e4d135c3e343)
239+
- [Unit testing: How to write your first unit test for T-SQL code](https://segovoni.medium.com/unit-testing-how-to-write-your-first-unit-test-for-t-sql-code-3bc1533acbbc)

0 commit comments

Comments
 (0)