Search
Write a publication
Pull to refresh

eXpress Persistent Objects и тестирование приложений

Всем доброго времени суток!
Хотел бы рассказать о тех возможностях тестирования, которые появляются при использовании ORM от DevExpress™ — eXpress Persistent Objects™ (XPO) для разработчиков на .NET.

Во первых — абстрагирование от конкретной СУБД.
Во вторых — отсутствие необходимости вообще в какой-нибудь СУБД на начальном этапе разработки и при тестировании.



Начнем со структуры нашей БД.

...<br>using DevExpress.Xpo;<br><br>namespace PrimerDlyaHabr {<br>  public class Person : XPObject {<br>    [Indexed("LastName", Unique = true)]<br>     public string FirstName;<br>     public string LastName;<br>     public Decimal Wage;<br>     [Association]<br>     public Department Department;<br>     public Person(Session session) : base(session) { }<br>   }<br><br>   public class Department : XPObject {<br>     [Indexed(Unique = true)]<br>     public string Name;<br>     [Association]<br>     public XPCollection<Person> Staff { get { return GetCollection<Person>("Staff"); } }<br>     public Department(Session session) : base(session) { }<br>   }<br>}<br><br>* This source code was highlighted with Source Code Highlighter.

У нас имеются два класса (таблицы), представляющие отделы (Department) и работников в них (Person).

Теперь напишем для них немного логики.

...<br>using DevExpress.Xpo;<br>using DevExpress.Xpo.DB.Exceptions;<br>using DevExpress.Data.Filtering;<br>using DevExpress.Xpo.Metadata;<br><br>namespace PrimerDlyaHabr {<br>   class PersonWork {<br>     IDataLayer dataLayer;<br>     /Получаем в конструктор источник данных XPO<br>     public PersonWork(IDataLayer dataLayer) {<br>       this.dataLayer = dataLayer; <br>       UpdateSchema();<br>     }<br><br>    //Проверка структуры БД. При необходимости ее генерация.    <br>     void UpdateSchema(){<br>       XPClassInfo[] classInfoList = new XPClassInfo[2];<br>       classInfoList[0] = dataLayer.Dictionary.QueryClassInfo(typeof(Person));<br>       classInfoList[1] = dataLayer.Dictionary.QueryClassInfo(typeof(Department));<br>       dataLayer.UpdateSchema(false, classInfoList);<br>     }<br><br>     public void AddDepartment(string name) {<br>       using(UnitOfWork session = new UnitOfWork(dataLayer)) {<br>         Department dep = new Department(session);<br>         dep.Name = name;<br>         dep.Save();<br>         session.CommitChanges();<br>       }<br>     }<br>     public bool RemoveDepartment(string name){<br>       using(UnitOfWork session = new UnitOfWork(dataLayer)){<br>         Department dep = GetDepartment(session, name);<br>         if(dep == null)return false;<br>         session.Delete(dep.Staff);<br>         session.Delete(dep);<br>         session.CommitChanges();<br>         return true;<br>       }<br>     }<br><br>     Department GetDepartment(UnitOfWork session, string name) {<br>       return session.FindObject<Department>(CriteriaOperator.Parse("Name = ?", name));<br>     }<br><br>     public int AddPerson(string firstName, string lastName, Decimal wage, string departmentName) {<br>       using(UnitOfWork session = new UnitOfWork(dataLayer)) {<br>         Department dep = GetDepartment(session, departmentName);<br>         if(dep == null) throw new ArgumentException(string.Format("Department '{0}' not found", departmentName));<br>         Person person = new Person(session);<br>         person.FirstName = firstName;<br>         person.LastName = lastName;<br>         person.Wage = wage;<br>         person.Department = dep;<br>         person.Save();<br>         session.CommitChanges();<br>         return person.Oid;<br>       }<br>     }<br><br>     public bool RemovePerson(int oid) {<br>       using(UnitOfWork session = new UnitOfWork(dataLayer)) {<br>         Person person = session.GetObjectByKey<Person>(oid);<br>         if(person == null) return false;<br>         session.Delete(person);<br>         session.CommitChanges();<br>         return true;<br>       }<br>     }<br><br>     CriteriaOperator GetSummaryCriteria(string departmentName) {<br>       return string.IsNullOrEmpty(departmentName) ? null : CriteriaOperator.Parse("Department.Name = ?", departmentName);<br>     }<br><br>     public Decimal CalcWageSummary() {<br>       return CalcDeparmentWageSummary(null);<br>     }<br>     public Decimal CalcDeparmentWageSummary(string name) {<br>       using(UnitOfWork session = new UnitOfWork(dataLayer)) {<br>         return (Decimal)session.Evaluate<Person>(CriteriaOperator.Parse("Sum(Wage)"), GetSummaryCriteria(name));<br>       }  <br>     }<br><br>     public int GetPersonCount() {<br>       return GetDeparmentPersonCount(null);<br>     }<br>     <br>    public int GetDeparmentPersonCount(string name) {<br>       using(UnitOfWork session = new UnitOfWork(dataLayer)) {<br>         return (int)session.Evaluate<Person>(CriteriaOperator.Parse("Count()"), GetSummaryCriteria(name));<br>       }<br>    }<br>   }<br>}<br><br>* This source code was highlighted with Source Code Highlighter.

Теперь мы имеем класс PersonWork, который выполняет какие-то действия с объекта( добавляет/удаляет отделы/людей, считает сумму зарплат и кол-во сотрудников.
В конструкторе класс получает интерфейс IDataLayer — интерфейс источника данных в XPO.

Для тестирования данного класса будем использовать фрэймворк NUnit.

...<br>using System.Data;<br>using DevExpress.Xpo;<br>using DevExpress.Xpo.DB;<br>using NUnit.Framework;<br><br>namespace PrimerDlyaHabr {<br>   [TestFixture]<br>   public class PersonTests {<br>     PersonWork personWork;<br><br>     [SetUp]<br>     public void SetUp() {<br>      //Создаем хранилище данных в памяти<br>       IDataStore dataStore = new InMemoryDataStore(new DataSet(), AutoCreateOption.DatabaseAndSchema);<br>      //Создаем источник данных  <br>      IDataLayer dataLayer = new SimpleDataLayer(dataStore);    <br>      //Создаем тестируемый класс<br>       personWork = new PersonWork(dataLayer);<br>     }<br><br>     [Test]<br>     public void Wage() {<br>       personWork.AddDepartment("Main");<br>       personWork.AddPerson("Vasya", "Pupkin", 100, "Main");<br>       personWork.AddPerson("Petya", "Vasin", 150, "Main");<br><br>       personWork.AddDepartment("Additional");<br>       personWork.AddPerson("Kostya", "Kostin", 90, "Additional");<br>       personWork.AddPerson("Katya", "Morozova", 90, "Additional");<br><br><br>       Assert.AreEqual(100 + 150 + 90 + 90, personWork.CalcWageSummary());<br>       Assert.AreEqual(100 + 150, personWork.CalcDeparmentWageSummary("Main"));<br>       Assert.AreEqual(90 + 90, personWork.CalcDeparmentWageSummary("Additional"));<br><br>       Assert.AreEqual(4, personWork.GetPersonCount());<br>       Assert.AreEqual(2, personWork.GetDeparmentPersonCount("Main"));<br>       Assert.AreEqual(2, personWork.GetDeparmentPersonCount("Additional"));<br>     }<br>   }<br>}<br><br>* This source code was highlighted with Source Code Highlighter.

На выходе получили один класс тестов с одним тестом. Для запуска теста нам не нужно иметь в наличии какой-либо СУБД. Для этих целей существует класс InMemoryDataStore, который является хранилищем данных в памяти и использует в своих недрах DataSet. Соответственно этот DataSet можно использовать для сохранения информации из хранилища в XML'ку.
Наш тест добавляет два отдела, четырех сотрудников и проверяет работу методов подсчета суммы зарплат и количества сотрудников.
Тесты прошли удачно… Система готова…
Т.е. мы получили готовый класс бизнес-логики, не задумываясь на тем, на какой СУБД он будет работать…
Теперь нам нужно использовать ее в реальных условиях.

Для этого добавим форму и в ее конструкторе создадим экземпляр personWork.

...<br>using DevExpress.Xpo.DB;<br>using DevExpress.Xpo;<br>...<br>   PersonWork personWork;<br>   public Form1() {<br>     InitializeComponent();<br>    //Получаем строку подключения для MSSql Server<br>     string connectionString = MSSqlConnectionProvider.GetConnectionString("Server", "Database");<br>    //Получаем провайдер подключения к MSSql Server<br>     IDataStore provider = XpoDefault.GetConnectionProvider(connectionString, AutoCreateOption.DatabaseAndSchema);<br>    //Создаем источник данных<br>     IDataLayer dataLayer = new SimpleDataLayer(provider);<br>    //Создаем наш класс работы с персоналом<br>     personWork = new PersonWork(dataLayer);<br>   }<br>...<br><br>* This source code was highlighted with Source Code Highlighter.

И используем класс по назначению… The End.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.