The Database Populator Framework follows the same idea of DbMonster (or at least, was partially inspired from it). The purpose of the framework is to provide a flexible (and smart) set of random data generators to populate your database.
Why ?
If you plan to do database testing, the solution are not much: use transaction, create/kill database after each unit test, clean up after each test or use the new technique using Enterprise Services.
The objective of DPF is not to test the database but provide "food" for your unit test. By generating data randomly (satisfying the constraint), you do not need to clean up and you will avoid test clashing on each other. Moreover, you can also use the DPF to test your database under load.
Random but no dumb
In order to generate data for testing database, you need to take into accounts the constraints on the columns and between the tables. By providing a DataSet representing you database, the framework analyses it and create a set of generators. The data generated satisfies all the constraint. Of course, each row generator can be customized for your own needs, but in general, the information contained in the dataset is enough.
The ideas
The idea are rather simple:
- Each DataColumn has its corresponding IDataGenerator instance,
- Each DataTable has its corresponding ITablePopulator instance,
- Each UniqueConstraint has its corresponding IUniqueValidator instance which takes care of ensuring the all unique constraint are validated,
- Each ForeignKeyConstraint has its corresponding IForeignKeyConstraint instance which will fetch valid foreign key values
The generation of a new row is made inside a ITablePopulator as follows:
- Create a row,
- Call each IDataGenerator and fill the row,
- For each foreign key, get relevant values,
- Verify unique constraints, if violated go to 1,
- return row
The first bits
The first bits of the framework is located in the MbUnit.Framework.Data namespace. Althout the framework still needs work, the first example is running and is quite promising.
I have built a simple database containing User - Order - Product - OrderProduct:
this.dataSet=new DataSet();
DataTable users=dataSet.Tables.Add("Users");
DataColumn userID = users.Columns.Add("UserID",typeof(int));
DataColumn userName=Users.Columns.Add("UserName",typeof(string));
DataColumn userName.AllowDBNull=false;
DataTable orders=dataSet.Tables.Add("Orders");
DataColumn orderID=orders.Columns.Add("OrderID",typeof(int));
DataColumn orderDate = orders.Columns.Add("OrderDate",typeof(DateTime));
DataColumn oUserID = orders.Columns.Add("UserID",typeof(int));
DataTable products=dataSet.Tables.Add("Products");
DataColumn productID=products.Columns.Add("ProductID",typeof(int));
DataColumn productName = products.Columns.Add("ProductName",typeof(string));
DataColumn productPrice = products.Columns.Add("ProductPrice",typeof(decimal));
DataTable orderProducts=dataSet.Tables.Add("OrderProducts");
DataColumn opOrderID=orderProducts.Columns.Add("OrderID",typeof(int));
DataColumn opProductID=orderProducts.Columns.Add("ProductID",typeof(int));
DataColumn quantity=orderProducts.Columns.Add("Quantity",typeof(int));
// pks
users.Constraints.Add("PK_Users",userID,true);
orders.Constraints.Add("PK_Orders",orderID,true);
products.Constraints.Add("PK_Products",productID,true);
orderProducts.Constraints.Add("PK_OrderProducts",
new DataColumn[]{ opOrderID, opProductID}
,true);
// fks
orders.Constraints.Add("FK_Orders_Users",userID,oUserID);
orderProducts.Constraints.Add("FK_OrderProducts_Orders",orderID,opOrderID);
orderProducts.Constraints.Add("FK_OrderProducts_Products",productID,opProductID);
This database is easily populated using the DPF:
// creating populator
IDatabasePopulator pop=new DatabasePopulator();
// anaylising internal structure
this.pop.Populate(this.db.DataSet);
After that, you can customize the behavior of every data generator. Once this is done, you are ready to "feed" your database:
//getting the users table populator
ITablePopulator userPop = pop.Tables[users];
// adding new row
users.Rows.Add( userPop.Generate() );