Tfs laika uzskaite (d2) - Vienumu testi

Pārejam pie otrās daļas...

Laika uzskaites sistēma plānots, ka sastāvēs no sekojošiem moduļiem:

  • DataModel: saturēs visu nepiečiešamo informāciju par datu modeli (LINQ to SQL klases)
  • Interfaces: servisu sadarbības modeļa intereisi (datu pārraides konkrakti, WCF)
  • WebServices: datu apmaiņas servisu interfeisu implementācija
  • DataLayer: datu pieejas un biznesa loģikas slānis, kurš sevī realizē servisu fasādes patternu
  • Sql: datu struktūras pārvaldības projekts (VSDBPro)
  • WAControl: laika uzskaites lietotāja kontrole (Team System Web Access lietotāja interfeiss)
  • WorkLogControl: laika uzskaites lietotāja kontrole (Visual Studio lietotāja interfeiss)
  • WindowsTestClient: Visual Studio projekta kontroles visuālais testēšanas klients
  • UnitTests: vienumu testi (pārsvarā tiks izmantoti, lai pārbaudītu un nodrošinātu servisu interfeisu implementācijas projekta kvalitāti)
  • Client.Setup: projekts, ar kura palīdzību laika uzskaites sistēma tiks instalēta lietotājiem, kuri gribēs laiku uzskaitīt no Visual Studio platformas
  • Server.Setup: apvieno visu nepieciešmo, kas būs nepieciešams Team System Application Tier servera slānim (servisu implementācijas un Team System Web Access kontroles instalācija)

Plānoto projektu atkarības diagramma varētu izskatīties šāda apmēram:

 

Protams rodas jautājums, kāpēc tik mazā sistēmā ir nepieciešams tik daudz fizisko projektu? Jā, iespējams, ka skaits ir neadekvāts, bet lai uzskatāmāk nodalītu jēdzienus un apgabalus, atdalīsim tos arī fiziski katru savā projektā.

 

Tātad jautājumu pirmajā daļā pareizi atbildēja puisis strīpainajā kreklā priekšpēdējajā rindā :) "Jāsāk ir ar vienumu testiem (unit tests)". Bet nu visu pēc kārtas.

Lai mēs varētu sekmīgi uzrakstīt vienumu testus, mums ir manuprāt nepieciešams izpildīt sekojošus priekšnosacījumus:

  • nepieciešama datu struktūra
  • nepieciešams datu objektu modelis
  • nepieciešams datu apmaiņas ziņojuma struktūru kontrakts
  • nepieciešams servisa datu apmaiņas kontrakts

 

Pirmos 3 var iegūt ar LINQ tehnoloģijas palīdzību.

 

Datu modelis

Lai uzsāktu darbu ar LINQ to SQL tehnoloģijām mums ir nepieciešams pievienot mūsu projektam jaunu LINQ to SQL klasi, kas apkopos sevī informāciju par mūsu sistēmā izmantojamajām datu struktūrām.

 

Pirms tam, lai mēs veiksmīgi varētu veikt LINQ klašu izveidi, mums ir nepieciešams izdomāt arī pašu datu bāzes struktūru, kurā mēs glabāsim laika uzskaiti.

 

Pēc tam kad esam izveidojuši fizisko datu modeli, laiks ķerties klāt loģiskajam - LINQ to SQL. Viss, kas ir jādara, ir nepieciešams no Server Explorer loga attiecīgā servera un datu bāzes, kur sakotnēji izveidojām fizisko datu modeli, "pārvilkt" to uz LINQ to SQL klašu dizainera darba virsmu :) Jaunais datu modelis izskatās apmēram šāds:

Kā redzams, tad ER diagramma stipri neatšķiras no LINQ to SQL diagrammas :)

Biju iedomājies, ka šī LINQ redakcija varētu būt tik ērta un piedāvāt pašai automātisko konvertāciju no "user_name" (Sql) uz "UserName" (LINQ), bet šis pasākums notiek tikai ar cilvēka manuālu iejaukšanos.

Tātad datu modelis mums ir vairāk vai mazāk izveidots, varam ķerties klāt pie servisu interfeisiem un to implementācijas.

 

Servisi

Tātad, mums ir nepieciešams:

  • nepieciešams datu apmaiņas ziņojuma struktūru kontrakts
  • nepieciešams servisa datu apmaiņas kontrakts

 

Par servisu datu apmaiņas ziņojuma struktūru varētu kalpot pats WorkLog objekts no datu modeļa. Viss, kas ir nepieciešams, lai norealizētu WCF datu kontraktu, ir jāieslēdz modelim attiecīgā serializācija (projekts: DataModel):

 

Kodā mēs iegūstam klasēm šādus atribūtus:

[Table(Name="dbo.WorkLog")]
[DataContract()]
public partial class WorkLog : INotifyPropertyChanging,
INotifyPropertyChanged
{

 

un katram attiecīgajam property apmēram šādus:

[Column(Storage="_WorkLogId", DbType="UniqueIdentifier NOT NULL", IsPrimaryKey=true)]
[DataMember(Order=1)]
public System.Guid WorkLogId
{

 

NB! Pievēršam uzmanību saitēm (relationship)... piemēram WorkLog klasei laika mērvienību saite (TimeUnitType):

[Association(Name="TimeUnitType_WorkLog", Storage="_TimeUnitType", ThisKey="TimeUnitTypeId", IsForeignKey=true)]
public TimeUnitType TimeUnitType
{

 

Kā redzams, tad šī veida saites netiek apveltītas ar [DataMember()] atribūtu un tas ir jāņēm vērā, ja vēlas darboties ar LINQ ģenerēto kodu WCF kontekstā. Turpretim, ja mēs aplūkojam TimeUnitType klase un papētam tās property, tad tur ir redzams, LINQ to SQL ir ģenerējis generic EntitySet, kas satur visus laika uzskaites ierakstus, kas atbilst konkrētajam laika mērvienības tipam:

[Association(Name="TimeUnitType_WorkLog", Storage="_WorkLogs", OtherKey="TimeUnitTypeId")]
[DataMember(Order=7, EmitDefaultValue=false)]
public EntitySet<WorkLog> WorkLogs
{

 

Ja papēta serializācijas principus nedaudz dziļāk, tad ir skaidrs, kāpēc tiek serializēta saite no master uz child un nevis otrādāk: serializācijas process beidzās sekmīgi, ja visas asociācijas visā objektā (šajā gadījumā WorkLog) ir serializētas. Ja gan master, gan arī child objektā būtu ieslēgta serializācija (ar DataMember atribūtu palīdzību), tad serializācijas laikā vienkārši notiktu cikliskās atkarības un serializācija nenotiktu. Par to var arī pārliecināties Rika Strahla rakstā.

Tomēr, neskatoties uz to, šo uzvedību ir iespējams mainīt pieliekot klāt, piemēram, WorkLog klases TimeUnitType atribūtu:

[Association(Name="TimeUnitType_WorkLog", Storage="_TimeUnitType", ThisKey="TimeUnitTypeId", IsForeignKey=true)]
[DataMember()]
public TimeUnitType TimeUnitType
{

 

un attiecīgi TimeUnitType klasei noņemot:

[Association(Name="TimeUnitType_WorkLog", Storage="_WorkLogs", OtherKey="TimeUnitTypeId")]
//[DataMember(Order=7, EmitDefaultValue=false)]
public EntitySet<WorkLog> WorkLogs
{

 

Šāda veida modifikācijas protams nav atļautas un nedzīvo ilgi, jo nākamreiz kā izkustināsiet kaut ko modelī, kods tiks pārģenerēts pa jaunu. To es uzskatu par nepilnību LINQ to SQL dizainera rīkā nevis pašā tehnoloģijā. Par šo papētīšu sīkāk noteikti, jo daudzi varētu ar šāda veida problēmām saskarties, ja sāks izmantot LINQ kā ikdienas tehnoloģiju darbam ar servisiem un datu bāzēm.

 

Tātad esam tikuši galā ar servisu ziņojumu struktūras izveidi, varam ķerties klāt pie servisa darbības kontrakta izstrādes.

Neko lieki nesarežģījot, mēs servisu veidosim kā parastu CRUD operāciju nodrošinošu servisu, kurā būs pāris papildus operācijas, kas būs nepieciešamas vienumu testiem (projekts: Interfaces).

Tātad servisa darbības interfeiss izskatās sekojoši:

[ServiceContract]
public interface IWorkLogService
{
[OperationContract]
void ClearAll();

[OperationContract]
void Insert (DataModel.WorkLog workLog);

[OperationContract]
void Edit(DataModel.WorkLog workLog);

[OperationContract]
void Delete(DataModel.WorkLog workLog);

[OperationContract]
DataModel.WorkLog Get(Guid workLogId);

[OperationContract]
List<DataModel.WorkLog> GetList (int workItemId);

[OperationContract]
List<DataModel.WorkLog> GetAll();
}

 

Visas metodes ir vairāk vai mazāk pašaprakstošas, atliek tikai piezīmes par pāris no tām:

  • ClearAll: metode tiks izmantota, pirms katra vienumu testu izsaukšanas, lai no datu bāzes iztīrītu kāda cita testa starprezultātus. Idejiski, tas ir jādara katram vienumu testam: "aiz sevis jānotīra", bet drošības pēc, to izdara arī katrs nākamais tests;
  • Get(Guid): tiks izmantota, lai pilnībā iegūtu informāciju par kādu konkrētu laika uzskaites ierakstu (tiks izmantots arī specifiskos vienumu testos);
  • GetList(int): pārsvarā tiks izmantots, lai laika uzskaites kontrolē aizpildītu sarakstu ar laika uzskaites ierakstiem attiecīgajam Work Item;
  • GetAll(): atgriež pilnīgi visus laika uzskaites ierakstus no datu bāzes, nepieciešams pāris vienumu testiem;

 

Tā iemesla dēļ, ka nav iespējams serializēt saites starp child un master objektiem, tika realizēts arī references datu serviss, kas piegādās visus nepieciešamos klasifikatorus.

[ServiceContract]
public interface IReferenceDataProvider
{
[OperationContract]
List<TimeUnitType> GetTimeUnitTypes();

[OperationContract]
TimeUnitType GetDefaultTimeUnitType();

[OperationContract]
List<WorkLogType> GetWorkLogTypes();

[OperationContract]
WorkLogType GetDefaultWorkLogType();
}

 

Tā kā mums tagad ir interfeisi, tad varam ķerties pie to implementācijas (projekts: WebServices):

 

public class WorkLogService : IWorkLogService
{

 

Kā redzams, tad WCF servisa implementācija nav nekas vairāk kā parasta, klase, kas implementē attiecīgo servisa uzvedības kontrakta interfeisu. Viss krāšņums WCF servisiem slēpjās tā konfigurācijā. Lai mēs sekmīgi varētu iedarbināt WCF servisu, konfigurācijai jāizskatās kaut kas ļoti līdzīgs šim:

  <system.serviceModel>
<
diagnostics performanceCounters="Default">
<
messageLogging logMalformedMessages="false" logMessagesAtServiceLevel="false" logMessagesAtTransportLevel="false" />
</
diagnostics>
<
behaviors>
<
serviceBehaviors>
<
behavior name="WebServices.WorkLogServiceBehavior">
<
serviceMetadata httpGetEnabled="true" />
<
serviceDebug includeExceptionDetailInFaults="true" />
</
behavior>
<
behavior name="WebServices.ReferenceDataProviderBehavior">
<
serviceMetadata httpGetEnabled="true" />
<
serviceDebug includeExceptionDetailInFaults="true" />
</
behavior>
</
serviceBehaviors>
</
behaviors>
<
services>
<
service behaviorConfiguration="WebServices.WorkLogServiceBehavior" name="WebServices.WorkLogService">
<
endpoint address="" binding="wsHttpBinding" contract="Interfaces.IWorkLogService">
<
identity>
<
dns value="localhost" />
</
identity>
</
endpoint>
<
endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</
service>
<
service behaviorConfiguration="WebServices.ReferenceDataProviderBehavior" name="WebServices.ReferenceDataProvider">
<
endpoint address="" binding="wsHttpBinding" contract="Interfaces.IReferenceDataProvider">
<
identity>
<
dns value="localhost" />
</
identity>
</
endpoint>
<
endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</
service>
</
services>
</
system.serviceModel>

 

Viss šis stuff beidzot nav jāraksta ar rokām. Tā vietā var izmantot ērtu WCF Configuration Editor, ko var palaist vai nu no studijas pašas vai arī kā stand-alone aplikāciju.

2_6

 

Kopsummā ērti ir sadarīt visu konfigurācija, plus arī papētīt un palasīt papildus iespējas, ko ierugi pirmo reizi varbūt tikai pašā redaktorā.

Btw, noteikti ieteiktu ieslēgt diagnosticējošos rīkus WCF servisā, lai vēlāk atkļūdot būtu pēc iespējas efektīvāk.

2_9

 

Lai virzītos tālāk un sāktu darbu ar vienumu testiem, vispirms ieteiktu pārbaudīt vai serviss vispār strādā (b, pārbaudīt tā konfigurāciju, jo paši servisi nestrādās, jo ir tikai implementēts interfeiss bez biznesa loģika pagaidām). Ja redzat kaut ko tamlīdzīgu, tas nozīmē, ka vismaz servisu konfigurācija ir korekta :)

2_8

 

Lai tālāk varētu rakstīt vienumu testus ir 2 iespējas:

  • no vienumu testu projekta veidot saiti uz WCF servisiem
  • implementēt servisu fasādes objektus un vienumu testos izmantot fasādes objektus

Es balsoju par 2 variantu, jo fasādes uzvedība anyway kaut kur būs jātestē, jo tieši tur atradīsies objekti, kurus intensīvi izmantos pati kontrole.

Tātad vispirms mums ir jāizveido datu pieejas līmeņa projektā reference uz WCF servisiem.

2_10

 

Pēc sekmīgi pievienotas WCF servisu references varam aizpildīt servisa fasādes objekta funkcionalitāti.

public class WorkLogManager : IWorkLogService
{

 

Un patiesībā secināju, ka ērtāk ir par servisa fasādes objektā implemntēt tieši to pašu servisa darbības kontrakta interfeisu viena iemesla pēc: lai fasādē neaizmirst norealizēt pieeju kādai interfeisa operācijai :)

Kods fasādes objektam izskatās apmēram šādi:

public DataModel.WorkLog Get(Guid workLogId)
{
using (WorkLogServiceClient client = new WorkLogServiceClient(new WSHttpBinding(), EndpointAddress))
{
return client.Get (workLogId);
}

Tā kā esam norealizējuši fasādes objektu, tālāk varam sarakstīt visus mums nepieciešamos vienumu testus.

 

Pats vienumu testa kods izskatās šādi:

[TestMethod ()]
public void GetTest()
{
_workLogManager.ClearAll();

DataModel.WorkLog workLog = new DataModel.WorkLog();
Guid guid = Guid.NewGuid();
workLog.WorkLogId = guid;
workLog.WorkLogDateTime = DateTime.Now;
workLog.WorkItemId = 1;
workLog.UserName = "Valdis";
workLog.WorkDone = 2;
workLog.TimeUnitTypeId = 2;
workLog.Description = "desc...";
workLog.WorkLogTypeId = 1;
workLog.WorkLogged = DateTime.Now;

_workLogManager.Insert (workLog);

DataModel.WorkLog foundWorkLog = _workLogManager.Get (guid);

Assert.IsNotNull (foundWorkLog);

Assert.AreEqual (guid, foundWorkLog.WorkLogId);
}

 

Pēc tam, kad ir uzkrastīts vienumu tests attiecīgajai metodei, mēs varam ķerties pie šīs operācijas realizācijas servisā.

public DataModel.WorkLog Get(Guid workLogId)
{
using (WorkLogDataContext context = new WorkLogDataContext(DbConnectionHelper.GetWorkLogDbConnection))
{
DataModel.WorkLog workLog = context.WorkLogs.Single(w => w.WorkLogId == workLogId);
return workLog;
}
}

Jāpiezīmē šiet viena nianse, kuru visticamāk ka varētu arī Jūs kaut kur izmantot. Tas ir DbConnectionHelper.GetWorkLogDbConnection. Šis property mums atgriež TFS servera Work Item Tracking datu bāzes savienojuma datus (teorētiski šeit varēja darīt tā, ka Sql datu bāzes savienojuma datus varētu konfigurēt atsevišķi, bet tad tas nelīmētos smuki kopā ar Tfs arhitektūras bildi, kas bija zīmēta pirmajā daļā, jo teorētiski datu bāzes, kuras izmanto Tfs serveris pats var atrasties uz cita servera un lai nebūtu atsevišķi kaut kas jākonfigurē, tad laika uzskaites sistēma šo informāciju saņem no paša Tfs servera). Tātad savienojuma datu iegūšanas operācija izskatās šāda:

string connectionString = null;
bool blnFoundDB = false;
TeamFoundationServer server = new TeamFoundationServer(LocalServerName);
IRegistration service = server.GetService(typeof(IRegistration)) as IRegistration;

if (service != null)
{
RegistrationEntry[] registrationEntries = service.GetRegistrationEntries("WorkItemTracking");
if (registrationEntries.Length > 0)
{
foreach (RegistrationEntry regEntry in registrationEntries)
{
foreach (Database database in regEntry.Databases)
{
if (database.Name == "WIT DB")
{
connectionString = database.ConnectionString.Replace("@SQLServerName@", database.SQLServerName).Replace("@DatabaseName@", database.DatabaseName);
blnFoundDB = true;
break;
}
}
if (blnFoundDB)
{
break;
}
}
}
}

 

Ieteiktu papētīt TeamFoundationServer klasi, ļoti interesantas lietas var darīt caur to :)

 

Un tā tālāk turpinām rakstīt vienumu testus, katrai servisa fasādes objekta metodei, lai pēc testu palaišanas kopsumā būtu redzama sekojoša bilde :)

 

Tātad esam tikuši galā veiksmīgi ar vienumu testiem mūsu laika uzskaites sistēmai. Un pozitīvais moments šajā visā ir tāds, ka mums ir gatava un galvenais notestēta funkcionalitāte neuzrakstot nevienu koda rindiņu lietotāja interfeisa slānim. Bieži vien ir pavisam otrādāk, tāpēc arī esmu liels vienumu testu fans, jo tas tev dod drošības sajūtu, ka tos vari palaist jebkurā momentā un ja tie izpildās, tad funkcionalitāte nav salausta, un arī ja kāds no testiem neizpildās, problēmu jeb salauzto apgabalu var viegli un ātri identificēt.

 

Kopsavilkums

Tātad šajā nodaļā mēs apskatījām:

  • plānoto projektu fizisko izvietojumu, kā arī uz atkarību diagrammas
  • izveidojām LINQ to SQL klases, kas kalpos mums kā datu piekļūves līmeņa realizācija, gan arī kā datu pārraides ziņojumu struktūras kontraksts (ieslēdzot Unidirectional serializāciju LINQ modelim)
  • izveidojām WCF servisu, kas kalpos kā EndPoint Web Access, Visual Studio kontrolēm un vienumu testiem
  • izveidojām servisu fasādes modeli, kas nodoršina tieši to pašu funkcionalitāti, ko serviss, tikai nodrošina nedaudz centralizētu pieeju pašiem WCF servisiem (piemēram, nav jākonfigurē katram klientam sava konfigurācija pieejas datiem utt.)
  • izveidojām vienumu testus, kuri nosedza 100% servisu fasādes objekta kodu, kas nozīmē, ka mēs esam pārbaudījuši visas operācijas, ko piedāvā laika uzskaites serviss

 

Nākamajā daļā pieslēgsimies pie Web Access un Visual Studio kontrolēm un niansēm, kas jāņem vērā izstrādājot Tfs Custom Work Item Tracking Controls komponentes.

 

Tfs laika uzskaites sērijas:

 

 

Cerams, ka noderēs!

_____________________________________

Published Monday, February 25, 2008 9:24 PM by valdis.iljuconoks

Comments

# ListView, ObjectDataSource un Linq

Šodien, papildinot Tfs laika uzskaites moduli, par kura 3. daļu man ir jāatvainojas, ka nav sanācis

Friday, May 30, 2008 11:57 PM by Valdis Iljuconoks

Leave a Comment

(obligāts) 
(obligāts) 
(brīvizvēles)
(obligāts)