BLOGas.lt
Sukurk savo BLOGą Kitas atsitiktinis BLOGas

LINQ to SQL testuojamumo pagerinimas - 4 dalis (IoC, DI)

Parašė Sergejus | 2008-06-26 00:01

Praeitose keturiose dalyse mes pilnai įgyvendinome LINQ to SQL testuojamumo pagerinimo prototipą (kodas prieinamas čia).


Kontrolės inversija ir priklausomybės injekcija


Prisiminkime, LINQ to SQL repositorijuje nėra nurodytas konkretus duomenų kontekstas, jį mes perduodame per konstruktorių. Tas pats liečia ir ProductService klasę, verslo logikai mes galime nurodyti bet kokį repozitorijų, įskaitant menamą. Šis principas vadinasi kontrolės inversija (Inversion of Control, IoC) ir padeda sumažinti klasių sukibimą.


Nepaisant fakto, kad mūsų klasės mažai sukibusios, testinė programa glaudžiai rišasi su NorthwindDataContext, SqlRepository ir ProductService. Jeigu mes nagrinėtume web aplikacija – ten toks prisirišimas būtų kiekviename puslapyje, kas nėra gerai iš dizaino pusės. Norint pakeisti repozitorijų (pavyzdžiui iš LINQ to SQL į Entity Framework), mums tektų keisti kodą kiekvienoje vietoje, kur naudojamas repozitorijus arba verslo logikos servisas.



Kas esate susipažinę su kontrolės inversijos principu žinote, kad kartu nagrinėjama ir kita sąvoka – priklausomybės injekcija (Dependency Injection, DI). Jos pagalba galima nusakyti,  kad ten kur bus prašomas IRepository tipo objektas, turi būti sukurtas ir pateiktas SqlRepository objektas. Kadangi toks susiejimas aprašomas vienoje vietoje, programos kodo prisirišimas prie konkrečių klasių žymiai sumažėja, kas ilgainiui sutaupo nemažai laiko.


Šiuo metu egzistuoja nemažai .NET Framework skirtų priklausomybės injekcijos karkasų: Castle Windsor, StructureMap, Spring.NET, Ninject, Autofac ir daugelis kitų. Ne taip seniai pasirodė naujas Microsoft sukurtas priklausomybės injekcijos karkasas Unity. Juo mes ir pasinaudosime.


Unity


Unity leidžia tipų susiejimus aprašyti tiek XML faile, tiek kode. Aš parodysiu kaip tai daroma iš kodo, tam apibrėžkime statinę klasę UnityConfig:



Pagrindinė Unity klasė yra UnityContainer. Metodo RegisterType pagalba galima susieti bendresnį tipą su labiau specifiniu. Taip, pavyzdžiui, visur kur bus prašoma Unity gauti IProductService objektą, bus grąžinamas naujas ProductService objektas. Už tokių objektų grąžinimą yra atsakintas metodas Resolve.


Atnaujinta mūsų programa atrodys taip:



Bandant paleisti šią programą, bus išmesta tokia klaida



Tai susiję su tuo, kad kuriant nurodyto tipo objektą, Unity bando pasinaudoti daugiausiai parametrų priimančiu konstruktoriumi. Kadangi NorthwindDataContext turi du konstruktorius su dviem argumentais, Unity negali nuspręsti kuriuo iš jų pasinaudoti. Tarkime, mes norime kad naujas NorthwindDataContext būtų kuriamas naudojant konstruktorių be parametrų. Vienas iš būdų tai pasiekti, nurodyti atributą InjectionConstructor virš tuščio konstruktoriaus:



Toks sprendimas tinka nuosavoms klasėms, bet ne automatiškai generuojamoms (tokioms kaip LINQ to SQL klasės). Kitas būdas – atnaujinti UnityConfig klasės konstruktorių:



Metodas Configure leidžia nurodyti, kad NorthwindDataContext tipo objektas turi būti kuriamas naudojant tuščią konstruktorių.


Grįžtant prie metodo Resolve, verta parodyti kaip bus vykdomas IProductService objekto kūrimas:



  • Pirma Unity norės sukurti ProductService objektą, bet tam reikia į konstruktorių perduoti IRepository objektą.

  • Tada Unity peržiūrės susiejimus ir norės sukurti SqlRepository objektą, bet ir jam reikia į konstruktorių perduoti DataContext objektą.

  • Pagal susiejimą ir konfigūraciją Unity sukurs NortwindDataContext objektą, tada užbaigs SqlRepository objekto kūrimą ir galiausiai grąžins sukurtą ProductService objektą.

Kaip matyti iš veiksmų eiliškumo, mūsų testiniai programai duomenų kontekstą išreikštinai net nereikia kurti, todėl galutinis variantas atrodo taip:



Matome, kad nuo šiol mūsų programa nepriklauso nuo jokios konkrečios klasės (išskyrus Product), todėl ir sukibimas yra minimalus.


Pabaiga


Su šia dalimi aš baigiu LINQ to SQL testuojamumo pagerinimo straipsnių ciklą ir LABAI laukiu jūsų atsiliepimų bei pageidavimų komentaruose!

Rodyk draugams

LINQ to SQL testuojamumo pagerinimas - 3 dalis (Mocking)

Parašė Sergejus | 2008-06-25 00:00

Praeitoje dalyje mes realizavome verslo logikos klasės ProductService funkcionalumą (išeities tekstai prieinami čia). Kaip ir žadėjau, šiandiem mes užsiimsime jos testavimu, bet prieš tai prisiminkime klasių diagramą:



ProductService testavimas


Kaip ir SqlRepository atveju, ProductService klasei aš pasirašiau keletą testavimo atvejų:



Pateiktame kode jus turėtų sudominti metodas Init. Atsimenate, mūsų pagrindinis tikslas buvo atskirti verslo sluoksnį nuo duomenų sluoksnio. Kadangi duomenų sluoksnį mes ištestavome ir žinome, kad jis veikia gerai, verslo sluoksnio testavimui nėra prasmės naudoti duomenų bazę. Tai be reikalo apkrautų serverį bei ženkliai prailgintų testų vykdymą.


Vienas iš galimų sprendimų – sukurti klasę, kuri realizuoja interfeisą IRepository ir tvarko įrašus atmintyje. Pagrindinė problema – kiekvienam verslo servisui reikėtų rašyti po savo klasę: ProductMemoryRepository, CategoryMemoryRepositorty ir pan. Šiuo atveju, aš pasinaudojau vienu iš sparčiai populiarėjančių Mock karkasų – MoQ.


MoQ panaudojimas testavime


Mock karkasų paskirtis – leisti sukurti netikrą (menamą) nurodyto tipo objektą bei aprašyti metodų reakciją į tam tikrus duomenis. Išanalizuokime pateiktą kodą:



  • Pirma sukuriamas netikras objektas, tenkinantis interfeisą IRepository ir konkretizuotas klasei Product. Svarbu suprasti, kad sukurtas objektas tik realizuoja interfeisą, bet nepateikia jokios realizacijos.

  • Toliau aprašome testinius duomenis, kuriuos turi grąžinti menamas repozitorijus (metodas aprašytas skiltyje Helpers).

  • Kadangi testavimui reikalingi ne visi, o tik du IRepository metodai GetAll ir IsNew, toliau aprašomi norai kiekvienam iš metodų.

  • Pirmas noras nusako, kad kviečiant GetAll, metodas grąžintų prieš tai sukurtus produktus. Labai patogu, kad metodų (ir savybių) pavadinimai išreiškiami ne tekstu, bet panaudojant Lambda išraiškas (ateinančiuose straipsniuose aš parodysiu kaip tai apsirašoma).

  • Antras noras nurodo, kad kviečiant IsNew su bet kuriuo Product tipo objektu, metodas grąžins teigiamą reikšmę tik esant produkto ID lygiam 0.

  • Paskutiniu žingsniu tiesiog perduodame aprašytą menamą repozitorijų į ProductService konstruktorių.

Matome kaip parastai ir efektyviai galime testuoti verslo logiką, atskirę ją nuo LINQ to SQL duomenų konteksto! Kitoje dalyje aš parodysiu kaip galima dar labiau sumažinti klasių sukibimą, panaudojant kontrolės inversijos ir priklausomybės injekcijos principus bei naują Microsoft priklausomybės injekcijos karkasą Unity.

Rodyk draugams

LINQ to SQL testuojamumo pagerinimas - 2 dalis

Parašė Sergejus | 2008-06-24 00:08

Praeitoje dalyje mes įgyvendinome SqlRepository klasę (kodas prieinamas čia) ir dabar klasių diagrama atrodo taip:



ProductService įgyvendinimas


Atskirę ir ištestavę duomenų sluoksnį, laikas pereiti prie verslo logikos. Šiame prototipe aš apsiribosiu dviem metodais: GetProduct ir ProductsInStock. Tam pirma aprašysime IProductService interfeisą




Ir vėl matome, ProductsInStock metodas grąžiną IQueryable, tam kad šio metodo rezultatu galima būtų ir toliau operuoti. Interfeiso IProductService realizacija yra pakankamai paprasta:



Metodas GetProduct grąžina egzistuojantį produktą jeigu toks egzistuoja repozitorijuje (nebūtinai duomenų bazėje) arba naują – priešingu atveju. Šių metodų panaudojimas galėtų būtų toks:



Kadangi metodas ProductsInStock grąžina IQueryable, mes galime taikyti jam standartinius išplėtimo metodus. Tiek šiam kartui, kitoje dalyje aš aptarsiu MoQ karkaso panaudojimą testuojant šią klasę.

Rodyk draugams

LINQ to SQL testuojamumo pagerinimas - 1 dalis

Parašė Sergejus | 2008-06-23 00:01

Praeitoje dalyje mes aptarėme repozitorijaus projektavimo šabloną bei aptarėme esybes, su kuriomis dirbsime toliau. Kodas prieinamas čia.


SqlRepository įgyvendimas


Pats laikas realizuoti repozitorijų, skirtą darbui su LINQ to SQL:



SqlRepository klasės konstruktorius priima duomenų kontekstą, su kuriuo vėliau ir bus dirbama. Tai leis atrišti verslo logikos ir duomenų sluoksnius. Kintamasis identity – tai nuoroda į LINQ to SQL esybės savybę, kuri susieta su pirminiu raktu. Vėliau pasinaudojant šiuo kintamuoju mes galėsime nuskaityti esybių pirminius raktus. Toliau realizuojame visus interfeiso IRepository metodus iš eilės:



Šalia to, SqlRepository klasėje naudinga realizuoti interfeisą IDisposable, kas leis užtikrinti duomenų konteksto atlaisvinimą:



SqlRepository testavimas


Tam, kad įsitikinti mano repozitorijaus šablonas veikia gerai, aš aprašiau 14 testavimo atvejų:



Tenka pripažinti, aš nesu labai patyręs testavimo atvejų rašyme, bet tikrai malonu žiūrėti į “žalius” rezultatus



Aprašyto repozitorijaus panaudojimas galėtų būti toks:



Kaip matyti, duomenų kontekstas yra panaudojamas tik vieną kartą, perduodant jį repozitorijaus konstruktoriui. Kitoje dalyje mes įgyvendinsime ProductService klasę…

Rodyk draugams

LINQ to SQL testuojamumo pagerinimas - 0 dalis

Parašė Sergejus | 2008-06-22 19:49

Įžanga


Nežinau kaip jums, bet man vis daugiau tenka dirbti su LINQ to SQL. Tai galinga ir patogi technologija, pagreitinantį aplikacijų kūrimą, bet ji turi tam tikrą trūkumą – ribotą testuojamumą. Tiksliau pasakius, nėra labai paprasta testuoti verslo logiką atskirai nuo duomenų bazės.


Aš jau senokai nagrinėju skirtingas LINQ to SQL testuojamumo pagerinimo galimybes ir prieš kelias dienas pradėjau kurti prototipą, leisiantį nepriklausomą verslo logikos ir duomenų sluoksnių testavimą. Tam aš nusprendžiau pasinaudoti patobulintu repozitorijaus (Repository) projektavimo šablonu. Kodėl būtent repozitorijaus šablonas? Mano manymu tai yra vienas paprasčiausių ir efektyviausių projektavimo šablonų, leidžiančių eliminuoti LINQ to SQL duomenų konteksto (data context) panaudojimą, taip atskiriant verslo logikos sluoksnį nuo duomenų sluoksnio.


Pirma nutariau pasižiūrėti egzistuojančius sprendimus. Buvau nustebintas, kad tokių sprendimų yra labai mažai ir didžioji jų dalis nėra universalūs. Iš įdomesnių verta paminėti bazinės repozitorijaus klasės kūrimą bei nuo esybės priklausančio repozitorijaus kūrimą. Deja, nei vienas iš sprendimų man netiko…


Šiandien aš pradedu kelių straipsnių ciklą, skirtą LINQ to SQL testuojamumui pagerinti. Visas kodas prieinamas čia.


Repository projektavimo šablonas


Pradėsime nuo repozitorijaus projektavimo šablono. Tradicinis repozitorijaus šablonas reikalauja apibrėžti tokį interfeisą:



Aš nutariau šį šabloną truputi pakeisti: pridėjau metodus SaveAll ir DeleteAll, tam kad nereikėtų operuoti transakcijos sąvoka saugant kelis įrašus; pridėjau metodą IsNew, kuris leistų sužinoti ar įrašas yra naujas (nebuvo išsaugotas duomenų bazėje); pašalinau metodą Get, nes taip ir nesugalvojau kaip LINQ to SQL pagalba ištraukti objektą, žinant tik raktinio stulpelio pavadinimą ir tipą (jeigu turėsite minčių – labai laukiu komentaruose!).



Kaip matyti iš diagramos, dar vienas svarbus skirtumas – GetAll grąžina ne IEnumerable, bet IQueryable. Tai suteiks neįtikėtinai daug lankstumo verslo logikos sluoksnyje.


Savo prototipui, aš naudosiu gerai žinomą duomenų bazę Northwind, o dar tiksliau, tik 2 jos esybes: Product ir Category:



Turint NorthwindDataContext duomenų kontekstą ir LINQ to SQL esybes, kitoje dalyje realizuosime IRepository interfeisą, skirtą LINQ to SQL.

Rodyk draugams

params raktažodžio niuansai

Parašė Sergejus | 2008-06-19 17:03

Šiandien geras mano pažįstamas uždavė įdomų klausimą, su kuriuo teko asmeniškai susidurti prieš porą mėnesių. Pamaniau verta jį užduoti ir jums…


Nekompiliuodami kodo, pasakykite kokie 2 skaičiai bus išvesti į ekraną ir kodėl:



Atsakymus bei savo pamąstymus rašykite komentaruose!

Rodyk draugams

Apibendrintas Singleton projektavimo šablonas

Parašė Sergejus | 2008-06-16 22:41

Šiandien skaitydamas O‘Reilly knygą „C# 3.0 Design Patterns“ pastebėjau labai įdomų Singleton projektavimo šablono variantą – apibendrintą Singleton.


Kaip žinia, Singleton projektavimo šablonas leidžia sukurti lygiai vieną klasės egzempliorių. Šio projektavimo šablono C# realizacija galėtų atrodytų taip:



Prieš tai pateiktas pavyzdys inicializuoja objektą iš karto, kas ne visada gali būti optimalu. Objekto inicializavimas pagal poreikį (lazy-loading) gali būti realizuotas taip:



arba taip:



Pirmu atveju patikrinimui naudojamas if sakinys, todėl norint kad klasė gerai veiktų esant kelioms gijoms, reikia naudoti bloką lock. Antru atveju aprašoma speciali vidinė klasė, kuri yra kuriama tik pirmą kartą prieinant prie Singleton klasės savybės Instance. Kiek teko matyti, antras variantas naudojamas dažniau negu pirmas.


Tai kas per projektavimo šablonas apibendrintas Singleton? Tai bendrybinė (generic) Singleton klasė, kuri leidžia bet kurios klasės egzempliorių sukurti lygiai vieną kartą:



Tokios klasės panaudojimas atrodytų taip:



Tiek šiam kartui!

Rodyk draugams

Pirmasis Baltijos šalių MSP susitikimas

Parašė Sergejus | 2008-06-15 21:10

Šį savaitgalį Estijoje vyko pirmasis Baltijos šalių Microsoft studentų partnerių susitikimas. Jo metu mes dalinomės patirtimi ir idėjomis, susijusiomis su vartotojų grupių veiklomis bei naujų žmonių įtraukimu. Šeštadienį aš dariau pristatymą apie šį blogą, apie Lietuvos .NET vartotojų grupę bei apie Microsoft Internship programą ir jos atrankos procesą.


Trumpai, renginys paliko tikrai gerą įspūdį, nes leido pasižiūrėti kaip organizuojamas darbas su  IT bendruomene kitose Baltijos šalyse. Grįžus į Lietuvą, manau reikės pabandyti pritaikyti kitų MSP išsakytus pasiūlymus dėl



  • aktyvaus .NET vartotojo grupės nario statuso

  • naujienų apžvalgos kiekvieno susitikimo pradžioje

Jeigu yra studentų, norinčių prisidėti prie šios veiklos ir ateityje gauti MSP statusą (ir tam tikrus bonusus), galite rašyti man ir aš susisieksiu su jumis rudenį!

Rodyk draugams

Kodo metrikų skaičiavimas Visual Studio 2008

Parašė Sergejus | 2008-06-11 21:25

Paskutines kelias dienas skaičiau apie įvairias kodo metrikas: kodo eilučių skaičių (Lines Of Code, LOC), paveldėjimo medžio gylį, ciklomatinį sudėtingumą ir panašiai. Šiandien nusprendžiau pasirašyti paprastą LOC skaičiuotuvą skirtą C# kodui. Pasileidau Visual Studio ir prisiminiau, kažkada Microsoft žadėjo integruoti kodo metrikas į Visual Studio 2008. Žiūriu, meniu Analyze atsirado naujas punktas Calculate Code Metrics for Solution!



Paleidžiame kodo metrikų skaičiavimą ir matome …



Visual Studio 2008 palaiko keturias pagrindines metrikas: ciklomatinį sudėtingumą, paveldėjimo medžio gylį, sukibimą su klasėmis ir tradicinį kodo eilučių skaičių. Remiantis šiomis keturiomis reikšmėmis apskaičiuojamas bendras kodo palaikomumo indeksas.



Apibendrinant, šaunu, kad jau ir Visual Studio siūlo pagrindinių metrikų skaičiavimą. Deja, bet norint naudoti daugiau metrikų, teks įsigyti trečių šalių įrankius, pavyzdžiui, NDepend.

Rodyk draugams

Silverlight 2 Beta 2

Parašė Sergejus | 2008-06-07 21:08

Prieš porą dienų Bill Gates anonsavo naują Silverlight beta versiją, o šiandien ji jau viešai prieinama! Kartu su Silverlight atsinaujino ir Expression Blend (dabar June 2008 Preview). Pasikeitimų naujoje versijoje tikrai nemažai, iš svarbesnių:



  • Didžioji dalis komponentų perkelta iš pagalbinės System.Windows.Controls.dll bibliotekos į pagrindinę System.Windows.dll

  • Atsirado naujas TabControl komponentas, atsinaujino TextBox, Calendar, DatePicker ir GridSplitter komponentai, atsisakyta WatermarkedTextBox komponento

  • Abipusis duomenų susiejimas dabar palaiko validavimą (tikrai svarbus dalykas praktikoje)

  • Patobulintas darbas su Windows Communication Foundation ir ADO.NET Data Services

  • ir daug daug kitų dalykų…

Išsamų sąrašą galima rasti Scott Guthrie ir ADOguy tinklaraščiuose (aka bloguose).


Ko dar trūksta: asmeniškai man - ComboBox (DropDownList) ir ProgressBar komponentų…

Rodyk draugams