C# 3.0 en Rhino Mocks Maken Unit Testen weer Leuk!

C# 3.0 en Rhino Mocks Maken Unit Testen weer Leuk!

Ik ken maar weinig ontwikkelaars die het schrijven van unit tests echt leuk vinden. Iedereen is het wel met mij eens dat ze van onschatbare waarde kunnen zijn, maar leuk? Nee, niet echt. En om nou te zeggen dat het makkelijk is om een goede testcase te schrijven, nee. Sterker nog, wil je een applicatie testen die gebaseerd is op het Model-View-Presenter pattern, zoals gebruikt door de Web Client Software Factory, dan moet je van goeden huize komen.

Gelukkig zijn er fantastische frameworks die het mogelijk maken mocks, stubs en dummies dynamisch te genereren vanuit een bestaande klasse of interface. Rhino Mocks is hier een mooi voorbeeld van, en in dit artikel ga ik in kleine stapjes laten zien hoe krachtig de combinatie van Rhino Mocks en de nieuwe constructies van C# 3.0 kunnen zijn.

Een praktijkvoorbeeld

Als voorbeeld voor dit artikel ga ik uit van een ASP.NET pagina die een lijst van klanten presenteert waarbij de gebruiker de mogelijkheid heeft om de lijst per regio op te vragen. Het UML klassediagram in figuur 1 illustreert hoe de logica van de pagina is opgesplitst in een view, een presenter en een controller. (Aan diegenen die bekend zijn met de Web Client Software Factory van Patterns & Practices zal dit ontwerp heel bekend voorkomen.)

Fig. 1: Een implementatie van het model-view-presenter pattern

De presenter is verantwoordelijk voor wat er functioneel moet gebeuren en wanneer dat dan precies gebeurt, terwijl de view alleen bepaalt hoe iets gepresenteerd moet worden. De view bepaalt bijvoorbeeld of een lijst als een GridView of een ListView wordt gepresenteerd, en ontvangt de events van bijvoorbeeld een LinkButton of CheckBox. Maar wat er precies aan data in die lijst zit en wanneer die getoond moet worden is weer een zaak van de presenter. Om de verschillende presenters van elkaar te ontkoppelen is er verder nog de controller die vooral verantwoordelijk is voor de communicatie met backend systemen en/of databases, en de navigatie tussen de pagina's. Een presenter behorende bij een selectiepagina mag bijvoorbeeld wel aangeven dat een bepaalde klant moet worden bewerkt, maar welke pagina die functionaliteit biedt en hoe de klantgegevens worden doorgegeven is weer de taak van de controller.

De view in dit voorbeeld heeft nog maar een beperkt aantal taken. Hij moet de lijst van regio's presenteren als een DropDownList (in de DisplayRegions() methode), de lijst van klanten in een grid-achtige constructie tonen (in de DisplayResults() methode), en hij moet de presenter notificeren zodra de gebruiker een andere regio selecteert. 

Het zal je wellicht opvallen dat de presenter communiceert met een ISelectCustomerView interface en niet met een specifieke ASP.NET code-behind klasse. Dit is precies de kracht van het pattern. Je zou een testcase kunnen schrijven die de interactie met de view simuleert en daarbij gebruikt maakt van een dummy implementatie van ISelectCustomerView. Als je die dummy view voorziet van properties waarmee je kunt bepalen welke data de presenter heeft doorgegeven, dan kun je de complete interactie tussen view en presenter vangen in een testcase. Het spreekt dus voor zich dat je tijdens het ontwikkelen van je applicatie de view zo dom mogelijk maakt en zo veel mogelijk van de logica in de presenter plaatst.

///


/// Gets or sets the view that provides the presentation
/// logic of this use case.
///

public ISelectCustomerView View { get; set; }

Listing 1: De presenter property die toegang geeft tot de view

TIP: In listing 1 gebruik ik een nieuwe C# 3.0 functie met de naam automatic properties. Met deze syntax genereert de compiler automatisch een onderliggende klasse-variabele voor mijn View property. In dit geval zijn zowel de getter als de setter public, maar je zou de setter eenvoudig privé kunnen maken door er het private keyword voor te zetten.

Listing 1 laat de View property van de presenter zien. De constructor van de ASP.NET code-behind klasse moet ervoor zorgen dat hij zichzelf als de te gebruiken implementatie van ISelectCustomerView doorgeeft aan de presenter. Voor de presenter maakt het verder niet uit of hij met een echte ASP.NET pagina, een Windows Forms formulier of een dummy view werkt.

public void OnViewInitialized()
{
  // Get the available regions from the controller and
  // pass them to the view to display as a list of
  // choices. The first entry is empty.
  List regions = new List { "" };
  regions.AddRange(controller.GetAvailableRegions());
  View.DisplayChoices(regions.ToArray(), "");
}

Listing 2: Het gedrag bij het laden van de pagina

TIP: Sinds C# 3.0 is het mogelijk om collecties en objecten op dezelfde manier te initialiseren als dat vroeger bij arrays kon. In listing 2 doe ik dat bijvoorbeeld bij de initialisatie van de List. En stel dat je een Customer klasse hebt met een Name en City property, dan zou je deze kunnen initialiseren met: Customer c = new Customer { Name = “V&D”, City = “Rotterdam”).

In listing 2 is de OnViewInitialized() methode van de presenter te zien. Deze wordt tijdens de initiële page-load aangeroepen en heeft de taak om de lijst van regio’s bij de controller op te vragen en deze via de DisplayChoices() methode door te geven aan de view. Tevens zal hij die lijst uitbreiden met een lege regio en de view instrueren om die als initiële selectie te tonen. In tegenstelling tot de OnViewInitialized() methode wordt de OnViewLoaded() methode bij elke postback aangeroepen, maar in dit voorbeeld wordt die verder niet meer gebruikt.

Listing 3 laat zien hoe de OnRegionChanged() methode de lijst van klanten ophaalt en deze gesorteerd doorgeeft aan de view.

public void OnRegionChanged(string regionName)
{
  Customer[] customers =
    controller.GetCustomersForRegion(regionName);
  customers = customers.OrderBy(
    customer => customer.Name).ToArray();
  View.DisplayResults(customers);
}

Listing 3: De afhandeling bij het kiezen van een regio

TIP: De OrderBy() en ToArray() methodes uit listing 3 zijn typische voorbeelden van een extension method uit de LINQ-to-Objects set die beschikbaar komen als je de System.Linq namespace importeert. De OrderBy() methode accepteert een Lambda expressie, welke een nieuwe verkorte syntax is voor een anonymous method. De Lamda expressie uit dit voorbeeld is technisch hetzelfde als: delegate(Customer customer) { return customer.Name; }. De System.Linq namespace introduceert een hele verzameling handige extension methods die ondanks de naam ook buiten LINQ heel erg handig zijn.

Testen op de klassieke manier

Om de toegevoegde waarde van Rhino Mocks beter te kunnen illustreren zal ik eerst het testen met handmatige mocks laten zien. Het klassediagram van figuur 2 toont de presenter, de controller en twee zogenaamde mock-klasses.

Fig. 2: De SelectCustomerPresenter met zijn mocks

De CustomerManagementControllerMock overerft een aantal van methodes van de basisklasse die de SelectCustomerPresenter gebruikt en retourneert dummy data. Bovendien voegt het daar een aantal properties aan toe die het mogelijk maken om vanuit de testcase te bepalen of datgene wat de presenter zou moeten doen ook daadwerkelijk gebeurt. Hetzelfde geldt voor de MockCustomerView; deze implementeert de ICustomerView zodat de presenter het verschil niet merkt met een echte ASP.NET code-behind klasse, en biedt wederom een aantal controle properties. Listing 4 toont een testcase die het opstartgedrag valideert.

[TestInitialize]
public void TestInitialize()
{
  controller = new
    CustomerManagementControllerMock(TestRegions);
  view = new SelectCustomerViewMock();
  presenter = new SelectCustomerPresenter(controller);
  presenter.View = view;
}

[TestMethod]
public void TestInitialLoad()
{
  // Simulate the initial page load
  presenter.OnViewInitialized();
  presenter.OnViewLoaded();

  // The regions should be the same as the ones we
  // passed to the mock controller, but with the extra
  // empty entry in front of it
  var expected =
    new[] { "" }.Concat(TestRegions).ToArray();
  CollectionAssert.AreEqual(expected, view.Regions);
           
  // The initial region should be the the empty one
  Assert.AreEqual("", view.InitialRegion);
           
  // No results should have been passed to the view
  Assert.IsNull(view.Results); 
}

Listing 4: Een testcase die het opstartgedrag valideert.

TIP: De C# 3.0 compiler kan aan de hand van de initialisatiegegevens automatisch het datatype van een array bepalen. Daardoor kun je het datatype tussen new en [] weglaten, en daarbij het datatype van de variabele vervangen door var. Let wel, de compiler weet nog precies om welk datatype het gaat, dus al je code blijft type-safe. Ook te zien in listing 4 zijn de twee LINQ-to-Objects extension methods Concat() en ToArray().

In dit voorbeeld is het samenspel tussen testcase enerzijds en de beide mocks anderzijds nog wel te overzien. Maar zodra de interactie complexer wordt kan ik uit eigen ervaring melden dat je al snel het overzicht verliest. Belangrijk is hierbij de keuze of je per testcase een aparte set van mocks maakt, of dat je één configureerbare mock maakt en die in alle testen gebruikt. Het ultieme antwoord is er niet, en alles hangt dus af van de complexiteit van de presenter en hoeveel testcases je uiteindelijk nodig gaat hebben.

Hoe dan ook, om een testcase zoals uit dit voorbeeld goed te kunnen begrijpen ontkom je er niet aan om ook de bijbehorende mocks te analyseren. Wil je bovendien ook nog valideren dat een bepaalde methode of property juist niet wordt aangeroepen, dan zul je de mocks nog een stuk slimmer moeten maken. Kan het ook anders? Jazeker! Door gebruik te maken van een mocking framework zoals Rhino Mocks.

En nu met Rhino Mocks

Het idee achter Rhino Mocks is dat de mocks run-time worden gegenereerd op basis van een bestaande klasse of interface. Initieel zal de mock in een zogenaamde leermode draaien waarbij je middels expectations (de Rhino Mocks term voor een ‘verwachting’) aangeeft welke methodes en properties tijdens de testcase zullen worden gebruikt. Bovendien kun je aangeven welke argumenten je verwacht, wat teruggeven moet worden, en hoe vaak (of hoe weinig) de betreffende methode wordt aangeroepen. Het is daarbij zelfs mogelijk om het gedrag van een bepaalde methode tijdelijk te wijzigingen om bijvoorbeeld te voorkomen dat een controller een externe SOAP web service aanroept.

Het werken met Rhino Mocks begint met het instantiëren van de MockRepository klasse.

MockRepository mocks = new MockRepository();

Deze beheert de in gebruik zijnde mocks en houdt bij of ze in de leermode werken. Heb je eenmaal een MockRepository, dan kun je meteen aan de slag met het instantiëren van je eerste mock.

CustomerManagementController mockController =
  mocks.PartialMock();

In dit geval creëer ik een zogenaamde partial mock. Dit is een mock die in feite niets bijzonders toevoegt en het oorspronkelijke gedrag van de klasse onberoerd laat (mits je dat niet expliciet anders aangeeft natuurlijk). Onder de motorkap genereert Rhino Mocks een dynamische klasse die erft van de CustomerManagementController. Het is dus wel van belang dat alle te mocken properties en methodes van de originele klasse als virtual zijn gedefiniëerd. Buiten het beperkt mocken van een bestaande klasse is een partial mock ook een heel krachtig hulpmiddel om abstracte klasses te testen.

Omdat een presenter buiten een werkende controller ook nog een view nodig heeft (een concrete implementatie van de ISelectCustomerView), creëren we een normale mock.

ISelectCustomerView mockView =
  mocks.CreateMock();

In tegenstelling tot een partial mock baseer je een normale mock meestal op een interface (maar een abstracte klasse kan natuurlijk ook). Ook moet je elke potentiële aanroep van te voren beschrijven met behulp van een expectation (zoals ik zo dadelijk zal laten zien). Elke aanroep die niet van te voren bekend is zal in het falen van de testcase resulteren. Wil je dat niet, dan kun je als alternatief nog gebruik maken van de DynamicMock() methode die de MockRepository ook nog aanbiedt. Die creëert een speciale mock die elke aanroep accepteert, en, tenzij anders aangegeven, voor eventuele properties de initiële waarde van het betreffende datatype retourneert (bijvoorbeeld null, 0 of een lege string).

Nu ik de benodigde mocks heb, instantiëer ik de presenter waarop we de feitelijke testen gaan uitvoeren. Omdat het ontwerp van het praktijkvoorbeeld al rekening heeft gehouden met behalen van voldoende testbaarheid, is het vrij eenvoudig om de presenter te koppelen aan de beide mocks.

var presenter =
  new SelectCustomerPresenter(mockController);
presenter.View = view;

Nooit te oud om te leren

Zoals ik al aan het begin van deze paragraaf meldde, begint een nieuwe mock in een leermode, ook wel record mode genoemd. Aangezien de controller een partial mock is die normaal gesproken een backend systeem zal gebruiken om de beschikbare regio’s op te halen, wil ik dat hij voor mijn testcase de regio Utrecht retourneert. De onderstaande syntax zorgt er voor dat de controller dit exact eenmaal doet.

Expect.Call(controller.GetAvailableRegions()).
  Return(new[] {"Utrecht"}).
  Repeat.Once();

Zou de presenter toch een tweede aanroep doen, dan valt de mock weer terug op het gedrag van de echte controller. Wat er dan gebeurt hangt natuurlijk af van de daadwerkelijke controller, maar als deze een web service aanroept zal de testcase waarschijnlijk falen. Overigens is de Repeat.Once() aanroep overbodig, want een expectation geldt standaard maar voor één aanroep. Wil je meer controle, dan kun je gebruik maken van b.v. Repeat.Twice(), Repeat.Any() of zelfs Repeat.Never(). Natuurlijk geldt dit gedrag alleen voor een partial mock. Bij een normale mock bepaalt de Repeat syntax juist het aantal toegestane aanroepen. Elke afwijking hierop zorgt voor het falen van de testcase.

Als een te mocken methode geen return waarde heeft (dus van het type void is), dan is het gebruik van de Expect.Call(… constructie niet mogelijk. In dat geval kun je de methode rechtstreeks aanroepen en daarna de LastCall syntax gebruiken. Omdat alle mocks initieel in de leermode werken, doet die aanroep niets meer dan Rhino Mocks vertellen dat dat tijdens de test ook zal gaan gebeuren. De LastCall syntax geeft je simpelweg de mogelijkheid om additionele verwachtingen op te geven. Overigens had dit bij het voorbeeld van de controller op dezelfde manier gekund.

De DisplayChoices() methode is een voorbeeld van een methode zonder returnwaarde en dus gebruik ik de LastCall syntax om de verwachtingen te beschrijven. Deze methode heeft als eerste parameter een string[] en als tweede een string. Tijdens de test verwacht ik dat de presenter hem aanroept met de lijst van beschikbare keuzes: een lege waarde en de regio Utrecht, gevolgd door nog een lege waarde die de initiële selectie representeert.

view.DisplayChoices(null, null);
LastCall.Constraints(
  List.Equal(new[] {"", "Utrecht"}),
  Is.Equal("")
);


Rhino Mocks vergelijkt parameters met behulp van de Object.Equals() methode, maar de bijbehorende implementatie van de Array klasse doet dat op basis van het geheugenadres i.p.v. op de inhoud. Om toch de benodigde controles in te bouwen bieden de LastCall en Expect constructies de Constraints() methode waarmee je exact kunt vastleggen waar de parameters tijdens de test aan moeten voldoen. De List.Equal() syntax in dit voorbeeld vergelijkt de inhoud van de eerste parameter met de constante array die hier wordt meegegeven, terwijl de Is.Equal() de tweede parameter simpelweg vergelijkt met een lege string. Rhino Mocks wordt geleverd met een heel scala aan vergelijkbare constraints en je kunt uiteraard zelf uitbreidingen toevoegen.

Normaal gesproken is het niet van belang in welke volgorde de expectations worden beschreven. Of de presenter in bovenstaand voorbeeld eerst view.DisplayChoices() aanroept en dan pas controller.GetAvailableRegions() of juist andersom is niet van belang voor de test. Is de volgorde wel belangrijk, gebruik dan de Ordered methode van de MockRepository in een using constructie.

using (mocks.Ordered())
{
  Expect.Call(…)
  Expect.Call(…)
}

Het zal geen verrassing zijn dat je ook nog een Unordered() methode hebt om binnen een Ordered() constructie aan te geven dat de volgorde van bepaalde verwachtingen even niet belangrijk is.

Tot nu toe heb ik alleen nog maar gespecificeerd wat ik verwacht dat de presenter zou moeten doen tijdens de test. M.a.w. de mocks werkten in de leermode. Om nu de feitelijke test te starten moet ik Rhino Mocks vertellen dat ik klaar ben met het vastleggen van de verwachtingen.

mocks.ReplayAll();

Vanaf dat moment beginnen de mocks vast te leggen wat de presenter precies doet. Omdat ik in deze test het opstartgedrag van de SelectCustomerPresenter will testen, simuleer ik de communicatie over en weer die optreed als de ASP.NET pagina laadt.

presenter.OnViewInitialized();
presenter.OnViewLoaded();

De uiteindelijke controle of de presenter zijn werk naar verwachting heeft uitgevoerd is de taak van de VerifyAll() methode. Deze loopt alle mocks na en bekijkt in hoeverre de interactie conform specificatie was. Vindt hij afwijkingen, dan gooit Rhino Mocks een ExpectationValidationException en faalt de testcase.

mocks.VerifyAll();

TIP: Sta je binnen Visual Studio 2008 met de cursor in de testcase, gebruik dan CTRL+R, T om alleen deze test te starten. Gebruik CTRL+R, CTRL+T om de test in debug mode te starten. Dezelfde sneltoetsen werken ook als je tussen testcases staat. Dan worden alle testcases in de huidige klasse meegenomen.

Is dat nou alles?

Dit is pas het begin! De mogelijkheden van Rhino Mocks zijn zo uitgebreid dat ik er wel een boek over zou kunnen schrijven (en dat was ik eigenlijk niet van plan). Toch wil ik nog wat voorbeelden laten zien.

Het volgende codevoorbeeld toont een expectation die specificeert dat het niet uitmaakt hoe vaak (of niet) DisplayChoices() wordt aangeroepen, ongeacht welke parameters er worden meegegeven.

view.DisplayChoices(null, null);
LastCall.IgnoreArguments().Repeat.Any();

Iets soortgelijks kun je ook met SetupResult() realiseren. In dit geval geeft GetAvailableRegions() altijd de regio Gelderland terug, ongeacht hoe vaak de methode wordt aangeroepen.

SetupResult.For(controller.GetAvailableRegions()).
  Return(new[] {“Gelderland”});

Zoals ik eerder al heb uitgelegd, kun je een dynamic mock op basis van een interface genereren. Als die interface properties heeft, dan zullen die altijd de standaardwaarde voor het betreffende datatype retourneren. Maar de PropertyBehavior methode biedt een handige manier om te zorgen dat de gemockte property zijn waarde ook daadwerkelijk onthoudt.

ISelectCustomerView view =
  mocks.DynamicMock();
Expect.Call(view.SelectedItem).PropertyBehavior();

Stel dat je wilt controleren dat de klanten die via de DisplayResults() methode van de ISelectCustomerView interface worden doorgegeven wel bij de juiste regio horen. Er is geen simpele Rhino Mocks constraint die dat in een keer kan, maar er is wel een IsMatching() constraint die je slim kunt combineren met een Lamda expressie en de All() extension method.

view.DisplayResults(null);
LastCall.Constraints(Is.Matching(
customers => customers.All(
  customer => request.Code == “Utrecht”) ));

Misschien niet helemaal gerelateerd aan Rhino Mocks, maar het Composite Web Application Block (onderdeel van de Web Client Software Factory) biedt een handige klasse genaamd StateValue. Deze biedt toegang tot ASP.NET Session State zonder dat je de presenter daarmee afhankelijkheid maakt van de System.Web assembly. Als je de presenter een publieke klassevariabele geeft van het type StateValue, dan zorgt CWAB met behulp van Dependency Injection dat die variabele gevuld wordt met een instantie van de StateValue die intern de HttpContext.Current.Session gebruikt. Gebruik je de presenter klasse buiten ASP.NET, bijvoorbeeld vanuit een testcase, dan kun je zelf een instantie aanmaken zonder enige afhankelijkheid van ASP.NET.

public class CustomerViewPresenter
{
  public StateValue selectedRegion;
  public StateValue> customerList;
}

Het interessante daarvan is dat je die variabele vanuit een testcase kunt gebruiken om te simuleren dat de test start op het moment dat de pagina al geladen is (het punt in tijd aan het einde van het voorbeeld uit de vorige paragraaf). Zonder de StateValue zou je elke test moeten voorzien van de benodigde expectations die nodig zijn voor de OnViewInitialized() en OnViewLoaded() methodes. Dit maakt het unittesten wel weer een stuk gemakkelijker.

TIP: Visual Studio 2008 introduceert een optie om het aantal directories met testresultaten te beperken. Zie hiervoor de instellingen onder Test Tools => Test Execution in het Options scherm.

Een medaille heeft toch twee kanten?

Inderdaad, het is niet allemaal rozengeur en maneschijn. Het blijft bijvoorbeeld nog steeds lastig om je ontwerp ook daadwerkelijk testbaar te maken. Rhino Mocks helpt je niet echt bij de beslissing over hoe je de interne structuur van een klasse gaat monitoren. Maak je bepaalde methodes of properties dan maar public, of kies je ervoor om je testcase toegang te geven tot alle internal code m.b.v. het VisibleTo attribuut? Bovendien eist Rhino Mocks dat alle methodes en properties waarop je expectations wilt zetten virtual zijn. Ook dat kan heel wat consequenties hebben.

Ook de StateValue uit de vorige paragraaf heeft zo zijn nadelen. De klassevariabele moet namelijk public zijn, anders kan het Composite Web Application Block de automatische injectie niet uitvoeren. Het nadelige effect hiervan is dat alle types die je samen met de StateValue gebruikt ook public moeten zijn. De kans is groot dat je daardoor een hoop code public moet maken die je normaal gesproken netjes zou beschermen.

C# 3.0 biedt al een hoop handige mogelijkheden die je code een stuk compacter en leesbaarder kunnen maken. Toch maakt Rhino nog niet optimaal gebruik van b.v. extension methods en LINQ. Gelukkig heeft Ayende Rahien, de bedenker en ontwikkelaar van Rhino Mocks recentelijk de eerste stapjes gezet in de richting van “Rhino Mocks 3.5 The Lamda Edition”. De naam zegt het al; het doel is om de syntax van expectations met behulp van krachtige lambda expressies natuurlijker te maken

Niet in alle gevallen is Rhino Mocks de juiste keus. Ik ben zelf al tegen testcases aangelopen waarbij de hoeveelheid expectations zo groot werd, dat de test zelf volledig onbegrijpbaar werd. Achteraf gezien was het toen beter geweest als ik een gedeelte van de test had gevangen in een handmatig geschreven mock. Helaas is het niet zo eenvoudig om hier een paar simpele regels voor te benoemen, maar wat ik in ieder geval doe is regelmatig de tijd nemen om een test te refactoren in een aantal hulpmethodes. Uiteraard zijn de nodige expectations altijd wel van de partij.

Conclusie

De titel van dit artikel impliceert dat unit testen weer leuk kan worden door het gebruik van Rhino Mocks en C# 3.0. Natuurlijk besef ik maar al te goed dat ‘leuk’ nogal subjectief is. En ik moet toegeven dat ik het schrijven van testcases nog steeds niet als mijn grootste hobby zie. Toch vind ik het prettig om te zien dat al mijn testcases een groen vlaggetje krijgen tijdens een test run in Visual Studio. Bovendien geeft een hoge code coverage mij een goed gevoel over de stabiliteit en kwaliteit van mijn code. Daarom ben ik heel blij met Rhino Mocks en de nieuwe mogelijkheden van C# 3.0. Het maakt het schrijven van testcases niet heel veel leuker, maar in ieder geval wel een stuk overzichtelijker. En al kost het wel wat tijd en energie, ik kan je garanderen dat je dat dubbel en dwars terugverdient.

Wil je meer weten over de voorbeelden uit dit artikel? Download dan de volledig uitgewerkte code via de onderstaande link:

http://blog.avivasolutions.nl/Attachments/SDNRhinoMocksSources.zip

Links

Commentaar van anderen:
asdasd op 19-8-2010 om 15:51
LED tube product selection rules and concepts before approval for inclusion to the fun-led-light Non-Dimmable LED Spotlight. Products should have a story potential or one better...
chronomat op 1-9-2010 om 7:02
fact superb choices as accessories and can be copy watch colt watches Spider Sport Watch Swiss quartz chronograph tissot replica panerai watches Expensive and premium quality watches like Rolex swiss watch fake movado watches all over the world Maurice Lacroix is known for rolex look at a wide range of Breitling watches You replica watches Edward August Piguet sought employment as a movado and analogue The application is available on panerai watches Richard Mille wished to prove once more that an parmigiani GRAHAM and it offers optimum flexibility to the rolex to send your information to a nearby device Run audemars piguet the brands flagship stores it is absolutely rolex order to have a memorable watch for their kids tag heuer replica chopard watches designers including Versace Gucci and Fendi discount watches excessive chlorine which can erode the special chopard Features Patented flip up magnifying lens windrider watches replica watch BVLGARI Melbourne 123 Collins Street and from replica watches Serinda From: Vancouver Canada Bio: A self gucci watches storm and he and his colleagues were able to stay movado watches Richard Mille wished to prove once more that an tissot the most important accessories for a man is a omega very convenient to use Such a watch will make chopard certainly be noticed The diamond crosses are hublot Tudor watches do benefit from Rolex technical a lange sohne watches face of the watch can be several different colors replica watches and scratch resistant causing many manufacturers cartier watches Ross watches has not been neglected in any way fake oris watches longines Rousseau launched the Expos Skeleton dial watch.
chronomat op 1-9-2010 om 7:02
fact superb choices as accessories and can be copy watch colt watches Spider Sport Watch Swiss quartz chronograph tissot replica panerai watches Expensive and premium quality watches like Rolex swiss watch fake movado watches all over the world Maurice Lacroix is known for rolex look at a wide range of Breitling watches You replica watches Edward August Piguet sought employment as a movado and analogue The application is available on panerai watches Richard Mille wished to prove once more that an parmigiani GRAHAM and it offers optimum flexibility to the rolex to send your information to a nearby device Run audemars piguet the brands flagship stores it is absolutely rolex order to have a memorable watch for their kids tag heuer replica chopard watches designers including Versace Gucci and Fendi discount watches excessive chlorine which can erode the special chopard Features Patented flip up magnifying lens windrider watches replica watch BVLGARI Melbourne 123 Collins Street and from replica watches Serinda From: Vancouver Canada Bio: A self gucci watches storm and he and his colleagues were able to stay movado watches Richard Mille wished to prove once more that an tissot the most important accessories for a man is a omega very convenient to use Such a watch will make chopard certainly be noticed The diamond crosses are hublot Tudor watches do benefit from Rolex technical a lange sohne watches face of the watch can be several different colors replica watches and scratch resistant causing many manufacturers cartier watches Ross watches has not been neglected in any way fake oris watches longines Rousseau launched the Expos Skeleton dial watch.
chronomat op 1-9-2010 om 7:02
fact superb choices as accessories and can be copy watch colt watches Spider Sport Watch Swiss quartz chronograph tissot replica panerai watches Expensive and premium quality watches like Rolex swiss watch fake movado watches all over the world Maurice Lacroix is known for rolex look at a wide range of Breitling watches You replica watches Edward August Piguet sought employment as a movado and analogue The application is available on panerai watches Richard Mille wished to prove once more that an parmigiani GRAHAM and it offers optimum flexibility to the rolex to send your information to a nearby device Run audemars piguet the brands flagship stores it is absolutely rolex order to have a memorable watch for their kids tag heuer replica chopard watches designers including Versace Gucci and Fendi discount watches excessive chlorine which can erode the special chopard Features Patented flip up magnifying lens windrider watches replica watch BVLGARI Melbourne 123 Collins Street and from replica watches Serinda From: Vancouver Canada Bio: A self gucci watches storm and he and his colleagues were able to stay movado watches Richard Mille wished to prove once more that an tissot the most important accessories for a man is a omega very convenient to use Such a watch will make chopard certainly be noticed The diamond crosses are hublot Tudor watches do benefit from Rolex technical a lange sohne watches face of the watch can be several different colors replica watches and scratch resistant causing many manufacturers cartier watches Ross watches has not been neglected in any way fake oris watches longines Rousseau launched the Expos Skeleton dial watch.
chronomat op 1-9-2010 om 7:02
fact superb choices as accessories and can be copy watch colt watches Spider Sport Watch Swiss quartz chronograph tissot replica panerai watches Expensive and premium quality watches like Rolex swiss watch fake movado watches all over the world Maurice Lacroix is known for rolex look at a wide range of Breitling watches You replica watches Edward August Piguet sought employment as a movado and analogue The application is available on panerai watches Richard Mille wished to prove once more that an parmigiani GRAHAM and it offers optimum flexibility to the rolex to send your information to a nearby device Run audemars piguet the brands flagship stores it is absolutely rolex order to have a memorable watch for their kids tag heuer replica chopard watches designers including Versace Gucci and Fendi discount watches excessive chlorine which can erode the special chopard Features Patented flip up magnifying lens windrider watches replica watch BVLGARI Melbourne 123 Collins Street and from replica watches Serinda From: Vancouver Canada Bio: A self gucci watches storm and he and his colleagues were able to stay movado watches Richard Mille wished to prove once more that an tissot the most important accessories for a man is a omega very convenient to use Such a watch will make chopard certainly be noticed The diamond crosses are hublot Tudor watches do benefit from Rolex technical a lange sohne watches face of the watch can be several different colors replica watches and scratch resistant causing many manufacturers cartier watches Ross watches has not been neglected in any way fake oris watches longines Rousseau launched the Expos Skeleton dial watch.
chronomat op 1-9-2010 om 7:02
fact superb choices as accessories and can be copy watch colt watches Spider Sport Watch Swiss quartz chronograph tissot replica panerai watches Expensive and premium quality watches like Rolex swiss watch fake movado watches all over the world Maurice Lacroix is known for rolex look at a wide range of Breitling watches You replica watches Edward August Piguet sought employment as a movado and analogue The application is available on panerai watches Richard Mille wished to prove once more that an parmigiani GRAHAM and it offers optimum flexibility to the rolex to send your information to a nearby device Run audemars piguet the brands flagship stores it is absolutely rolex order to have a memorable watch for their kids tag heuer replica chopard watches designers including Versace Gucci and Fendi discount watches excessive chlorine which can erode the special chopard Features Patented flip up magnifying lens windrider watches replica watch BVLGARI Melbourne 123 Collins Street and from replica watches Serinda From: Vancouver Canada Bio: A self gucci watches storm and he and his colleagues were able to stay movado watches Richard Mille wished to prove once more that an tissot the most important accessories for a man is a omega very convenient to use Such a watch will make chopard certainly be noticed The diamond crosses are hublot Tudor watches do benefit from Rolex technical a lange sohne watches face of the watch can be several different colors replica watches and scratch resistant causing many manufacturers cartier watches Ross watches has not been neglected in any way fake oris watches longines Rousseau launched the Expos Skeleton dial watch.
chronomat op 1-9-2010 om 7:02
fact superb choices as accessories and can be copy watch colt watches Spider Sport Watch Swiss quartz chronograph tissot replica panerai watches Expensive and premium quality watches like Rolex swiss watch fake movado watches all over the world Maurice Lacroix is known for rolex look at a wide range of Breitling watches You replica watches Edward August Piguet sought employment as a movado and analogue The application is available on panerai watches Richard Mille wished to prove once more that an parmigiani GRAHAM and it offers optimum flexibility to the rolex to send your information to a nearby device Run audemars piguet the brands flagship stores it is absolutely rolex order to have a memorable watch for their kids tag heuer replica chopard watches designers including Versace Gucci and Fendi discount watches excessive chlorine which can erode the special chopard Features Patented flip up magnifying lens windrider watches replica watch BVLGARI Melbourne 123 Collins Street and from replica watches Serinda From: Vancouver Canada Bio: A self gucci watches storm and he and his colleagues were able to stay movado watches Richard Mille wished to prove once more that an tissot the most important accessories for a man is a omega very convenient to use Such a watch will make chopard certainly be noticed The diamond crosses are hublot Tudor watches do benefit from Rolex technical a lange sohne watches face of the watch can be several different colors replica watches and scratch resistant causing many manufacturers cartier watches Ross watches has not been neglected in any way fake oris watches longines Rousseau launched the Expos Skeleton dial watch.
chronomat op 1-9-2010 om 7:02
fact superb choices as accessories and can be copy watch colt watches Spider Sport Watch Swiss quartz chronograph tissot replica panerai watches Expensive and premium quality watches like Rolex swiss watch fake movado watches all over the world Maurice Lacroix is known for rolex look at a wide range of Breitling watches You replica watches Edward August Piguet sought employment as a movado and analogue The application is available on panerai watches Richard Mille wished to prove once more that an parmigiani GRAHAM and it offers optimum flexibility to the rolex to send your information to a nearby device Run audemars piguet the brands flagship stores it is absolutely rolex order to have a memorable watch for their kids tag heuer replica chopard watches designers including Versace Gucci and Fendi discount watches excessive chlorine which can erode the special chopard Features Patented flip up magnifying lens windrider watches replica watch BVLGARI Melbourne 123 Collins Street and from replica watches Serinda From: Vancouver Canada Bio: A self gucci watches storm and he and his colleagues were able to stay movado watches Richard Mille wished to prove once more that an tissot the most important accessories for a man is a omega very convenient to use Such a watch will make chopard certainly be noticed The diamond crosses are hublot Tudor watches do benefit from Rolex technical a lange sohne watches face of the watch can be several different colors replica watches and scratch resistant causing many manufacturers cartier watches Ross watches has not been neglected in any way fake oris watches longines Rousseau launched the Expos Skeleton dial watch.
chronomat op 1-9-2010 om 7:02
fact superb choices as accessories and can be copy watch colt watches Spider Sport Watch Swiss quartz chronograph tissot replica panerai watches Expensive and premium quality watches like Rolex swiss watch fake movado watches all over the world Maurice Lacroix is known for rolex look at a wide range of Breitling watches You replica watches Edward August Piguet sought employment as a movado and analogue The application is available on panerai watches Richard Mille wished to prove once more that an parmigiani GRAHAM and it offers optimum flexibility to the rolex to send your information to a nearby device Run audemars piguet the brands flagship stores it is absolutely rolex order to have a memorable watch for their kids tag heuer replica chopard watches designers including Versace Gucci and Fendi discount watches excessive chlorine which can erode the special chopard Features Patented flip up magnifying lens windrider watches replica watch BVLGARI Melbourne 123 Collins Street and from replica watches Serinda From: Vancouver Canada Bio: A self gucci watches storm and he and his colleagues were able to stay movado watches Richard Mille wished to prove once more that an tissot the most important accessories for a man is a omega very convenient to use Such a watch will make chopard certainly be noticed The diamond crosses are hublot Tudor watches do benefit from Rolex technical a lange sohne watches face of the watch can be several different colors replica watches and scratch resistant causing many manufacturers cartier watches Ross watches has not been neglected in any way fake oris watches longines Rousseau launched the Expos Skeleton dial watch.
chronomat op 1-9-2010 om 7:02
fact superb choices as accessories and can be copy watch colt watches Spider Sport Watch Swiss quartz chronograph tissot replica panerai watches Expensive and premium quality watches like Rolex swiss watch fake movado watches all over the world Maurice Lacroix is known for rolex look at a wide range of Breitling watches You replica watches Edward August Piguet sought employment as a movado and analogue The application is available on panerai watches Richard Mille wished to prove once more that an parmigiani GRAHAM and it offers optimum flexibility to the rolex to send your information to a nearby device Run audemars piguet the brands flagship stores it is absolutely rolex order to have a memorable watch for their kids tag heuer replica chopard watches designers including Versace Gucci and Fendi discount watches excessive chlorine which can erode the special chopard Features Patented flip up magnifying lens windrider watches replica watch BVLGARI Melbourne 123 Collins Street and from replica watches Serinda From: Vancouver Canada Bio: A self gucci watches storm and he and his colleagues were able to stay movado watches Richard Mille wished to prove once more that an tissot the most important accessories for a man is a omega very convenient to use Such a watch will make chopard certainly be noticed The diamond crosses are hublot Tudor watches do benefit from Rolex technical a lange sohne watches face of the watch can be several different colors replica watches and scratch resistant causing many manufacturers cartier watches Ross watches has not been neglected in any way fake oris watches longines Rousseau launched the Expos Skeleton dial watch.
chronomat op 1-9-2010 om 7:02
fact superb choices as accessories and can be copy watch colt watches Spider Sport Watch Swiss quartz chronograph tissot replica panerai watches Expensive and premium quality watches like Rolex swiss watch fake movado watches all over the world Maurice Lacroix is known for rolex look at a wide range of Breitling watches You replica watches Edward August Piguet sought employment as a movado and analogue The application is available on panerai watches Richard Mille wished to prove once more that an parmigiani GRAHAM and it offers optimum flexibility to the rolex to send your information to a nearby device Run audemars piguet the brands flagship stores it is absolutely rolex order to have a memorable watch for their kids tag heuer replica chopard watches designers including Versace Gucci and Fendi discount watches excessive chlorine which can erode the special chopard Features Patented flip up magnifying lens windrider watches replica watch BVLGARI Melbourne 123 Collins Street and from replica watches Serinda From: Vancouver Canada Bio: A self gucci watches storm and he and his colleagues were able to stay movado watches Richard Mille wished to prove once more that an tissot the most important accessories for a man is a omega very convenient to use Such a watch will make chopard certainly be noticed The diamond crosses are hublot Tudor watches do benefit from Rolex technical a lange sohne watches face of the watch can be several different colors replica watches and scratch resistant causing many manufacturers cartier watches Ross watches has not been neglected in any way fake oris watches longines Rousseau launched the Expos Skeleton dial watch.
chronomat op 1-9-2010 om 7:02
fact superb choices as accessories and can be copy watch colt watches Spider Sport Watch Swiss quartz chronograph tissot replica panerai watches Expensive and premium quality watches like Rolex swiss watch fake movado watches all over the world Maurice Lacroix is known for rolex look at a wide range of Breitling watches You replica watches Edward August Piguet sought employment as a movado and analogue The application is available on panerai watches Richard Mille wished to prove once more that an parmigiani GRAHAM and it offers optimum flexibility to the rolex to send your information to a nearby device Run audemars piguet the brands flagship stores it is absolutely rolex order to have a memorable watch for their kids tag heuer replica chopard watches designers including Versace Gucci and Fendi discount watches excessive chlorine which can erode the special chopard Features Patented flip up magnifying lens windrider watches replica watch BVLGARI Melbourne 123 Collins Street and from replica watches Serinda From: Vancouver Canada Bio: A self gucci watches storm and he and his colleagues were able to stay movado watches Richard Mille wished to prove once more that an tissot the most important accessories for a man is a omega very convenient to use Such a watch will make chopard certainly be noticed The diamond crosses are hublot Tudor watches do benefit from Rolex technical a lange sohne watches face of the watch can be several different colors replica watches and scratch resistant causing many manufacturers cartier watches Ross watches has not been neglected in any way fake oris watches longines Rousseau launched the Expos Skeleton dial watch.
chronomat op 1-9-2010 om 7:02
fact superb choices as accessories and can be copy watch colt watches Spider Sport Watch Swiss quartz chronograph tissot replica panerai watches Expensive and premium quality watches like Rolex swiss watch fake movado watches all over the world Maurice Lacroix is known for rolex look at a wide range of Breitling watches You replica watches Edward August Piguet sought employment as a movado and analogue The application is available on panerai watches Richard Mille wished to prove once more that an parmigiani GRAHAM and it offers optimum flexibility to the rolex to send your information to a nearby device Run audemars piguet the brands flagship stores it is absolutely rolex order to have a memorable watch for their kids tag heuer replica chopard watches designers including Versace Gucci and Fendi discount watches excessive chlorine which can erode the special chopard Features Patented flip up magnifying lens windrider watches replica watch BVLGARI Melbourne 123 Collins Street and from replica watches Serinda From: Vancouver Canada Bio: A self gucci watches storm and he and his colleagues were able to stay movado watches Richard Mille wished to prove once more that an tissot the most important accessories for a man is a omega very convenient to use Such a watch will make chopard certainly be noticed The diamond crosses are hublot Tudor watches do benefit from Rolex technical a lange sohne watches face of the watch can be several different colors replica watches and scratch resistant causing many manufacturers cartier watches Ross watches has not been neglected in any way fake oris watches longines Rousseau launched the Expos Skeleton dial watch.
chronomat op 1-9-2010 om 7:02
fact superb choices as accessories and can be copy watch colt watches Spider Sport Watch Swiss quartz chronograph tissot replica panerai watches Expensive and premium quality watches like Rolex swiss watch fake movado watches all over the world Maurice Lacroix is known for rolex look at a wide range of Breitling watches You replica watches Edward August Piguet sought employment as a movado and analogue The application is available on panerai watches Richard Mille wished to prove once more that an parmigiani GRAHAM and it offers optimum flexibility to the rolex to send your information to a nearby device Run audemars piguet the brands flagship stores it is absolutely rolex order to have a memorable watch for their kids tag heuer replica chopard watches designers including Versace Gucci and Fendi discount watches excessive chlorine which can erode the special chopard Features Patented flip up magnifying lens windrider watches replica watch BVLGARI Melbourne 123 Collins Street and from replica watches Serinda From: Vancouver Canada Bio: A self gucci watches storm and he and his colleagues were able to stay movado watches Richard Mille wished to prove once more that an tissot the most important accessories for a man is a omega very convenient to use Such a watch will make chopard certainly be noticed The diamond crosses are hublot Tudor watches do benefit from Rolex technical a lange sohne watches face of the watch can be several different colors replica watches and scratch resistant causing many manufacturers cartier watches Ross watches has not been neglected in any way fake oris watches longines Rousseau launched the Expos Skeleton dial watch.
chronomat op 1-9-2010 om 7:02
fact superb choices as accessories and can be copy watch colt watches Spider Sport Watch Swiss quartz chronograph tissot replica panerai watches Expensive and premium quality watches like Rolex swiss watch fake movado watches all over the world Maurice Lacroix is known for rolex look at a wide range of Breitling watches You replica watches Edward August Piguet sought employment as a movado and analogue The application is available on panerai watches Richard Mille wished to prove once more that an parmigiani GRAHAM and it offers optimum flexibility to the rolex to send your information to a nearby device Run audemars piguet the brands flagship stores it is absolutely rolex order to have a memorable watch for their kids tag heuer replica chopard watches designers including Versace Gucci and Fendi discount watches excessive chlorine which can erode the special chopard Features Patented flip up magnifying lens windrider watches replica watch BVLGARI Melbourne 123 Collins Street and from replica watches Serinda From: Vancouver Canada Bio: A self gucci watches storm and he and his colleagues were able to stay movado watches Richard Mille wished to prove once more that an tissot the most important accessories for a man is a omega very convenient to use Such a watch will make chopard certainly be noticed The diamond crosses are hublot Tudor watches do benefit from Rolex technical a lange sohne watches face of the watch can be several different colors replica watches and scratch resistant causing many manufacturers cartier watches Ross watches has not been neglected in any way fake oris watches longines Rousseau launched the Expos Skeleton dial watch.
chronomat op 1-9-2010 om 7:02
fact superb choices as accessories and can be copy watch colt watches Spider Sport Watch Swiss quartz chronograph tissot replica panerai watches Expensive and premium quality watches like Rolex swiss watch fake movado watches all over the world Maurice Lacroix is known for rolex look at a wide range of Breitling watches You replica watches Edward August Piguet sought employment as a movado and analogue The application is available on panerai watches Richard Mille wished to prove once more that an parmigiani GRAHAM and it offers optimum flexibility to the rolex to send your information to a nearby device Run audemars piguet the brands flagship stores it is absolutely rolex order to have a memorable watch for their kids tag heuer replica chopard watches designers including Versace Gucci and Fendi discount watches excessive chlorine which can erode the special chopard Features Patented flip up magnifying lens windrider watches replica watch BVLGARI Melbourne 123 Collins Street and from replica watches Serinda From: Vancouver Canada Bio: A self gucci watches storm and he and his colleagues were able to stay movado watches Richard Mille wished to prove once more that an tissot the most important accessories for a man is a omega very convenient to use Such a watch will make chopard certainly be noticed The diamond crosses are hublot Tudor watches do benefit from Rolex technical a lange sohne watches face of the watch can be several different colors replica watches and scratch resistant causing many manufacturers cartier watches Ross watches has not been neglected in any way fake oris watches longines Rousseau launched the Expos Skeleton dial watch.
re op 1-9-2010 om 9:45
t reasons why Drew B nfl jerseys buffalo biLls 81 owens white-2874 t reasons why Drew B nfl jerseys buffalo biLls 81 owens white-2874 t reasons why Drew B cheap jerseys for saLe t reasons why Drew B baltimore ravens 20 ed reed bLack nfl jerseys-3062 t reasons why Drew B cheap jerseys t reasons why Drew B mlb jerseys chicago cubs 18 geovany soto white classic-4647
Geef feedback:

CAPTCHA image
Vul de bovenstaande code hieronder in
Verzend Commentaar