The main idea of unit (or modular, as it is also called) testing is testing of individual program components, i.e. classes and their methods. Developing the code covered by the tests is very useful, because when used correctly, the possibility of regression in the history of the program’s development is practically eliminated - “something new was added, half of the old one fell ill”. Also, the “TDD” development methodology - Test Driven Development. According to her, the programmer first develops a set of tests for future functionality, calculates all the options for execution, and only then begins to write directly the working code that fits the already written tests.
Since the existence of tests in the program is not only a confirmation of the qualifications of the developer, but also often a requirement of the customer, I decided to address this issue and “touch” the tests close.
I work mostly in Visual Studio, I write on Sharpe, which means the choice was almost limited to two products - Nunit and Unit Testing Framework.
Unit Testing Framework is built in Visual Studio, a testing system developed by Microsoft, constantly evolving (among the latest updates is the ability to test the UI, which was already mentioned in Habré), and importantly, it will almost certainly exist all the time while there is a Visual Studio, which cannot be said about stronyh developments. Excellent integration into the IDE and the function of calculating the percentage of code coverage in the program finally tipped the scales - the choice was made.
The network has a rather large number of various tutorials on testing, but all of them usually boil down to testing a hand-written calculator or comparing strings. These things, of course, are also necessary and important, but they pull bad examples for serious examples, if frankly, they don’t pull at all. I myself can test such tasks even in my mind.
')
Here is a list of more serious tasks.
• validation of the database creation
• validation of business logic
• getting the benefits of all this (in my case, the benefits were obtained))
So let's get started!DB Testing
According to the principles of testing and sound logic, we will have a separate database for testing. Therefore, we create the TestDB database, add the Users table to it.
CREATE TABLE [dbo].[Users](
[id] [ int ] IDENTITY (1,1) NOT NULL ,
[login] [ nchar ](100) COLLATE Ukrainian_CI_AS NOT NULL ,
[hash] [ nchar ](50) COLLATE Ukrainian_CI_AS NOT NULL ,
[ level ] [ int ] NULL ,
CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED
([id] ASC )
WITH (PAD_INDEX = OFF , STATISTICS_NORECOMPUTE = OFF , IGNORE_DUP_KEY = OFF , ALLOW_ROW_LOCKS = ON , ALLOW_PAGE_LOCKS = ON ))
CREATE UNIQUE NONCLUSTERED INDEX [IX_UserName] ON [dbo].[Users]
([Login] ASC )
WITH (PAD_INDEX = OFF , STATISTICS_NORECOMPUTE = OFF , SORT_IN_TEMPDB = OFF , IGNORE_DUP_KEY = OFF , DROP_EXISTING = OFF , ONLINE = OFF , ALLOW_ROW_LOCKS = ON , ALLOW_PAGE_LOCKS = ON )
and we enter data into it
INSERT [dbo].[Users] ([Login], [Hash], [ Level ]) VALUES (N 'Sirix' , N '827ccb0eea8a706c4c34a16891f84e7b' , 1)
INSERT [dbo].[Users] ([Login], [Hash], [ Level ]) VALUES (N 'Tester' , N '3095C3E4F1465133E5E6BE134EB2EBE2' , 1)
INSERT [dbo].[Users] ([Login], [Hash], [ Level ]) VALUES (N 'Admin' , N 'E3AFED0047B08059D0FADA10F400C1E5' , 2)
INSERT [dbo].[Users] ([Login], [Hash], [ Level ]) VALUES (N 'U1' , N '827CCB0EEA8A706C4C34A16891F84E7B' , 1)
Add another stored procedure, which we will test. It will be engaged in a rather usual thing - according to the given login and password hash, to return the user ID.
CREATE PROCEDURE [dbo].[GetUserId]
(
@login nchar (100),
@hash nchar (50),
@id int OUTPUT
)
AS
SELECT @id = ID FROM Users WHERE Login = @login AND hash = @hash
RETURN
* This source code was highlighted with Source Code Highlighter .
Unit Testing Framework allows you to test various aspects of database operation - checking the schema, the number of records in tables, stored procedures, query execution time, their results, and much more.
Getting tests
Create a new solution of type TestProject. Let's call it LinkCatalog.Tests. And add to it a new test Database Unit Test

The window for setting the connection with the database appears. We configure connection with our DB and we press OK. In the same window, you can specify the parameters of autogeneration data for tests. This function uses the Data Generation Plan and allows you to fill the database table with test values using patterns and even regular expressions.
Click OK and get to the database testing window.
Test number 1
The first test is the easiest. Check the number of entries in the table:
Select count (*) FROM Users
Now we set the condition for the test correctness. As I said, there is a large list of all sorts of criteria, but we are interested in one of the simplest - ScalarValue.

All condition options are configured in the Properties window.

Everything! At this first test is completed. Run and watch

What was required to prove - the lines are successfully stored in the database.
Test number 2
Now it is time to do a more realistic test than the number of records. This is a stored procedure. What if the developers of the database made a critical error in it? But this is the most important part of the user authentication subsystem!
Create a new test database by clicking on the green plus sign


Here is the test code:
/* */
DECLARE @id INT ;
SET @id = -1
/* id = 1 */
EXEC GetUserId N 'Sirix' , N '827ccb0eea8a706c4c34a16891f84e7b' , @id OUTPUT ;
SELECT * FROM Users WHERE id = @id
* This source code was highlighted with Source Code Highlighter .
Another condition is already used here - it is expected that there will be exactly 1 row in the sample.

This test also completes successfully, which is good news.

According to the results of testing the database, we can say that it works stably and expectedly. Now, the coverage of the database procedures reaches 100% - the limit, which is not easy to achieve in a more complex application or database with a large number of tables / procedures / links.
Code unit tests
Now let's start testing the code.
I had a small ASP.NET application with an MVC pattern implementation. The model was a separate assembly under the proud name of DAL and included a wrapper access to the database. One of the requirements was the use of DataReader in ADO.NET. Settings, as is customary, are stored in web.config.
I decided to start testing the model: this is the code that was to be tested.
namespace LinkCatalog.DAL
{
public class UserModel
{
...........
public static int GetUserIdByName(string username)
{
string query = " SELECT ID FROM Users WHERE Login = @login;";
DB. get ().CommandParameters. Add ( new SqlParameter("@login", username));
int id = -1;
int .TryParse(DB. get ().GetOneCell(query).ToString(), out id);
return id;
}
}
public class DB
{
...........
private static DB instance;
public static DB get ()
{
if (instance == null )
instance = new DB();
return instance;
}
private SqlConnection connection ;
private SqlDataReader reader;
public List CommandParameters;
private DB()
{
this. connection = new SqlConnection(WebConfigurationManager.ConnectionStrings["DBConnectionString"].ConnectionString);
this.CommandParameters = new List();
}
public object GetOneCell(string query)
{
SqlCommand sc = new SqlCommand(query, this. connection );
if (this.CommandParameters. Count != 0)
sc. Parameters .AddRange(this.CommandParameters.ToArray());
this. connection . Open ();
object res = sc.ExecuteScalar();
this.CommandParameters.Clear();
this. Close ();
return res;
}
}
}
* This source code was highlighted with Source Code Highlighter .
Add a new unit test to the project

and get a file of this content:
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace LinkCatalog.Tests
{
[TestClass]
public class UserModel_Tests
{
[TestMethod]
public void TestMethod1()
{
}
}
}
* This source code was highlighted with Source Code Highlighter .
The attribute [TestClass] means that this class contains test methods, and [TestMethod] means that such a method is a specific method.
Add a link to the DAL assembly to the project and import the LinkCatalog.DAL namespace. The preparatory work is completed, it is time to write tests.
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace LinkCatalog.Tests
{
using LinkCatalog.DAL;
[TestClass]
public class UserModel_Tests
{
[TestMethod]
public void GetUserById_Test()
{
Assert.AreNotEqual(UserModel.GetUserIdByName(" Admin "), -1);
}
}
}
* This source code was highlighted with Source Code Highlighter .
We always have an administrator with such a login and therefore its identifier cannot be -1.
Run the test - and the error:

An exception popped up:
Test method LinkCatalog.Tests.UserModel_Tests.GetUserById_Test threw exception:
System.NullReferenceException: Object reference not set to an instance of an object .
LinkCatalog.DAL.DB..ctor() in C:\Users\\Documents\Visual Studio 2010\Projects\Practice\DAL\Database.cs: line 35
LinkCatalog.DAL.DB. get () in C:\Users\\Documents\Visual Studio 2010\Projects\Practice\DAL\Database.cs: line 17
LinkCatalog.DAL.UserModel.GetUserIdByName( String username) in C:\Users\\Documents\Visual Studio 2010\Projects\Practice\DAL\Models\UserModel.cs: line 63
LinkCatalog.Tests.UserModel_Tests.GetUserById_Test() in C:\Users\\Documents\Visual Studio 2010\Projects\Practice\LinkCatalog.Tests\UserModel_Tests.cs: line 12
* This source code was highlighted with Source Code Highlighter .
As you can see, the error lies in the DB class constructor - it cannot find the configuration file and, as a result, the test fails not only unsuccessfully, but also with an error.
The solution to the configuration file problem is quite simple:
No, the test environment will not automatically connect web.config. Instead, each test project creates its own app.config configuration file, and all that is required is to add the necessary settings to it.
<? xml version ="1.0" encoding ="utf-8" ? >
< configuration >
< configSections >
< section name ="DatabaseUnitTesting" type ="Microsoft.Data.Schema.UnitTesting.Configuration.DatabaseUnitTestingSection, Microsoft.Data.Schema.UnitTesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</ configSections >
< DatabaseUnitTesting >
< DataGeneration ClearDatabase ="true" />
< ExecutionContext Provider ="System.Data.SqlClient" ConnectionString ="Data Source=SIRIXPC\sqlexpress;Initial Catalog=TestDB;Integrated Security=True;Pooling=False"
CommandTimeout ="30" />
< PrivilegedContext Provider ="System.Data.SqlClient" ConnectionString ="Data Source=SIRIXPC\sqlexpress;Initial Catalog=TestDB;Integrated Security=True;Pooling=False"
CommandTimeout ="30" />
</ DatabaseUnitTesting >
< connectionStrings >
< add name ="DBConnectionString" connectionString ="Data Source=SIRIXPC\SQLEXPRESS;Initial Catalog=tbd;Integrated Security=True"
providerName ="System.Data.SqlClient" />
</ connectionStrings >
</ configuration >
* This source code was highlighted with Source Code Highlighter .
Now the test is successful:

Slightly change the test
[TestMethod]
public void GetUserById_Test()
{
Assert.AreNotEqual(UserModel.GetUserIdByName( "Admin" ), -1);
Assert.AreEqual(UserModel.GetUserIdByName( "0-934723 ### 12sdf s" ), -1);
}
* This source code was highlighted with Source Code Highlighter .
The database is guaranteed there is no such login (the login must be without spaces, this is followed by the validators in the controller upon registration), and this is the second and last verification method.

The test is not passed again - we look at the details:
Test method LinkCatalog.Tests.UserModel_Tests.GetUserById_Test threw exception:
System.NullReferenceException: Object reference not set to an instance of an object .
UserModel.GetUserIdByName, null :
int .TryParse(DB. get ().GetOneCell(query).ToString(), out id);
:
var res = DB. get ().GetOneCell(query);
if (res != null )
int .TryParse(res.ToString(), out id);
* This source code was highlighted with Source Code Highlighter .
Now everything is OK!

This is how testing helps to identify some errors in the program, let them arise from forgetfulness / laziness / not knowing, but it is easier to fix them than on a live server. This topic, of course, does not cover all aspects of testing programs and only opened this area. The possibility of conditional tests that depend on other tests, user interface tests and other things remained behind the scenes.
Test your programs and let you have a few bugs, and many, many features!