Мы не владеем тем, чего мы не понимаем RSS 2.0
 Thursday, July 10, 2008

Этим постом я хочу начать серию публикаций о LINQ. Я планирую сделать серию из трех статей, в которых будет раскрыто: новые возможности и кострукции в .NET Framework и в C# в частности, также особое внимание будет уделено query methods и query expressions, я попытаюсь расказать как писать LINQ-запросы для in-memory коллекций (LINQ to Entities), также вкратце сделаю обзор expression trees, и на последок – LINQ to SQL и LINQ to XML.

Но все по-порядку.

В этом посте я хочу сделать обзор новых конструций .NET Framework 3.5 (все примеры, которые будут использованы ниже и далее приводятся на C#). Итак, мы рассмотрим:

  • Anonymous types and Local Type Inference
  • Automatic properties
  • Object and collection initializers
  • Extension methods
  • Partial methods
  • Lambda expressions

Начнем, пожалуй...

Анонимные типы (anonymous types)

Прежде чем приступить к рассмотрению анотнимных типов, давайте познакомимся с таким новым ключевым словом как var. Многим, особенно знакомым с JavaScript, это ключевое слово будет знакомо, но в отличии от JavaScript, где оно описывает late-bound objects, в .NET 3.0 это строго типизированная переменная.

Например:

Как видно из примера, ключевое слово var можно использовать для любого типа данных, но в тоже время получать строгую типизацию данных. Помните, что var – это не boxing, и никакого приведения к object не происходит «за кулисами». Тем более, что для поддержки var в IL не было добавлено никаких новых инструкций, и если посмотреть Reflector'ом на код, то пример, приведенный выше будет выглядеть привычным образом:

CLR никогда «не узнает», что вы использовали var для обьявления локальной переменной. Это всего лишь «синтаксический сахар». Но в то же время без введения var было бы сложно работать с LINQ.

Использование var в свою очередь накладывает ряд ограничений. А именно:

  • т. к. Определение типа var происходит по присвоенному значению, то просто объявить переменную, как var i; а потом ее использовать в коде нельзя;
  • Использовать var можно только внутри метода или get/set блоков свойств (которые, по сути, тоже являются методами);
  • Ключевое слово var не может быть использовано для определения типа возвращаемого значения и типа параметров метода.

 

Но без использования var нельзя бы было использовать и анонимные типы. У нас просто нет выбора, т. к. при использовании анонимных типов мы не знаем имя класса (оно генерируется компилятором автоматически). Но об этом далее.

Итак, мы уже узнали что такое var, какие ограничения он накладывает при использовании. Сейчас давайте рассмотрим еще одну новую возможность .NET Framework 3.0 – анонимные типы (anonymous types).

Пример:

 

Анонимные типы – это удобная возможность C# (VB.NET), которая позволяет программистам кратко описывать inline CLR типы, без необходимости явного определения классов. Анонимные типы очень важны при выполнении и преобразовани запросов в LINQ.

Т. к. при создании объекта, который приведен выше, мы явно не указывали тип, то компилятору «не отстается выбора» как создать этот тип за нас.

Что же происходит при создании анонимного типа. Открыв код Reflector'ом мы не увидим истинной картины, потому что Reflector настолько «умный», что понимает анонимные типы, и отображает их так как они описаны в коде (приведенном выше).

Но переключившись в режим отображения IL кода можно увидеть, что создается новый объект типа <>f__AnonymousType0`3<string, string, int32>.

Компилятор создал для нас строго типизированный объект. Но предугадать имя класса, которое будет сгененированно для анонимного типа практически невозможно, потому что оно зависит от компилятора. Для CLR нет никакой разницы между использованием анонимных типов и явно определенных именованых типов. Анонимные типы, так же как и var, просто синтаксический сахар, позволяющие сократить время на написание кода.

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

Но стоит нам поменять местами свойства, или добавить новое свойство – сразу будет создан новый объект.

Анонимные типы работают для случаев, когда:

  • Когда в классах вам не нужны методы;
  • Когда вам на важно имя сгенерированного класса;
  • Когда вам подходят автоматические имплементации для переопределенных компилятом методов, таких как Equals, GetHashCode и ToString.

Одним из важных моментов, на мой взгляд, является то что поддерживается data binding для анонимных типов. Следовательно, нет ограничения на использование анонимных типов в ASP.NET или Windows Forms. Но об этом я раскажу в следующем посте.

По поводу использования var и анонимных типов было много споров и дискуссий. Наблюдая и делая для себя выводы, хочу сказать: не стоит бояться использовать var или анонимные типы, но в тоже время, хотел бы заметить что наиболее целесообразно использовать их в связке с LINQ. Не стоит терять читаемость кода и тем самым усложнять code review и его поддержу (что может произойти при использовании var, например).

Наример:

Когда переменная var x инициализируется с какой-то функции, и мне, как reviewer'у не известно, что эта функция возвращает, и имя переменной x тоже ни о чем мне не говорит, то сразу же возникает много вопросов.

Автоматические свойства (automatic properties)

Наверное все, кто пишет сейчас на C#, привыкли описывать классы следующим образом:

Как видно из примера, никакой дополнительной логики в get/set нет. Возникает вопрос: а почему бы просто не использовать поля класса, и не использовать свойства. Есть много недостатков в использовании публичных полей класса, вместо свойств, основными из которых являются:

  • Нельзя легко организовать data binding к полям (в отличии от свойств);
  • Сложно будет добавить дополнительную логику и заменить публичные поля на свойства, без перекомпляции сборок, зависящих от класса.

В C# появилась новая возможность определять автоматические свойства, без необходимости создавать приватные поля для свойств. Компилятор их создаст за нас. Используя автоматические свойства, приведенный выше пример можно переписать следующим образом:

Когда компилятор встречает пустое get/set свойство, то он автоматически создаст приватное поле и реализацию для свойства. Давайте посмотрим как это происходит. При генерации приватных полей компилятор добавляет CompilerGeneratedAttribute к их обьявлению и к get/set операторам свойств.

Вот как выглядит наш класс Company с автоматическими свойствами в Reflector'е:

Исходя и этого, CLR не видит разницы между привычным для нас способом описания классов и классов c использованием автоматических свойств.

Важным момент: для обявления атоматического свойства всегда должны присутствовать как get так и set. Нельзя создавать read-only или write-only свойства (упуская get или set).

Из личного опыта, отмечу, что если вам необходимо свойство только на чтение или на запись, то можно определить private уровень доступа к get (или к set) оператору свойства (эта возможность работает для автоматических свойств).

При использовании автоматических свойств есть ограничения: если в будущем вам понадобится добавить логику валидации в get (или set), то прийдется явно реализовать свойство (потому что нет возможности обратится к автоматически сгенерированному полю, относящемуся к этому свойству).

Инициализаторы объектов и коллеций (object and collection initializers)

Когда мы рассматривали анонимные типы, мы уже бегло расмотрели инициализаторы объектов. Давайте познакомимся с ними поближе и также рассмотрим инициализаторы коллекций.

Программирование с использованием .NET сильно зависит от использования свойств объектов, с которыми мы работаем. При создании объекта мы практически всегда инициализируем свойства этого объекта. И наверное вам всегда хотелось сделать это быстрее и короче (всю процедуру инициализации). С появлением инициализаторов объектов это стало возможно. Рассмотрим «класический» пример:

Этот же код можно записать, используя инициализаторы объектов, следующим образом:

Можно заметить, что то же самое можно сделать, используя конструктор класса. Но у нас не всегда есть такой уровень контроля (и не всегда мы можем менять объекты, с которыми мы работаем). В то же время, ничто не мешает нам использовать классы с параметризированными конструкторами вместе с инициализаторами объектов. Например, предположим, что использованный нами для примера класс Company содержит конструктор, принимающий имя компании. Мы можем записать наш пример следующим образом:

Кроме того, инициализаторы объектов позволяют инициализировать вложенные объекты. Расширим наш класс-пример Company, добавив в него свойство Address (типа Address). Тогда,используя инициализаторы объектов, можно инициализировать как свойства Company, так и свойства Address, который является вложенным объектом Company.

Важный момент: запись, приведенная в первом примере, где мы создавали объект а потом инициализоровали его свойства, и второй пример, с использованием инициализатора объекта – они НЕ равны.

Отличие заключается в том, как объект создается компилятором. В первом случае сразу создается объект company, во стором же случае объект сначала создается во временную переменную, а затем присваивается newCompany. Это называется атомарное присвоение (atomic assignment). Присвоение должно читатся справа налево: выполнить правую часть и присвоить левой. Это важно при использовании многопоточности.

Чтобы подробней узнать, о том как работают инициализаторы объектов, советую к прочтению пост Bart De Smet : C# 3.0 Object Initializers Revisited.

Инициализаторы коллекций работают подобно инициализаторам объектов. Рассмотрим пример. Привычным для нас способом добавления объектов в коллекцию (в .NET 2.0), является следующий способ: сначала создается объект коллекции, а потом добавляются элементы коллекции, используя метод Add.

Используя инициализаторы коллекций, этот пример можно записать следующим образом:

Когда компилятор встречает такой синтаксис, он преобразует это в вызовы метода Add коллекции. Никаких новых IL инструкций не было добавлено для поддержки инициализаторов объектов и коллекций. Для CLR оба вызова идентичны.

В предыдущих версиях "Orcas" можно было упускать имя типа (Company в нашем случае) для элементов при инициализации коллекции, но в релизной версии компилятор требует явного указания типа объекта. Это было сделано по довольно простым причинам: в случае, если вы создаете типизированную коллекцию, и указываете интерфейс или абстрактный класс, какое дожно быть правильное поведение компилятора в этом случае?

На этом пожалуй и все об инициализаторах объектов и коллекций. Далее рассмотрим методы-расширения (extension methods).

Методы-расширения или Extension methods

Методы-расширения (назовем их так), или extension methods – это еще одна новая возможность .NET Framework 3.5 для расширения уже существующих публичных контрактов существующих CLR типов.

Порой возникают ситуации, когда нам очень нехватает какого-нибудь метода в существующем типе, и нам приходиться писать своего рода helper'ы. Например, мне всегда нехватало TrimIsNullOrEmpty у String. Сейчас добавить этот метод, используя extension method'ы, не составляет особой сложности.

Итак, рассмотрим пример простого extension method'а.

Обратите внимание, что статический метод первым параметром указывает на объект, который необходимо «расширить». Происходит это с помощью ключевого слова this перед параметром метода. Это ключевое слово указывает компилятору на то, что этот статический метод должен быть добавлен к объекту типа String.

Использовать этот extension method очень просто:

Отличить extension method'ы в IntelliSence можно по значку: .

Методы-расширения могут быть применены как к классам, так и к интерфейсам (interface) или перечислениям (enum). Эта возможнось позволяет расширять IEnumerable<T> интерфейс, обеспечивая тем самым поддержку LINQ.

У extension method'ов есть свои ограничения на использование:

  • Методы-расширения должны быть определены в статическом классе. Кроме того сам метод должен быть маркирован как статический;
  • Первый параметр должен иметь модификатор this и указывать тип, к которому применяется данный метод-расширение;
  • Метод-расширение не будет вызван, если его сигнатура (название, параметры и возвращаемое значение) совпадают с уже определённым в типе, к которому он должен применяться (об этом чуть позже).

Большой набор методов-расширений поставляются вместе с LINQ, и являются частью LINQ. Сейчас мы их рассматривать не будем, приведу только пример использования extension method'ов в LINQ:

Этот LINQ запрос будет транслирован компилятором в вызовы extension method'ов следующим образом:

Новый синтаксис, который вы вероятно заметили: c => c.NumberOfEmployees > 100 называется лямбда-выражением (lambda expression), о котором я раскажу немного позже.

А сейчас немного о extension method resolution, или о том, как компилятор определяет, какой из методов вызвать – метод-расшерение или метод класса, если оба совпадают по имени, возвращаемому и принимаемому значениям. Рассмотрим пример:

Далее, для объектов ClassA, ClassB и ClassC вызовем метод Do с разными параметрами:

В итоге можно получить следующие результаты выполнения этого кода:

Extension method Do(this object element, int id) called.
Extension method Do(this object element, string name) called.
ClassB.Do called
Extension method Do(this object element, string name) called.
ClassC.Do called
ClassC.Do called


Итак, как же работает extension method resolution. Ответ довольно «простой», компилятор выберет метод, который является наиболее «близким». А точнее:

- Сначала ищется строго типизированный метод класса, и если он найден, то используется он;
- Затем комплятор ищет строготипизированный метод-расширение и если он найден, то использует его;
- Далее, компилятор ищет нетипизированный метод класса, и если находит, то он будет вызван, иначе – ищется нетипизированный метод-расширение.
- Если же ни один из вариантов не найден – то произойдет ошибка компиляции.

Отмечу, что если использовать эффективно методы-расширения, то это может значительно улучшить как читаемость кода (особенно при code review) так и уменьшить количество строк кода и багов. НО: если у вас есть возможность изменить код или пронаследовать класс, чтоб добавить новый метод – я бы советовал поступить именно так. Прибегайте к использованию extension method'ов только по крайней необходимости.

Partial methods

Еще одним нововведением в .NET Framework 3.5 являются «частичные методы» (или partial methods). Всем уже знакома концепция частичных классов, которая была добавлена в .NET 2.0. Используя partial классы в Windows Forms и ASP.NET разделяется код, который был сгенерирован, например дизайнером, и пользовательский код. Также удобно выносить какие-нибудь низкоуровневые задачи бизнес-логики в отдельные файлы (объедененные логически одним partial классом).

Примечание: Используя тег <DependentUpon> (а файле проекта) можно низкоуровневые реализации класса визуально сгруппировать под классом с основной реализацией.


В .NET Framework 3.5 существует возможность создавать не только partial классы, но и partial методы. В основном partial методы используются при автоматической генерации кода (например, при использовании дизайнера в LINQ to SQL). Но у них есть свои практические преимущества (при использовании их при разработке библиотек классов).

Начнем с примера:

В примере я определил обе части partial класса в одном файле, но в «реальной жизни» они вероятней всего будут разделены по разным файлам. В первой части я определил метод DoSubtask как partial и упустил реализацию метода (как это обычно делаеться для абстрактных классов или интерфейсов). И использовал метод DoSubtask в методе DoTask(). Используем наш класс Worker и вызовем метод DoTask (а затем посмотрим что же происходит «за кулисами»).

Что же происходит с методом DoSubtask, когда он дожен вызватся, ведь он не реализован. Ответ – ничего. Да, ничего, если посмотреть «внутрь» с помощью Reflector'а или ILDASM'а, то можно увидеть что этого метода в сборке просто не существует. Компилятор, встречая partial метод без реализации не только исключает его из сборки, но и исключает все его вызовы.

Когда нам понадобится реализовать метод DoSubtask достаточно написать partial в классе, и IntelliSence подскажет какие из partial методов не реализованы.

Покажу еще один пример использования partial методов в LINQ (т. к. это не является целью этого поста, то не буду объяснять КАК это сделать, раскажу лишь, как это работает). Итак, когда мы используем LINQ to SQL Classes компонент для создания модели базы, то для добавленных в дизайнере объектов будут созданы дополнительные partial методы, которые можно реализовывать, тем самым расширяя функциональность того или иного объекта или контекста.

Использование partial методов имеет свои ограничения.

Во-первых, partial метод должен всегда быть приватным, если вы попытаетесь добавить модификатор доступа, отличный от приватного, то вы получите ошибку компиляции:

error CS0750: A partial method cannot have access modifiers or the virtual, abstract, override, new, sealed or extern modifiers


В случае, если бы было можно создавать public partial методы, то для внешних контрактов, использующих ваш класс небыло бы никакой гарантии, что partial метод реализован. По этой же причине нельзя создать делегат на partial метод.

Еще одним ограничением является то что, partial методы должны возвращать void. Если попытатся скомпилировать следующий код:

 

То получим следующую ошибку компиляции:

error CS0766: Partial methods must have a void return type


По той же причине partial методы не могут принимать out параметры. Т.к. out параметр должен быть инициализирован при выходе из метода, и если partial метод не будет реализован, то и out параметр не будет инициализирован. Но, не смотря на ограничение с out, параметры передающиеся с помощью ref разрешены (хотя, на самом деле, out и ref – одно и то же, просто компилятор выполняет разные проверки).

error CS0752: A partial method cannot have out parameters


На этом пожалуй и все, что я хотел расказать о partial методах. И у нас осталась одна не раскрытая тема – лямбда-выражения (lambda expressions).

Лямбда-выражения (lambda expressions)

С выходом .NET 2.0 уразработчиков появилась возможность создавать inline-методы, в тех случаях где ожидались делегаты (например в List<T>.Find() можно указать предикат поиска не создавая предикат отдельно).

Лямбда-выражения – это более сжатый функциональный синтаксис для определения анонимных методов. Лямбда-выражения повсеместно используются в LINQ, и предоставляют возможность очень компактного и типобезопасного способа записи методов, для последующей передачи и в качестве аргументов методов. Где-то так..

Но лучше разобратся на примере.

 

В нашем примере c => c.NumberOfEmployees < 100 – это и есть лямбда-выражение:
c – имя параметра,
=> - лямбда оператор,
c.NumberOfEmployess < 100 – это «тело» лямбды.

Самый простой способ понять лямбда-выражения – думайте о них как об анонимных методах. Но у лямбда-выражений есть свои преимущества перед анонимными методами:

  • в лямбда-выражениях тип параметра может быть вычислен, поэтому, при использовании тип параметра можно не указывать;
  • лямбда-выражение в качестве тела может содержать как выражение так и блок операторов, в отличие от анонимных делегатов, тело которых может содержать только блок операторов;
  • лямбда-выражения могут быть преобразованы в деревья выражений (expression trees), о которых я раскажу в следующий раз.

Оператор => всегда следует засписком параметров не стоит путать его с операторами сравнения: >= или <=.


Примеры лямбда-выражений:

 

В примере f1 и f2 – с неявным определением типа (с выводом типа из тела выражения), в то время как f3 и f4 – с явным указанием типа аргумента; f1, f3, f5, f6, f8-f10 – это лямбда-выражения с выражением (expression) в качестве тела, все остальные – с блоком операторов (statement block) в теле лямбда-выражения; f5 и f10 – лямбды с несколькими параметрами.

Приведу еще один пример лямбда-выражения:

 

На этом пожалуй и все. В следующий раз я раскажу о expression trees и о том как строить запросы к коллекциям в памяти используя LINQ.

Happy coding.

P.S.: Вы можете также скачать примеры и презентацию к этой статье.

Thursday, July 10, 2008 1:25:07 PM (FLE Standard Time, UTC+02:00)  #    Comments [2] - Trackback
.NET | LINQ
 Sunday, March 02, 2008

Содержание:

1. Введение
2. Установка
3. Создание тестов с помощью ArtoOfTest WebAii
4. Конфигурирование тестов
5. Первый тест
6. Работаем с AJAX
7. Поиск элементов и автосгенерированные Id элементов в ASP.NET

Введение

Итак, закомимся – WebAii – framework для автоматизированного тестирования web приложений. Сайт разработчиков: http://www.artoftest.com.
Что же предлагает WebAii:

  • Автоматизация с использованием событий DOM, или вызов событий клавитуры/мыши напрямую (клики, перетаскивание и прокрутка, ввод текста).
  • Подежка приложений Ajax и JavaScript Unit-testing.
  • Возможность поиска элементов на странице с возможностью указания «упрощенных» названий элементов для упрощения поиска их на странице.
  • Реализация DOM-модели в .NET, что дает возможность навигации по элементам из кода тестового модуля.
  • Поддержка NUnit и Visual Studio Team Test шаблонов и расширений.
  • Поддержка visual capturing элементов и состояний браузера.
  • А также многие другие полезные возможности, облегчающие процесс тестирования и написания unit-тестов (TestRegions, HTML popups, frames support, etc).
  • Большое количество примеров использования.

Установка

Для того, чтобы скачать WebAii, необходимо зарегистрироватся у них на форуме. Ну что ж, регистрируемся, качаем. Далее устанавливаем:

Установка проходит простыми нажатиями Next в инсталяторе J.
После установки заглянем в папку, в которую мы поставили WebAii.


Подробно о назначении каждой сборки я расказывать не буду. Просто отмечу что главной сборкой является ArtoOfTest.WebAii.dll, которая является ядром test framework, и в которой собраны такие важные классы, как Settings, Manager, Browser, Find и т. д.
Замечу что в это папке находится add-in для FireFox, который тоже необходимо установить. Откройте FireFox и просто перетяните файл webaii@artoftest.com.xpi в окно FireFox, дождитесь пока станет активной кнопка Install Now и нажмите ее, все, add-in готов к использованию:

Создание тестов с помощью ArtoOfTest WebAii.

В этом примере я раскажу как использовать WebAii в Visual Studio Team Test / Team Suite. Использование WebAii совместно с NUnit описано здесь.
Итак, запускаем Visual Studio, и создаем новый проект теста: File -> New -> Project. В дереве: Visual C# -> Test -> Test Project.

После создания проекта, удаляем все созданные автоматически файлы (мы будем использовать темплейты, которые были установлены, при установке WebAii). Для этого нажимаем правой кнопкой на проекте и выбираем Add -> New Item .. а в появившемся окне ищем WebAii VsUnit Test, даем «человеческое» имя файлу теста и жмем Add.

Все, шаблон «готов к употреблению».
Здесь на минутку остановимся и взглянем на базовый класс нашего Unit Test – BaseVsUnitTest. В нем есть несколько полезных, часто используемых свойств: Manager, Settings, Browser, Desktop, Log, Find, которые используются чаще всего, при этом они уже есть (как видим в базовом классе, и нам не прийдется заботится об их создании/инициализации) .

using ArtOfTest.WebAii.Core;
using System;

namespace ArtOfTest.WebAii.TestTemplates
{
   
public abstract class BaseVsUnitTest 
   

       
protected BaseVsUnitTest(); 
        
       
public Actions Actions { get; } 
       
public Browser ActiveBrowser { get; } 
       
public Desktop Desktop { get; } 
       
public Find Find { get; } 
       
public Log Log { get; } 
       
public Manager Manager { get; } 

       
public void CleanUp(); 
       
public static Settings GetSettings(); 
       
public void Initialize(); 
       
public void Initialize(string logLocation); 
       
public void Initialize(TestContextWriteLine vsWriteLineDelegate); 
       
public void Initialize(Settings settings, TestContextWriteLine vsWriteLineDelegate); 
       
public void Initialize(string logLocation, TestContextWriteLine vsWriteLineDelegate); 
       
public void SetTestMethod(object testObject, string testMethodName); 
   
}
}

Конфигурирование тестов

Вместо того, чтобы конфигурировать тест из кода, в WebAii существует возможность конфигурирования с помощью файла конфигурации App.config. Возьмем за основу ранее созданный проект (или скачиваем его здесь) и добавим к нему файл конфигурации. Жмем правой кнопкой мыши на пректе и выбираем Add -> New Item .. а в диалоге выбираем "Application Configuration File" -> жмем Add.

После чего файл конфигурации будет добавлен в проект. Теперь мы можем добавить секцию конфигурации. Открываем App.config файл. Выглядеть он должен где-то так:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
</configuration>

Для начала зарегистрируем конфигурационную секцию WebAii.Settings. Для этого, внутри элемента configuration добаляем новый элемент configSections, в котором регистрируем секцию WebAii.Settings.

<configuration
   
<configSections
       
<section name="WebAii.Settings" type="ArtOfTest.WebAii.Core.SettingsConfigSectionHandler,ArtOfTest.WebAii, Version=1.0.600.0, Culture=neutral, PublicKeyToken=4fd5f65be123776c" /> 
    
</configSections>
</configuration>

А затем добавляем саму конфигурацию (ниже, под элементом /configSections). Всё вместе это должно выглядеть следующим образом:

<?xml version="1.0" encoding="utf-8" ?> 
   
<configuration>
       
<configSections
           
<section name="WebAii.Settings" type="ArtOfTest.WebAii.Core.SettingsConfigSectionHandler,ArtOfTest.WebAii, Version=1.0.600.0, Culture=neutral, PublicKeyToken=4fd5f65be123776c" /> 
       
</configSections
       
<WebAii.Settings 
           
annotateExecution="false" 
            
baseUrl=http://www.testserver.net/ 
           
clientReadyTimeout="50000" 
           
defaultBrowser="InternetExplorer"             
           
enableScriptLogging="false" 
           
enableUILessRequestViewing="false" 
           
executionDelay="0" 
           
executionTimeout="60000" 
           
localWebServer="None" 
           
logLocation="C:\WebAiiLog\" 
           
queryEventLogErrorsOnExit="false" 
           
scriptLoggingPort="8099" 
           
simulatedMouseMoveSpeed="0.5" 
           
webAppPhysicalPath="" />
   
</configuration>

К сожалению, ArtOfTest не поставляет XSD схему для своей секции, следовательно Intellisence отсутствует при редактровании секции WebAii.Settings. Более подробно о свойствах WebAii.Setting можно узнать здесь, я же отмечу наиболее важные/полезные настройки:

  • baseUrl – указывает базовый URL приложения, если эта опция указана, то при использовании методов NavigateTo() надо указывать относительные пути (довольно удобно, если предполагается, что тест должен работать вне зависимости от того, где находится сайт)ю
  • clientReadyTimeout – время ожидания клиента (при тестировании во время разработки на локальном сервере и при прогоне тестов на продакшене, время отклика может оказатся разным).
  • defaultBrowser – Браузер по умолчанию, в котором будет выполнен тест (по умолчанию Internet Explorer).

Первый тест

Пришло время сделать первый тест. Сделаем его очень простым (как делают приложения "Hello, World", когда начинают изучать язык программирования).

Для начала, в метод, помеченный атрибутом [TestInitizalize()] добавим следующий код:

if (Manager.Browsers.Count == 0)

   
Manager.LaunchNewBrowser();
}

Это позволит при запуске каждого теста проверять, существует ли запущенный браузер, и запускать новый, если нет запущенных.
А теперь наш Hello, World.

[TestMethod]
public void FirstTestMethod() 

   
Manager.ActiveBrowser.NavigateTo(http://google.com/); 

    // Insert "Hello, World" to seach field. 
   
HtmlInputText searchField = Find.ByAttributes<HtmlInputText>("name=\"q\""); 
   
searchField.Text = "Hello, World";

    // Find search button and click. 
   
HtmlInputSubmit submit = Find.ByName<HtmlInputSubmit>("btnG"); 
   
submit.Click();

    // Wait till browser gets ready. 
   
Manager.ActiveBrowser.WaitUntilReady(); 
   
HtmlInputText searchField2 = Find.ByAttributes<HtmlInputText>("name=\"q\"");

    Assert.AreEqual(searchField.Text, searchField2.Text);
}

Простой тест, позволяющий проверить, работает ли Google J. Все довольно просто: открываем браузер, открываем страницу поиска Google, ищем поле ввода по имени (name = "q") и кнопку поиска, указываем, что хотим искать "Hello, World", и жмем на кнопку, проверяем, что в поле поиска осталось значение ("Hello, World"), которое мы искали. Пример можно скачать здесь.
А теперь о плохом:
Иногда можно получить следующую ошибку: ArtOfTest.WebAii.Exceptions.ExecuteCommandException
Это по всей видимости проиходит потому, что браузер не готов выполнять посланные команды, в часности, NavigateTo, поэтому, если такое возникает, добавьте в начало теста строку ожидания браузера (пока он не перейдет в состояние ready)
Manager.ActiveBrowser.WaitUntilReady();

Работаем с AJAX

Первое впечатление при попытке подружить WebAii и AJAX было – «не работает». Проверим так ли это. Создадим простое Web-приложение, которое использует ASP.NET AJAX.

Замечание:
В дальнейшем предполагается, что ASP.NET AJAX установлен.
Для этого выбираем File -> Add -> New Web Site. Из предложенных вариантов выбираем ASP.NET AJAX-Enabled Web Site и «поселяем» новый веб-сайт в IIS: выбираем
- Location: HTTP
- Language: Visual C#
- Жмем Browse и создаем приложение/виртуальную директорию, к которой будет находится сайт

Далее, сделаем простой пример с применением AJAX. Это немного выходит за рамки темы даного поста, поэтому чтобы чтобы не делать все описанное ниже, можно просто скачать демо-проект и разместить его в IIS.
Итак, добавим на страницу для начала компонент UpdatePanel (тем самым обеспечив асинхронное обновление части страницы). Внутрь UpdatePanel поместим Label и Button, при клике на последнюю, будем асинхронно обновлять надпись в Label.

Исходный код ASPX (Default.aspx):

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> 
<head runat="server"> 
    <
title>Untitled Page</title
</head>

<body
   
<form id="form1" runat="server"> 
       
<asp:ScriptManager ID="ScriptManager1" runat="server" /> 
           
<div
               
<asp:UpdatePanel runat="server" ID="demoUpdatePanel"> 
                   
<ContentTemplate
                       
<asp:Label runat="server" ID="lblTitle"></asp:Label
                       
<br /> 
                       
<asp:Button runat="server" ID="btnGo" Text="Press to go!" 
                            OnClick="OnGoButtonClick" /> 
                   
</ContentTemplate
               
</asp:UpdatePanel
           
</div
   
</form>
</body>
</html>

Исходный код code-behind (Default.aspx.cs):

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class _Default : System.Web.UI.Page

    protected void Page_Load(object sender, EventArgs e) 
   

   


   
protected void OnGoButtonClick(object sender, EventArgs e) 
   

       
this.lblTitle.Text = "Async GO"
    }
}


Вот такое простое web-приложение получилось. Запускаем, жмем кнопку "Press to go!" и убеждаемся, что приложение работает асинхронно, и окно браузера не перерисовается полностью (а только меняется надпись).

Приложение есть, и оно работает используя ASP.NET AJAX. Пришло время протестировать его. Снова воспользуемся ранее созданным проектом BaseWebAiiProject (в котором мы тестировали Google). Открываем файл FirstTest.cs, находим в нем FirstTestMethod() и под ним добавляем следующий метод:

[TestMethod]
public void TestAjaxApplication()

   
Manager.ActiveBrowser.WaitUntilReady();
}

В этот метод будет добавлен код, которым мы проверим, как изменилось состояние надписи после нажатия на кнопку.
Прежде чем написать сам тест, отмечу, что в WebAii, а точнее у класса Actions, существует метод, с помощью которого можно ждать измения состояния элементов страницы – WaitForEment(FindParam, waitTimeout, invertCondition [optional]).
Пример...

[TestMethod]
public void TestAjaxApplication()

   
Manager.ActiveBrowser.WaitUntilReady(); 
   
Manager.ActiveBrowser.NavigateTo("~/TestAjax/Default.aspx"); 

   
// Check intial state. 
   
HtmlSpan label1 = Find.ById<HtmlSpan>("lblTitle"); 
   
Assert.AreEqual(string.Empty, label1.InnerText);

    // Search for "go" button. 
   
HtmlInputSubmit goButton = Find.ByName<HtmlInputSubmit>("btnGo"); 
   
goButton.Click();

    FindParam seachParam = new FindParam("id=\"lblTitle\"", "innerText=\"\"");
    Manager.ActiveBrowser.Actions.WaitForElement(seachParam, 500, true);

    HtmlSpan label2 = Find.ById<HtmlSpan>("lblTitle"); 
   
string text = label2.InnerText; 
   
Assert.AreEqual("Async GO", text);
}

.. и объяснения: для начала находим элемент span (наш asp:Label), и проверяем его innerText, убеждаемся что он равен «», затем нажимаем кнопку и ждем пока изменится состояние описанное в FindParam. Замечу, что в FindParam можно указывать несколько условий, по которым будет производится поиск. В нашем случае – ищем кнопку с id = "lblTitle" и аттрибутом innerText = "". Учитывайте, что WaitForElement будет «ждать» элемент 500ms (столько, сколько ему «сказали» ждать), и по истечению этого периода времени выполнение теста будет продолжено.

Поиск элементов и автосгенерированные Id элементов в ASP.NET.

Учитывая то, что в ASP.NET идентификаторы элементов создаются автоматически, то прямой поиск по id = "myButton" не удастся, т.к. в HTML этот Id для кнопки ASP.NET будет преобразован, например в такой ID: GridView1$ctl07$myButton. Как же делать поиск в таком случае? К сожалению Find.ById не поддерживает регулярные выражения (что бы в значительной мере облегчило жизнь), но существует такой метод как FindCustom, где можно указывать предикаты поиска.
Пример:

[TestMethod]
public void FindCustomTest()

   
Manager.ActiveBrowser.WaitUntilReady(); 
   
Manager.ActiveBrowser.NavigateTo("~/TestAjax/Default.aspx");

    // Original control ID within DOM is GridView1$ctl02$myButton. 
   
IdSearch searchPredicate = new IdSearch("myButton"); 
   
HtmlInputSubmit myButton = Find.ByCustom<HtmlInputSubmit>(searchPredicate.Search); 
   
Assert.AreEqual("Do", myButton.Value); 
}

Далее, если посмотреть в отладчике, на тест с использованием предиката, то можно увидеть следующее

Таким образом, указывая только часть имени и используя предикаты поиска, можно искать элементы с Id, автосгенерированными ASP.NET. Пример, и класс предиката, можно скачать здесь.

На этом пожалуй и все. Happy coding.

Sunday, March 02, 2008 7:42:04 PM (FLE Standard Time, UTC+02:00)  #    Comments [0] - Trackback
.NET | ASP.NET | Testing
 Monday, February 11, 2008

Недавно увидел довольно интересный add-in: SequenceViz для Reflector'a, с помощью которого можно в рефлекторе проматривать диаграммы переходов.
Скачать его можно с CodePlex.

Для установки плагина необходимо распаковать содежимое архива в папку, куда установлен Reflector. После этого в конфигурационный файл Reflector'a необходимо добавить следующие строчки конфигурации плагина (выделенные красным):

После установки и конфигурирования плагина, необходимо добавить его к списку используемых плагинов Reflector'a. Для этого запустите рефлектор, и из меню выберите View -> Add-Ins. В появившемся окне указываем SequenceViz.dll. После этого перезапустите Reflector.
Вероятней всего, что сразу у вас плагин не заработает, т. к. он использует еще одну библиотеку – GnuPlot. Скачиваем ее и ставим. Если при установке путь был изменен на отличный от C:\Program Files\GnuWin32\bin\pic2plot.exe, то необходимо изменить настройку в Reflector.exe.config - <add key="Pic2PlotPath" value="your-path-here" />.

Также, вероятней всего, что вам понадобится Adobe SVG Viewer, без которого диаграммы просто не отображаются.

А теперь пример. Для начала создадим простой пример:


А теперь посмотрим на пример рефлектором, используя SequenceViz.

Небольшое предупреждение: Не удаляйте файл seq.svg, иначе диаграммы рисоватся небудут J.
Have fun!

Monday, February 11, 2008 2:15:38 PM (FLE Standard Time, UTC+02:00)  #    Comments [0] - Trackback
.NET | Visualization
 Wednesday, January 30, 2008

Набрав в IDE Visual Studio 2005/2008 слово int можно увидеть как int так и Int32 (Int16, Int64).

И тут возникает вопрос (особенно у молодых разработчиков), какая же разница между int и Int32, например.
Скажем так, разницы особо нет никакой, отличие только в том, что int – это alias для System.Int32. Технически в C# разницы нет, потому что int компилятором будет преобразован в Int32.
Например, Рихтер советует использовать именно Int32, т е полные названия типов, а не их алиасы (aliases).


Многие типы FCL имеют методы в имена которых включены имена типов. Например у типа BinaryReader есть методы ReadBoolean ReadInt32 и ReadSingle и т.д. а у типа System.Convert — методы ToBoolean ToInt32 и ToSingle и т.д.

 

Пример:


using System;
namespace ConsoleApplication1
{
   
class Program 
    { 
       
static void Main(string[] args) 
       

            int alias1 = 123; 
           
Int32 alias2 = 345; 
           
Console.WriteLine(alias1); 
           
Console.WriteLine(alias2); 
       

    
}
}

 

Теперь посмотрим на IL это примера:


.method private hidebysig static void Main(string[] args) cil managed

   
.entrypoint 
   
.maxstack
   
.locals init
       
[0] int32 alias1, 
       
[1] int32 alias2) 
    L_0000: ldc.i4.s 0x7b 
   
L_0002: stloc.0 
   
L_0003: ldc.i4 0x159 
   
L_0008: stloc.1 
   
L_0009: ldloc.0 
   
L_000a: call void [mscorlib]System.Console::WriteLine(int32
   
L_000f: ldloc.1 
   
L_0010: call void [mscorlib]System.Console::WriteLine(int32
   
L_0015: ret    
}

 

 

Как видим, обе переменные: alias1 и alias2 являются Int32.
Поэтому, использовать алиасы или полные имена типов – выбор каждого, или же это описано у вас в coding standards, которые приняты при разработке проекта (если таковые имеются).

Примечание:
Компилятор не даст вам создать перечисление (
enum) на основании Int32


P.S.: Лично я использую Int32.

Wednesday, January 30, 2008 2:36:49 PM (FLE Standard Time, UTC+02:00)  #    Comments [0] - Trackback
.NET
 Monday, January 21, 2008

 

Быстрее всего это осуществить с помощью свойства MonthNames y DateTimeFormat.

Ниже приведен пример как это сделать для текущей культуры. Чтобы получить месяцы для какой-то специальной культуры, необходимо создать ее экземпляр

CultureInfo info = new CustureInfo("ru-RU").

Надеюсь, это избавить Вас от необходимости «хардкодить» имена месяцев.

 

using System;
using System.Collections.Generic;
using System.Text;
using System.Globalization;

namespace MonthNamesTest
{
   class Program
   {
     static void Main(string[] args)
     {
       CultureInfo info = CultureInfo.CurrentUICulture;
       foreach (string name in info.DateTimeFormat.MonthNames)
       {
          Console.WriteLine(name);
       }
     }
   }
}

 

Результат:

 

Примечание:
DateTimeFormat.MonthNames возвращает 13(!) месяцев. Последний равен «». Поэтому проверяйте имя месяца с помощью String.IsNullOrEmpty, прежде чем добавить его, например, в DropDownList.

Monday, January 21, 2008 6:52:20 PM (FLE Standard Time, UTC+02:00)  #    Comments [0] - Trackback
.NET | ASP.NET
Navigation
Archive
<March 2010>
SunMonTueWedThuFriSat
28123456
78910111213
14151617181920
21222324252627
28293031123
45678910
Blogroll
Visitors:
 
Locations of visitors to this page
My LinkedIn profile:
 
logo
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2010
FLUID
Sign In
Statistics
Total Posts: 20
This Year: 0
This Month: 0
This Week: 0
Comments: 7