|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Index
1. IntroductionIf you have developed or maintained business applications, you will probably have found the following typical scenario several times:
When many people are involved in a system, with specific responsibilities (analyst, developers, DBA administrators, etc.) and sometimes, with contradictory requirements, you will find that databases are not frozen. Normally, databases evolve (new tables, relations, changes in name's and data type’s fields) throughout the development process much more than we could have initially expected. Yes! Databases are alive! On one hand, that’s a good thing: software is built to be changed. On the other hand, however, the problem arrives on the scene when we try to maintain our application robust enough surrounded by a rapid changing environment and functionality. This common scenario is error-prone and is derived from the de-synchronization of applications and underlying databases. The tool and method provided in this article shows a way to automatically check the syntactical correctness of your SQL code in your Data Access Layer every time you need it. In this way, you will have an automated way of detecting DB–Application inconsistencies inside your SQL code before releasing a new version of your software. It is useful to have a second chance to detect DAL bugs before compromising the robustness of your software. Now, you have just realized where the name ‘Armadillo’ comes from: consider this technique as a defensive programming weapon for your daily programming toolbox. 2. BackgroundBefore going deeply in the topic, I will assume you have the following basic knowledge:
The examples provided assumes a MS SQL Server database. However, the technique does not depend on the RDBMS. It can be applied to Oracle or MySQL with minor changes (in fact, the Armadillo framework is prepared for such extensibility: only an interface must be implemented to support a new DB technology). The example assumes the following DB schema (scripts provided for MS SQL Server):
You know, the classical, minimalist and educational DB for gathering Customers, Invoices, and Invoice Lines. 3. Target: Typical DAL codeBefore starting with testing, first let's have a look at a typical DAL, for example, the Customer code in the DAL assembly. This code is designed to encapsulate the Data Access Layer and contains all the SQL of the application, it manages connections and database APIs. using System;
using System.Data;
using OKOKO.Armadillo.Attributes;
using System.Data.SqlClient;
namespace OKOKO.MyBiz.Dal
{
/// <summary>
/// Customer DAL class. Implements a classical CRUD example
/// </summary>
public class Customer
{
public static Customer Create(string id, string name,
string surname, string phone)
{
//INSERT
SqlCommand cmd = new SqlCommand(
"INSERT INTO [Customer] ([id], [name], [surname], [phone])" +
"VALUES(@id, @name,@surname,@phone)" , Dal.Cnx);
cmd.Parameters.Add("@id", id);
cmd.Parameters.Add("@name", name);
cmd.Parameters.Add("@surname", surname);
cmd.Parameters.Add("@phone", phone);
cmd.ExecuteNonQuery();
//TODO: Build result...
return new Customer();
}
The previous code shows a typical DAL code for the class I have intentionally omitted part of the code (TODOs): the one that builds the response for the business layer to maintain us focused on the SQL, our main objective. public static Customer Read(string id)
{
//SELECT ID
SqlCommand cmd = new SqlCommand(
"Select * from customer where id=@id", //<-Sql code to be tested.
Dal.Cnx);
cmd.Parameters.Add("@id", id);
SqlDataReader dr = cmd.ExecuteReader();
//... read data and returns a Customers
dr.Close();
//TODO: Build result...
return new Customer();
}
The second example ( The following methods: Finally, in some cases, the DAL code can contain code similar to this: public static string GetSQLCustomersOrdered()
{
return "select name, surname from" +
" customer order by surname, name";
}
public static string GetSQLCustomersWithPhone()
{
return "select name, surname from Xcustomer" +
" where phone is not null" +
" order by surname, name";
}
public static string GetSqlDeleteAll()
{
return "delete customer";
}
These methods do not execute any SQL code. Instead, they return SQL statements in If you want to have a deeper look, you have more examples of the DAL code for Now, we will describe the steps for running the demo, and afterwards the implementation details will be provided. 4. Using the code & demoUnzip the attached Zipped file. You will find the following material:
Follow these steps:
NUnit will execute the engine that tests all the attribute-labeled DAL code. NUnit shows success tests in green, and fails in red plus debug information (see the Console.Out tab for details). If everything goes as expected, all tests will pass except two of them. The test log follows:
Testing Class: [OKOKO.MyBiz.Dal.Customer]
Testing SqlExecute Method: +OK Create()
Testing SqlQuery Method: +OK Read()
Testing SqlExecute Method: +OK Update()
Testing SqlExecute Method: +OK Delete()
Testing SqlQuery Method: +OK GetAll()
Testing SqlQuery Method: -FAIL GetBySurname()
>> Invalid column name 'surnameBUG'.
Testing SqlTextQuery Method: +OK GetSQLCustomersOrdered()
Testing SqlTextQuery Method: -FAIL GetSQLCustomersWithPhone()
>> Invalid object name 'Xcustomer'.
Testing SqlTextExecute Method: +OK GetSqlDeleteAll()
Class [OKOKO.MyBiz.Dal.Customer] tested. Results: OK: 7/9 Failed:2
Testing Class: [OKOKO.MyBiz.Dal.Invoice]
Testing SqlExecute Method: +OK Create()
Testing SqlQuery Method: +OK Read()
Testing SqlExecute Method: +OK Update()
Testing SqlExecute Method: +OK Delete()
Testing SqlQuery Method: +OK GetAll()
Testing SqlQuery Method: +OK GetInvoicesByCustomer()
Testing SqlTextQuery Method: +OK GetSQLInvoicesOrdered()
Class [OKOKO.MyBiz.Dal.Invoice] tested. Results: OK: 7/7 Failed:0
Testing Class: [OKOKO.MyBiz.Dal.InvoiceLine]
Testing SqlExecute Method: +OK Create()
Testing SqlQuery Method: +OK Read()
Testing SqlExecute Method: +OK Update()
Testing SqlExecute Method: +OK Delete()
Testing SqlQuery Method: +OK GetAll()
Testing SqlQuery Method: +OK GetByInvoice()
Class [OKOKO.MyBiz.Dal.InvoiceLine] tested. Results: OK: 6/6 Failed:0
-----------------------------------------------------------------
Final results: 3 classes & 22 methods tested.
Results: OK: 20/22 Failed: 2
A closer inspection in the log shows:
If you rebuild and execute the tests again, everything should be OK. The output will be something similar to:
Testing Class: [OKOKO.MyBiz.Dal.Customer]
...
Class [OKOKO.MyBiz.Dal.InvoiceLine] tested. Results: OK: 6/6 Failed:0
-----------------------------------------------------------------
Final results: 3 classes & 22 methods tested.
Results: OK: 22/22 Failed: 0
5. The strategy behind the scenes: AttributesThe main strategy was to label our DAL code with special attributes provided by the Armadillo framework. Later on, our testing engine will find by reflection such DAL methods and test them accordingly during the unit testing phase. Let's review the attributes introduced by Armadillo: DALClass
Usage example: using System;
using System.Data;
using OKOKO.Armadillo.Attributes;
using System.Data.SqlClient;
namespace OKOKO.MyBiz.Dal
{
/// <summary>
/// Customer DAL class. Implements a classical CRUD example
/// </summary>
[DalClass()]
public class Customer
{
...
SqlExecute
Usage example: [SqlExecute()]
public static Customer Create(string id, string name,
string surname, string phone)
{
//INSERT
...
SqlQuery
Usage example: [SqlQuery()]
public static Customer Read(string id)
{
//SELECT .. WHERE ID ...
....
SqlTextExecute
Usage example: [SqlTextExecute()]
public static string GetSqlDeleteAll()
{
return "delete customer";
}
SqlTextQueryVery similar to the previous one, Usage example: [SqlTextQuery()]
public static string GetSQLCustomersOrdered()
{
return "select name, surname from customer order by surname, name";
}
Using the quoted attributes provided by the Armadillo framework, we have labeled all the classes and methods of our DAL code. 6. A simple test project (DalMyBiz.Test)The testing assembly is very straightforward and easy to build. It consists of a unique class with three methods: The Armadillo test engine can be executed providing:
using System;
using OKOKO.Armadillo.Framework;
using OKOKO.MyBiz;
using NUnit.Framework;
namespace OKOKO.MyBiz.Dal.Test
{
/// <summary>
/// Launch the tests
/// </summary>
[TestFixture()]
public class DalTest
{
[SetUp()]
public void Init()
{
Dal.Cnx.Open();
}
[TearDown()]
public void Down()
{
Dal.Cnx.Close();
}
[Test()]
public void TestDB()
{
IDbInfo dbInfo = new SqlServerDbInfo(Dal.Cnx);
//Selection of Sql Server as target DB
TestEngine te = new TestEngine(
System.Reflection.Assembly.GetAssembly(typeof(Customer)),
dbInfo);
}
}
}
7. How it works: the testing engine explainedThe core of the test engine is located in the
Checking SQL Query MethodsSQL query methods are executed against the database. We are assuming no collateral effects on queries. I.e., be sure you do not have a trigger after a Checking SQL Execute MethodsIn this case, we cannot allow a real execution of the SQL because it changes the database. The test here will be:
Similar to the previous case, if everything goes well, nothing will happen, but if the SQL statement is not correct, an exception will be provided. Checking SQL Text Query & Exectute MethodsAs we explained before, DAL methods labeled with
Again, exceptions are the hint to know that SQL statements are corrupted. But, what about parameters?Yes, you will probably have noticed it: DAL methods can have parameters, and we should provide valid values if we want to invoke them by reflection. To overcome this, Armadillo creates default values for each parameter based on the parameter's type. See the method: Anyway, if you have cases where you want to control the parameters to be used during a test, you have an alternative: Armadillo provides another attribute for specifying the specific values for a test: Let's see a small example: [SqlExecute()]
public static bool Update(string id, string number,
string idCustomer, DateTime date)
{
...
}
In this first example, the test engine does not know a value to pass to However, if you need to change this behavior and force a value for a parameter, use the [SqlExecute()]
[ParamValue("idCustomer", "customer123")]
[ParamValue("number", 13)]
public static bool Update(string id, string number,
string idCustomer, DateTime date)
{
...
}
Now, Armadillo has information to provide a specific value for the param The IDbInfo interfaceThe access to the database for testing is encapsulated through the interface
A default implementation for SQL Server is also provided in the Armadillo framework ( 8. UsageIf you decide to use this technique, run the unit tests whenever:
Any detected failure at the time, allows you to fix it before distributing a new version of your code. Preventing, in this way, runtime fails. When to use it
When NOT to use it
9. To Do List
10. References
11. Points of InterestDealing with SQL errors could be a nightmare, especially if the code is under-documented and the database is changed very often. The provided method & tool allows you to test the DAL SQL code before releasing a new version. The objective is to armor your code against any SQL syntactic errors derived from DB changes. If the unit tests are executed in this way, SQL code will be tested against the DB (consider it like a form of assuring SQL precompilation). Therefore, this can avoid ugly surprises to customers at runtime. I developed and apply this technique because I really needed to assure the correctness of a running system with more than four thousand SQL statements. If you cannot automate this, imagine testing them manually one by one in each release. So, I will be happy if this could be helpful to somebody else. Please post your comments & share your opinion. Improvements and usage experiences are especially welcomed. 12. History
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||