📜 ⬆️ ⬇️

eXpress Persistent Objects and Testing

Good day to all!
I would like to talk about the testing capabilities that appear when using ORM from DevExpress ™ - eXpress Persistent Objects ™ (XPO) for developers on .NET.

Firstly, abstraction from a specific DBMS.
Secondly, there is no need for any database at all at the initial stage of development and during testing.


Let's start with the structure of our database.
...
using DevExpress.Xpo;

namespace PrimerDlyaHabr {
public class Person : XPObject {
[Indexed( "LastName" , Unique = true )]
public string FirstName;
public string LastName;
public Decimal Wage;
[Association]
public Department Department;
public Person(Session session) : base (session) { }
}

public class Department : XPObject {
[Indexed(Unique = true )]
public string Name;
[Association]
public XPCollection<Person> Staff { get { return GetCollection<Person>( "Staff" ); } }
public Department(Session session) : base (session) { }
}
}


* This source code was highlighted with Source Code Highlighter .

')
We have two classes (tables) representing departments (Department) and workers in them (Person).

Now let's write some logic for them.

...
using DevExpress.Xpo;
using DevExpress.Xpo.DB.Exceptions;
using DevExpress.Data.Filtering;
using DevExpress.Xpo.Metadata;

namespace PrimerDlyaHabr {
class PersonWork {
IDataLayer dataLayer;
// XPO
public PersonWork(IDataLayer dataLayer) {
this .dataLayer = dataLayer;
UpdateSchema();
}

// . .

void UpdateSchema(){
XPClassInfo[] classInfoList = new XPClassInfo[2];
classInfoList[0] = dataLayer.Dictionary.QueryClassInfo( typeof (Person));
classInfoList[1] = dataLayer.Dictionary.QueryClassInfo( typeof (Department));
dataLayer.UpdateSchema( false , classInfoList);
}

public void AddDepartment( string name) {
using (UnitOfWork session = new UnitOfWork(dataLayer)) {
Department dep = new Department(session);
dep.Name = name;
dep.Save();
session.CommitChanges();
}
}
public bool RemoveDepartment( string name){
using (UnitOfWork session = new UnitOfWork(dataLayer)){
Department dep = GetDepartment(session, name);
if (dep == null ) return false ;
session.Delete(dep.Staff);
session.Delete(dep);
session.CommitChanges();
return true ;
}
}

Department GetDepartment(UnitOfWork session, string name) {
return session.FindObject<Department>(CriteriaOperator.Parse( "Name = ?" , name));
}

public int AddPerson( string firstName, string lastName, Decimal wage, string departmentName) {
using (UnitOfWork session = new UnitOfWork(dataLayer)) {
Department dep = GetDepartment(session, departmentName);
if (dep == null ) throw new ArgumentException( string .Format( "Department '{0}' not found" , departmentName));
Person person = new Person(session);
person.FirstName = firstName;
person.LastName = lastName;
person.Wage = wage;
person.Department = dep;
person.Save();
session.CommitChanges();
return person.Oid;
}
}

public bool RemovePerson( int oid) {
using (UnitOfWork session = new UnitOfWork(dataLayer)) {
Person person = session.GetObjectByKey<Person>(oid);
if (person == null ) return false ;
session.Delete(person);
session.CommitChanges();
return true ;
}
}

CriteriaOperator GetSummaryCriteria( string departmentName) {
return string .IsNullOrEmpty(departmentName) ? null : CriteriaOperator.Parse( "Department.Name = ?" , departmentName);
}

public Decimal CalcWageSummary() {
return CalcDeparmentWageSummary( null );
}
public Decimal CalcDeparmentWageSummary( string name) {
using (UnitOfWork session = new UnitOfWork(dataLayer)) {
return (Decimal)session.Evaluate<Person>(CriteriaOperator.Parse( "Sum(Wage)" ), GetSummaryCriteria(name));
}
}

public int GetPersonCount() {
return GetDeparmentPersonCount( null );
}

public int GetDeparmentPersonCount( string name) {
using (UnitOfWork session = new UnitOfWork(dataLayer)) {
return ( int )session.Evaluate<Person>(CriteriaOperator.Parse( "Count()" ), GetSummaryCriteria(name));
}
}
}
}


* This source code was highlighted with Source Code Highlighter .


Now we have the PersonWork class, which performs some actions from the object (adds / deletes departments / people, considers the sum of salaries and the number of employees).
In the constructor, the class gets the IDataLayer interface — the XPO data source interface.

To test this class, we will use the NUnit framework.

...
using System.Data;
using DevExpress.Xpo;
using DevExpress.Xpo.DB;
using NUnit.Framework;

namespace PrimerDlyaHabr {
[TestFixture]
public class PersonTests {
PersonWork personWork;

[SetUp]
public void SetUp() {
//
IDataStore dataStore = new InMemoryDataStore( new DataSet(), AutoCreateOption.DatabaseAndSchema);
//
IDataLayer dataLayer = new SimpleDataLayer(dataStore);
//
personWork = new PersonWork(dataLayer);
}

[Test]
public void Wage() {
personWork.AddDepartment( "Main" );
personWork.AddPerson( "Vasya" , "Pupkin" , 100, "Main" );
personWork.AddPerson( "Petya" , "Vasin" , 150, "Main" );

personWork.AddDepartment( "Additional" );
personWork.AddPerson( "Kostya" , "Kostin" , 90, "Additional" );
personWork.AddPerson( "Katya" , "Morozova" , 90, "Additional" );

Assert.AreEqual(100 + 150 + 90 + 90, personWork.CalcWageSummary());
Assert.AreEqual(100 + 150, personWork.CalcDeparmentWageSummary( "Main" ));
Assert.AreEqual(90 + 90, personWork.CalcDeparmentWageSummary( "Additional" ));
Assert.AreEqual(4, personWork.GetPersonCount());
Assert.AreEqual(2, personWork.GetDeparmentPersonCount( "Main" ));
Assert.AreEqual(2, personWork.GetDeparmentPersonCount( "Additional" ));
}
}
}


* This source code was highlighted with Source Code Highlighter .


At the exit we got one class of tests with one test. To run the test, we do not need to have any DBMS available.
For these purposes, there is a class InMemoryDataStore, which is a data storage in memory and uses the DataSet in its depths.
Accordingly, this DataSet can be used to save information from the repository to the XML.
Our test adds two departments, four employees and checks the work of methods for calculating the amount of salaries and the number of employees.
The tests went well ... The system is ready ...

Now we need to use it in real conditions.
To do this, add a form and in its constructor create an instance personWork.

...
using DevExpress.Xpo.DB;
using DevExpress.Xpo;
...
PersonWork personWork;
public Form1() {
InitializeComponent();
// MSSql Server
string connectionString = MSSqlConnectionProvider.GetConnectionString( "Server" , "Database" );
// MSSql Server
IDataStore provider = XpoDefault.GetConnectionProvider(connectionString, AutoCreateOption.DatabaseAndSchema);
//
IDataLayer dataLayer = new SimpleDataLayer(provider);
//
personWork = new PersonWork(dataLayer);
}
...


* This source code was highlighted with Source Code Highlighter .


// And use the class as intended ...

Those. we got a ready-made business logic class, without thinking about which DBMS it will work on ...
The End.

Source: https://habr.com/ru/post/74009/


All Articles