Всем доброго времени суток!
Хотел бы рассказать о тех возможностях тестирования, которые появляются при использовании ORM от DevExpress™ — eXpress Persistent Objects™ (XPO) для разработчиков на .NET.
Во первых — абстрагирование от конкретной СУБД.
Во вторых — отсутствие необходимости вообще в какой-нибудь СУБД на начальном этапе разработки и при тестировании.
Начнем со структуры нашей БД.
Хотел бы рассказать о тех возможностях тестирования, которые появляются при использовании 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.