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

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

    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 1
    • 0
      В рунете мало статей по XPO, спасибо большое!

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Интересные публикации