Всем доброго времени суток!
Хотел бы рассказать о тех возможностях тестирования, которые появляются при использовании ORM от DevExpress™ — eXpress Persistent Objects™ (XPO) для разработчиков на .NET.
Во первых — абстрагирование от конкретной СУБД.
Во вторых — отсутствие необходимости вообще в какой-нибудь СУБД на начальном этапе разработки и при тестировании.
Начнем со структуры нашей БД.
У нас имеются два класса (таблицы), представляющие отделы (Department) и работников в них (Person).
Теперь напишем для них немного логики.
Теперь мы имеем класс PersonWork, который выполняет какие-то действия с объекта( добавляет/удаляет отделы/людей, считает сумму зарплат и кол-во сотрудников).
В конструкторе класс получает интерфейс IDataLayer — интерфейс источника данных в XPO.
Для тестирования данного класса будем использовать фрэймворк NUnit.
На выходе получили один класс тестов с одним тестом. Для запуска теста нам не нужно иметь в наличии какой-либо СУБД.
Для этих целей существует класс InMemoryDataStore, который является хранилищем данных в памяти и использует в своих недрах DataSet.
Соответственно этот DataSet можно пользовать для сохранения информации из хранилища в XML'ку.
Наш тест добавляет два отдела, четырех сотрудников и проверяет работу методов подсчета суммы зарплат и кол-ва сотрудников.
Тесты прошли удачно… Система готова…
Теперь нам нужно использовать ее в реальных условиях.
Для этого добавим форму и в ее конструкторе создадим экземпляр personWork.
//И используем класс по назначению…
Т.е. мы получили готовый класс бизнес-логики, не задумываясь на тем, на какой СУБД он будет работать…
The End.
Хотел бы рассказать о тех возможностях тестирования, которые появляются при использовании ORM от DevExpress™ — eXpress Persistent Objects™ (XPO) для разработчиков на .NET.
Во первых — абстрагирование от конкретной СУБД.
Во вторых — отсутствие необходимости вообще в какой-нибудь СУБД на начальном этапе разработки и при тестировании.
Начнем со структуры нашей БД.
...
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.
У нас имеются два класса (таблицы), представляющие отделы (Department) и работников в них (Person).
Теперь напишем для них немного логики.
...
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.
Теперь мы имеем класс PersonWork, который выполняет какие-то действия с объекта( добавляет/удаляет отделы/людей, считает сумму зарплат и кол-во сотрудников).
В конструкторе класс получает интерфейс IDataLayer — интерфейс источника данных в XPO.
Для тестирования данного класса будем использовать фрэймворк NUnit.
...
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.
На выходе получили один класс тестов с одним тестом. Для запуска теста нам не нужно иметь в наличии какой-либо СУБД.
Для этих целей существует класс InMemoryDataStore, который является хранилищем данных в памяти и использует в своих недрах DataSet.
Соответственно этот DataSet можно пользовать для сохранения информации из хранилища в XML'ку.
Наш тест добавляет два отдела, четырех сотрудников и проверяет работу методов подсчета суммы зарплат и кол-ва сотрудников.
Тесты прошли удачно… Система готова…
Теперь нам нужно использовать ее в реальных условиях.
Для этого добавим форму и в ее конструкторе создадим экземпляр 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.
//И используем класс по назначению…
Т.е. мы получили готовый класс бизнес-логики, не задумываясь на тем, на какой СУБД он будет работать…
The End.