eXpress Persistent Objects и тестирование

DunkanVS 1 ноября 2009 в 22:17 381
Всем доброго времени суток!
Хотел бы рассказать о тех возможностях тестирования, которые появляются при использовании 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.

Проголосовать:
+2
Сохранить: