Part 3: Automating Unit Testing with a Base Class

In Part 1 of Automating Unit Testing with a Base Class I provided a brief introduction to Unit Testing, provided an overview of the problems that unit testing business objects present, and briefly discussed why I include the database in my unit tests.  In part 2, I provided an overview of the process I've followed in testing the CRUD operations of my business objects.  In this final installment I'm going to discuss how I've simplified the testing of basic CRUD operations on my business objects by creating a base class for my business object unit tests.

Let's Review

Before moving on to the solution I'd like to quickly review the process I follow in testing the CRUD operations on my business objects.

Create

  •  Instantiate an instance of the object
  • Set the properties of the object with valid values.
  • Call the Save() method on the object, checking that the save succeeds.

Read

  • Do Create.
  • Retrieve the object out of the data store and check that the values of the saved object equal the values pulled from the data store.

Update

  • Do Read
  • Change the properties of the object.
  • Call the Save() method on the object.
  • Retrieve the object out of the data store and check that the values of the updated object equal the values pulled from the data store.

Delete

  • Do Create
  • Call Delete() on the object.
  • Try reading the object back out of the data store and ensure we get a null object (since its deleted).

See Part 2 for the code representation of the above

What can we Automate?

Over the past year I've written a lot of unit tests for the CRUD operations on my business objects.  A couple months ago I was working on a moderately sized .NET project coding away, writing failing tests for each of my CRUD operations, writing the code to make the tests past, and refactoring my way to cleaner code.  The process was feeling pretty good, except for one thing.  I seemed to be writing the same code over and over.  Duplication is bad, so why do I allow myself to write 50 test classes with almost identical logic for testing the CRUD operations on my objects?  I try to follow the Don't Repeat Yourself principle that Dave Thomas and Andy Hunt present in The Pragmatic Programmer so I set out to find a better way.

As I began my journey I began to evaluate the tests that I was writing for my business objects.  The tests were slightly different across the project but I saw a similarity among them that led me down an interesting path.  Each test object was essentially performing 3 different tasks, the only difference between the tests was the objects that they were performing the tasks on.

  • Task 1 - Loading objects with data.
  • Task 2 - Calling methods on my business objects (Save, Delete, Load).
  • Task 3 - Comparing two business objects to ensure the values saved and the values loaded were identical.

Task 1 - Loading Objects with data

The first task I identified was the process of loading objects with data.  In order to test the CRUD operations on my objects I first needed to load the objects with data so they could be saved to the database.  Let's take a quick look at how the data was loaded in the unit tests outlined in Part 2 of this series:

Customer customer = new Customer();
customer.ContactName = "Steve Eichert";
customer.Address = "221 South North West Ave.";
customer.City = "Philadelphia";
customer.Region = "PA";

To load the Customer object with data I instantiate an instance of the Customer class and then set the properties of the customer.  The class being instantiated and the properties being set change for each unit test, however, the process is the same.  First instantiate an instance of the class and then set the properties of that class.  Now that the task has been identified how can it be automated?

Provided we have the type of the business object we can use Activator.CreateInstance to instantiate an instance of the class.

BusinessObject bizObject = (BusinessObject) Activator.CreateInstance(BusinessObjectType);

After an instance of the class is created we need a method for setting the properties of the object.  The System.Reflection namespace gives us just what we need.  By using the GetProperties() method on the System.Type object we can identify all the properties defined on our objects.

///

<summary>
/// Load the properties of an BusinessObject with random values.
/// </summary>
/// <param name="bizObject">The <c>BusinessObject</c> to load the properties of.</param>
public
void
LoadProperties(BusinessObject bizObject) {
   System.Reflection.PropertyInfo[] properties = BusinessObjectType.GetProperties();
    foreach(PropertyInfo property in properties) {
      if(property.CanWrite) {
      
SetDynamicPropertyValue(bizObject, property);
     }
   }
}

The LoadProperties() method retrieves all the properties available on our business object and then loops over all the properties and sets appropriate values (SetDynamicPropertyValue).  Depending on the type (string, int, bool, etc) of the property we need to set a different value.  We also need to consider that certain properties have a limited set of potential values.  To set the value of each property we need to first determine the type of property using the PropertyType property of the PropertyInfo object (say that 10 times fast).  The below block of code shows how this can be accomplished.

///

<summary>
/// Set the property of an <c>BusinessObject</c> to a random (dynamic) value.
/// </summary>
/// <param name="bizObject">The <c>BusinessObject</c> to set the value of.</param>
/// <param name="property">The <c>PropertyInfo</c> to set the value of.</param>
private void SetDynamicPropertyValue(BusinessObject bizObject, PropertyInfo property) {
  Random random;
  string propertyKey = bizObject.GetType().Name + "-" + property.Name;
  if(_allowableValues[propertyKey] != null) {
    
object[] values = (object[]) _allowableValues[propertyKey];
    random =
new Random(Environment.TickCount);
    property.SetValue(bizObject, values.GetValue(random.Next(values.Length)),
null);
  }
else {
    ArrayList usedValues = (ArrayList)_uniqueValues[propertyKey];
    
switch(property.PropertyType.ToString()) {
      
case "System.String":
          property.SetValue(bizObject, GetRandomString(usedValues),
null);
         
break;}
      
case "System.Int32":
      
case "System.Double":
          random =
new Random(Environment.TickCount);
          property.SetValue(bizObject, random.Next(1, 9999),
null);
          
break;
       
case "System.Decimal":
          random =
new Random(Environment.TickCount);
          property.SetValue(bizObject, Convert.ToDecimal(random.Next(1, 9999)),
null);
         
break;
      
case "System.DateTime":
          Random dayRandom =
new Random(Environment.TickCount);
          Random monthRandom =
new Random(Environment.TickCount);
          property.SetValue(bizObject, DateTime.Parse(monthRandom.Next(1, 12) + "/" +
                                                                          dayRandom.Next(1, 2 + "/" +
                                                                          DateTime.Now.Year),
null);
         
break;
      
case "System.Boolean":
          random =
new Random(Environment.TickCount);
          property.SetValue(bizObject, Convert.ToBoolean(random.Next(0, 1)),
null);
         
break;
      
default:
         
if(property.PropertyType.BaseType != null && property.PropertyType.BaseType.ToString() == "System.Enum") {
               Array values = Enum.GetValues(property.PropertyType);
               random =
new Random(Environment.TickCount);
               property.SetValue(bizObject, values.GetValue(random.Next(values.Length)),
null);
          }
         
break;
       }
   }
}

The SetDynamicPropertyValue() method accepts the business object being tested (bizObject) as well as the current property that we need to assign a value to (property).  The method evaluates the type of property using the "PropertyType" property of the PropertyInfo object.  There's a couple utility methods that I'm not going to discuss at the moment as to stay on topic.  The bottom line is that by inspecting the PropertyType attribute of each property that we retrieve from the business object we can determine a valid value to assign to the property.  Once we find a value we can use the SetValue method on the PropertyInfo object to assign the property to our business object.

property.SetValue(bizObject, "A property value.", null);

Now that we've automated the process of loading our business  objects with values, lets move on to task 2, calling methods on our object.

Task 2 - Calling Methods

Now that we've instantiated our business object using Activator.CreateInstance, and set the all the properties of our objects using LoadProperties() and SetDynamicPropertyValue(), we need to call the methods on our object and ensure they provide the proper result.  Since all of our business objects inherit from a base BusinessObject class this is extremely easy to automate.  Let's first look back at the interface for our business object base class:

public abstract class BusinessObject {
   public BusinessObject() {}
   abstract public int ID { get; set; }  
   abstract
public void Load(string key);
   abstract public bool Save();
   abstract public bool Delete();
}

Since we already have an instance of a BusinessObject class we can call Save, Load, and Delete.  The base class provides a common interface that allows us to write code in our base test class in a generic fashion.  Rather then instantiating particular types of business objects we simply create an instance of our class using Activator.CreateInstance and then call the necessary methods.

protected
abstract Type BusinessObjectType { get; } 

/// <summary>
/// Test that the business object can be saved.
/// </summary>
[Test]
public virtual void CanBeSaved() {
   Save(BusinessObjectType);
}

/// <summary>
/// Test if the BusinessObject can be deleted.
/// </summary>
[Test]
public
virtual void CanBeDeleted() {
   BusinessObject bizObject = Save(BusinessObjectType);
   Assert.IsTrue(bizObject .Delete(), "The object could not be deleted.");
}


///
<summary>
/// Helper method for saving and business object.
/// </summary>
/// <param name="bizObjectType">The type of business object to save.</param>
protected BusinessObject Save(Type bizObjectType) {
   BusinessObject bizObject = (BusinessObject) Activator.CreateInstance(BusinessObjectType);
   LoadProperties(bizObject);
   SaveAsIs(bizObject);
    return bizObject;
}


///
<summary>
/// Helper method for saving and business object in its current state.
/// </summary>
/// <param name="bizObject">The <c>BusinessObject</c> to save.</param>
private
BusinessObject SaveAsIs(BusinessObject bizObject) {
   Assert.IsTrue(bizObject.Save(), "The " + bizObject.GetType().Name + " could not be saved.");
    return bizObject;
}

Task 3 - Comparing Objects

Now that we've successfully figured out how to load data into our objects as well as call the necessary methods on our objects we're only one step away from having all the pieces in place for the automation of our CRUD unit tests.  The final piece of the puzzle is the comparison of objects.  In order for us to ensure the properties assigned to our objects are persisted to the database properly we need to have the ability to compare two different objects.  In part 2, this was accomplished using the following code.

public void CanBeRead() {
   Customer savedCustomer =
new Customer();
   // ...set properties
   Assert.IsTrue(savedCustomer.Save());
   // read the customer
   Customer readCustomer = new Customer();
   readCustomer.Load(savedCustomer.CustomerID);
   // check properties of the loaded object against the saved object
   Assert.AreEqual(savedCustomer.ContactName, readCustomer.ContactName, "ContactName properties are not equal.");
   Assert.AreEqual(savedCustomer.ContactTitle, readCustomer.ContactTitle, "ContactTitle properties are not equal.");
   Assert.AreEqual(savedCustomer.Address, readCustomer.Address, "Address properties are not equal.");
   // check remaining properties as necessary...
}

As you can see we're manually comparing each property of the savedCustomer object to the same property on the readCustomer object.  We can again use Reflection to retrieve the properties of the business object class to help automate the process of comparing objects.  By looping over each property and comparing the values retrieved from the business objects we can determine if two business objects are "equal."

/// <summary>
/// Test if the business object can be successfully loaded by first saving
/// and then loading and comparing each property value.
/// </summary>
[Test]
public

virtual void CanBeLoaded() {
   BusinessObject savedBizObject = Save(BusinessObjectType);
   BusinessObject bizObject = ((BusinessObject) Activator.CreateInstance(BusinessObjectType);
   bizObject.Load(savedBizObject.ID);

   CompareBusinessObjects(savedBizObject, bizObject);
}

public void CompareBusinessObjects(BusinessObject bizObject, BusinessObject comparedBizObject) {
   // Get all properties and loop through them to ensure the values of the saved
   // business object are the same as the freshly loaded object.
   System.Reflection.PropertyInfo[] properties = BusinessObjectType.GetProperties();
   foreach(PropertyInfo property in properties) {
       if(property.CanWrite && !_ignoredProperties.Contains(bizObjectType.Name + "-" + property.Name))
         Assert.IsTrue(property.GetValue(bizObject,
null).Equals(property.GetValue(comparedBizObject, null)), property.Name + " of the loaded
                                               object is not equal to the value of the saved object.");
     }
   }

}

Wrapping Up

By focusing on the automation of the three common tasks that I was performing in all of my business object unit tests I was able to develop a base unit test that handled the testing of the major CRUD operations on my objects.  By automating this process I am able to focus my attention on the areas that deserve the most attention, the domain layer.  Rather then writing a bunch of very similar tests for testing the CRUD operations on my business objects I'm able to write a bunch of very different tests for the domain layer within my application.  This results in better productivity, and better quality software.

Conclusion

The process of unit testing CRUD operations on business objects can be a very tedious task.  By leveraging a base unit test class we are able to automate this process which greatly reduces the amount of time required to get a suite of unit tests up and running for our objects. 

Although there are a lot of details that I haven't covered in this series of posts on Automating Unit Tests with a Base Class I hope you have seen the advantages that can be gained.  In future posts I'll dive into more of the details and gotcha's that I didn't cover in this first series of articles. 

Did anyone actually make it all the way down  here?   If so please post your thoughts and comments!!

 

# Automating Unit Testing With a Base Class Posts

Saturday, June 19, 2004 1:38 PM by Steve Eichert    
Automating Unit Testing With a Base Class Posts

# re: Part 3: Automating Unit Testing with a Base Class

Saturday, June 19, 2004 5:13 PM by hBifTs    
Good...
It will reduce time :)

thx

# re: Part 3: Automating Unit Testing with a Base Class

Saturday, June 26, 2004 12:21 AM by Chris Garty    
I use OJB.Net (<a target="_new" href="http://ojb-net.sourceforge.net">http://ojb-net.sourceforge.net</a>) to hide persistence from my domain model. I do however have repetative tests to create, load, update, and save my objects to ensure my mappings are correct. I'll have to look at using this approach for those tests.

Nice job...

# re: Part 3: Automating Unit Testing with a Base Class

Saturday, June 26, 2004 2:56 AM by Steve    
Chris, I use a persistence layer as well to hide my domain model. Using a approach similar to the one I discussed here has allowed me to automate a lot of the testing of the basic operations. Helps my focus my attention on other areas that are in more need of the testing efforts!

# re: Part 3: Automating Unit Testing with a Base Class

Friday, March 30, 2007 2:06 AM by Nowzarth    
Hi Every Body
can any body send me the working sample code that is been discussed in the Part 3 Automating Unit Testing with a Base Class .
Email - nowzarthbawa@yahoo.com

# re: Part 3: Automating Unit Testing with a Base Class

Wednesday, April 04, 2007 6:54 PM by Dek Pava    
Agreed with Nowzarth, that would be nice if the working sample available to play with!

# re: Part 3: Automating Unit Testing with a Base Class

Monday, April 16, 2007 7:50 PM by ff    
ff

# re: Part 3: Automating Unit Testing with a Base Class

Tuesday, April 17, 2007 4:23 AM by Nowzarth    
huh
seems to be its a impossible task even by Steve it self. i wrote to his personal email and got reply saying, he is not aware this any more and he cud not remeber it.wht a tragedy for such a good blog and an artical.Any way thanks guys
Nowzarth

# info@shoppingnikesb.com

Thursday, October 22, 2009 10:24 PM by nike sb    
Eliminating the need for the extra trouble to wear cheap jordan shoes?nike sb?ugg boots sale?uggs?ugg sale?nike dunk?Gucci Shoes and nike sb for sale , in the extreme sports, look for happiness in life, with nike dunk high?nike sb dunk , showed off your tall body, do not worry about being laughed at, dancing new style discount ugg boots ?Christian Louboutin?nike dunk mid?Christian Louboutin Boots, for you to save money UGG Classic cardy boots will not forget UGG Classic Tall Boots?UGG Classic mini boots.

#

Wednesday, October 28, 2009 4:01 AM by ugg    

# re: Part 3: Automating Unit Testing with a Base Class

Friday, December 04, 2009 11:01 PM by wewe    
Yet, alltoo often, Hot Spots are hardly documented and over years of cheap uggs evolution, the source code that reifies them becomes cheap uggs entangled with the application specific code.



We base the technique for Hot Spot recovery on the design concept of template cheap uggs. We present the approach and the interactive analysis capabilities of SPOOL to visualize browse, and inspect Hot Spots in both separate and contextual form. The cheap uggs validated based on two industrial systems.

# re: Part 3: Automating Unit Testing with a Base Class

Monday, December 07, 2009 10:59 AM by cfgdfgh    
The summer day has section of lovable Tong Qu the designer handbag, lets your mood be more open and brighter brightly! Slightly arranges the anticipation: Above black chanel bags bright white Kitson tote brand letter, does not have the towering feeling, makes one think that on the contrary the letter and the chanel 2.55 bag is in itself a body. This section of chanel Flap bag bare color miu miu handbags, may use carries with the hand and shoulders two ways, soft cerebral cortex, ultra great capacity, both fashionable and practical design. The bare color replica handbags anything too has not limited in clothing matching, specially in the hot summer day, matches the flowers one-piece dress with the bare color chloe replica bags, neat fashionable matching
designer handbagsdesigner bagsDiscount designer handbagsdesigner pursehandbagsDiscount handbagsCheap handbagsLouis Vuitton handbagsDiscount Louis Vuitton handbagsDiscount Louis VuittonBalenciaga HandbagsBalenciaga BagsDiscount Balenciaga BagsChloe handbagsChloe bagssheet metal punching partshardware punching partschina crystal machinerychina crystal machinerybiometric fingerprint door lockprecision castingmetal castingcast steelcasting companylost wax castinginvestment casting|gucci fabric|gucci wallets|Hermes replica handbags|Hermes handbags|hermes kelly|hermes kelly bag|hermes birkin|hermes birkin bag|Jimmy Choo replica handbags|jimmy choo bag
|piaget watch|tag aquaracer watch|tag Carrera watch|tag heuer Link watch|Tag Heuer Watches|tag heuer formula 1|u-boat watches|Vacheron Constantin watches|Zenith watches|Replica Zenith watches|Zenith El Primero|Breguet watch|dewitt watches|replica Anonimo watches


# re: Part 3: Automating Unit Testing with a Base Class

Monday, December 07, 2009 10:12 PM by erer    
n just a few hours its all set for the trip to Discount software and the next event in EPT that is starting on Thursday. After Barcelona I have been in Oslo software coupon codes where I still hanging around. Today we in Betsson Boys have been at a photo shoot Newest software discount coupons “Norsk Pokermagasin


# ugg boots

Tuesday, December 08, 2009 9:59 AM by UGG Classic Cardy Boots    
Ugg Mayfaire Boots On Sale Buy Cheap Ugg knightsbridge boots,Ugg Bailey Button boots,more 2009 new uggs arrival at Softugg.Inc for you!Free shipping and nontax,Save you 50% OFF!

Classic Tall Ugg Boots on sale!,We are professional supply Ugg bailey button Classic Short Ugg Boots is your best choice,free shipping and nontax,only need 1 week to your door!

# ugg classic tall boots

Tuesday, December 08, 2009 12:20 PM by ugg classic tall boots    

# re: Part 3: Automating Unit Testing with a Base Class

Friday, December 11, 2009 12:02 PM by Cardy Ugg Boots    

# re: Part 3: Automating Unit Testing with a Base Class

Friday, December 11, 2009 12:04 PM by Cardy Ugg Boots    

# discount rolex

Friday, December 11, 2009 12:42 PM by discount rolex    
designer tiffany jewelry
cheap jewelry

tiffany jewelry
silver bracelets
designer replica handbags
cheap nfl jerseys

NFL jerseys
footwear
gucci shoes
ugg store
ugg discount

ugg shoes
boots shoes
ugg discount
cheap ugg classic

fashion ugg
cheap ugg shoes
luxury ugg boots

fake ugg
timberland shoes online
buy timberland boots
cheap timberland
nike air max
discount air nike
nike af1 shoes

af1 shoes
replica rolex watches
rolex watches
ugg classic cardy
buy ugg boots
ugg discount
buy ugg boots

ugg classic
wholesale handbags
nfl football jerseys
nfl jerseys replica
NHL Jerseys

nfl football jerseys
bape shoes
boots shoes
buy ugg boots
ugg classic

ugg boots
Black Ultra Tall Ugg Boots
ugg boots
ugg boots

ugg discount
ugg shoes
discount ugg boots
cheap handbags
cheap nfl jerseys

fashion tiffany jewelry
buy cheap jewelry

tiffany bangles
buy tiffany jewelery
timberland boots
classic timberland

discount rolex
nike jordan
nike shox

Wholsale Footwear for sale
Wholsale Prada Shoes for sale
Wholsale Supra Shoes for sale
Wholsale UGG Boots for sale
Wholsale UGG Classic for sale
Wholsale UGG Ultra for sale

Wholsale Nike Air Max Shoes for sale
Wholsale Nike Shox Shoes for sale
Wholsale New Balance Shoes for sale
Wholsale Nike Basketball Shoes for sale
Wholsale Christian Louboutin Shoes for sale
Wholsale Timberland Boots for sale

Wholsale Nike Air Jordan Shoes for sale
Wholsale AF1-shoes for sale
Wholsale Nike Dunk SB Shoes for sale
Wholsale Gucci Shoes for sale
Wholsale Puma Shoes for sale
Wholsale UGG Bailey Button for sale

Wholsale UGG Classic Cardy for sale
Wholsale UGG Classic Short for sale
Wholsale Timberland Men's 6 Inch Boots for sale
Wholsale Classic Timberland for sale
Wholsale Kid's Timberland Boots for sale
Discount Kid's Timberland Boots

Discount Classic Timberland Short
Discount Timberland Men's 6 Inch Boots
Discount UGG Classic Short
Discount UGG Classic Cardy
Discount UGG Bailey Button
Discount Christian Louboutin Shoes

Discount Timberland Boots
Discount Nike Air Jordan Shoes
Discount AF1-shoes
Discount Nike Dunk SB Shoes
Discount Gucci Shoes
Discount Puma Shoes

Buy Footwear online
Buy Prada Shoes online
Buy Supra Shoes online
Buy UGG Boots online
Buy UGG Classic online
Buy UGG Ultra online

Buy Nike Air Max Shoes online
Buy Nike Shox Shoes online
Buy New Balance Shoes online
Buy Nike Basketball Shoes online
Buy Christian Louboutin Shoes online
Buy Timberland Boots online

Buy Nike Air Jordan Shoes online
Buy AF1-shoes online
Buy Nike Dunk SB Shoes online
Buy Gucci Shoes online
Buy Puma Shoes online
Buy UGG Bailey Button online

Buy UGG Classic Cardy online
Buy UGG Classic Short online
Buy Timberland Men's 6 Inch Boots online
Buy Classic Timberland online
Buy Kid's Timberland Boots online

[url=http://www.lookshoes.net/]nike shox[/url]







# classic ugg tall

Sunday, December 13, 2009 9:37 PM by classic ugg tall    

Ugg bailey button is now very popular. It is different from classic tall ugg and classic short uggs. You can have two styles of one ugg bailey button. And now the ugg classic cardy is also popular one. Here, grey ugg boots will be the most fashion color in this year.Welcome to shop with us. 7 days to your door. Enjoy huge discount.

# re: Part 3: Automating Unit Testing with a Base Class

Sunday, December 13, 2009 10:30 PM by ffff    

# puma on sale

Monday, December 14, 2009 9:14 AM by kisspuma    
PUMA is the world-renowned multinational sports brand, provide the best puma sporting goods.Puma shoes is very popular by customers.Cheap puma shoes on sale,discount puma shoes, Free shipping and custom,6-8days to your door!!Top quality and lowest price!!Hot Sale Puma Men's Future Cat GT Ferrari,Puma Men's Baylee Future Cat II,Puma Men's Speed Cat,Puma Women's Engine Cat Low,Limited store,discount price,order it now!!!

# re: Waterfall vs. Agile Methodologies

Tuesday, December 15, 2009 7:41 PM by gweg    

# re: Part 3: Automating Unit Testing with a Base Class

Tuesday, December 15, 2009 7:41 PM by gwg    

# re: Part 3: Automating Unit Testing with a Base Class

Wednesday, December 16, 2009 10:44 PM by gwegw    

# uggs on sale

Friday, December 18, 2009 7:40 PM by ugg knightsbridge boots    

UGGS ON SALE
Hey,WHAT are you interested in fashion ugg boots? The winter is coming soon, I think you need a pair of ugg classic cardy to keep
your feet warm, or what do you think about ugg classic tall and ugg bailey button?I like these three series ugg boots so much, how about you?

ugg sundance boots
ugg nightfall boots
ugg ultra tall boots
ugg ultra short boots
ugg classic crochet boots
ugg classic mini boots
ugg classic short boots

# ugg bots

Saturday, December 19, 2009 11:46 AM by ugg bots    
Bose On Headphone
mp4 player
electricals kits
wholesale bose headphone
cheapest ipod touch
buy ipod touch
mp4 mp5 players
discount ipods touch
ugg boots
ugg discount
cheap ugg classic
ugg boots
fashion ugg
cheap ugg shoes
ugg discount
fashion tiffany jewelry
buy cheap jewelry
designer tiffany jewelry
cheap jewelry
tiffany jewelry
silver bracelets
tiffany bangles
buy tiffany jewelery
buy timberland boots
classic timberland
timberland shoes online
cheap timberland
nike air max
af1 shoes
nike jordan
nike shox
replica rolex watches
rolex watches
discount rolex
ugg shoes
luxury ugg boots
fake ugg
ugg discount
buy ugg boots
ugg classic
mp4 mp5 player
bluetooth headset
wholesale bose headphone
cheap ugg
ugg shoes online
buy ugg boots
ugg chestnut
Wholsale AF1-shoes for sale
Discount AF1-shoes
Buy AF1-shoes online
Wholsale Christian Louboutin Shoes for sale
Discount Christian Louboutin Shoes
Buy Christian Louboutin Shoes online
Wholsale Classic Timberland for sale
Discount Classic Timberland Short
Buy Classic Timberland online
Wholsale Footwear for sale
Buy Footwear online
Wholsale Gucci Shoes for sale
Discount Gucci Shoes
Buy Gucci Shoes online
Wholsale Kid's Timberland Boots for sale
Discount Kid's Timberland Boots
Buy Kid's Timberland Boots online
Wholsale Timberland Men's 6 Inch Boots for sale
Discount Timberland Men's 6 Inch Boots
Buy Timberland Men's 6 Inch Boots online
Wholsale New Balance Shoes for sale
Buy New Balance Shoes online
Discount Nike Air Jordan Shoes
Buy Nike Air Jordan Shoes online
Wholsale Nike Air Jordan Shoes for sale
Discount Nike Air Jordan Shoes
Buy Nike Air Jordan Shoes online
Wholsale Nike Air Max Shoes for sale
Wholsale Nike Air Max Shoes for sale
Buy Nike Air Max Shoes online
Wholsale Nike Basketball Shoes for sale
Buy Nike Basketball Shoes online
Discount Nike Dunk SB Shoes
Buy Nike Dunk SB Shoes online
Wholsale Nike Dunk SB Shoes for sale
Wholsale Nike Shox Shoes for sale
Buy Nike Shox Shoes online
Wholsale Prada