HotLog

вторник, 31 августа 2010 г.

Получение данных из Swing UI.

Как известно, SwingWorker предназначен для получения данных в background-потоке и последующей их передаче в EDT-поток.
Часто возникает и обратная задача: находясь в не-EDT потоке получить данные из UI. Типичный пример: отправляем введенные пользователем данные на сервер, происходит ошибка, надо спросить пользователя, что делать дальше: Retry/Ignore.
Для решения этой проблемы есть замечательный класс GuiActionRunner из библиотеки FEST-Swing. Пример использования:



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

суббота, 28 августа 2010 г.

Java Memory Model

Данная статья является пересказом-переводом JSR-133 FAQ

Что такое Memory Model?

Допустим у нас двухпроцессорная система. Один из процессоров изменяет значение какой-то переменной и сохраняет ее в регистр, в целях оптимизации не записывая ее в основную память (время доступа к основной памяти обычно много больше, чем время доступа к регистрам процессора). Второй процессор собирается прочитать значение той же переменной и читает его из основной памяти. Таким образом, первый и второй процессор, работая одновременно с одной и той же переменной, работают с разными значениями.
Memory Model определяет, в каких случаях один поток, читая значение переменной, получит значение, записанное ранее другим потоком.

Почему все так сложно?

Что бы дать максимальную свободу для проведения различных оптимизаций.

В этом примере скорее всего выгоднее переставить инструкции так, что бы работа с одной переменной была последовательна и не прерывалась работой с другой переменной (на 86ой архитектуре такая перестановка позволила бы хранить значение переменной а в регистре процессора, не скидывая его в основную память).

Если с точки зрения потока, выполняющего метод test ничего не изменилось, то другой поток может заметить, что x стало равным 4 раньше, чем переменной b было присвоено значение 3. Для устранения подобных двусмысленностей введены правила очередности, которые описывают, какие из действий в прошлом окажутся видимыми для данного потока в настоящем.

Правила очередности.

Пусть в момент времени T1 было произведено действие Д1, а в некоторый момент времени T2>T1 произведено действие Д2. Если Д1 было, например, запись в переменную, а Д2 - чтение из этой же переменной, то в общем случае не гарантируется, что Д2 прочитает новое значение. То есть не гарантируется, что выполнено отношение "Д2 после Д1". Следующие правила определяют, в каких конкретных случаях такое отношение гарантируется.

  1. В рамках одного потока: Действие Д1 происходит перед действием Д2, если в программе действие Д2 стоит позже, чем Д1. Это означает, что инструкции могут переставляться только так, что бы из программы это определить было невозможно (речь идет только об одном потоке).Пример

    Порядок присвоения значений переменным a и b может быть произвольным, но гарантируется, что при вычислении значения для переменной x все предыдущие значения будут видны (и x будет равен 5).

  2. Освобождение монитора происходит всегда раньше, чем последующий захват того же самого монитора. Это означает следующее: Пусть поток П1 произвел изменения И1, захватил монитор M, произвел изменения И2 и освободил монитор М. Пусть после этого поток П2 захватил тот же самый монитор M. Тогда потоку П2 будут видны все изменения, которые произвел поток П1: как И1 так и И2.

  3. Запись в volatile-переменную всегда происходит раньше, чем последующее чтение из нее. То есть все так же, как и в предыдущем пункте с заменой захвата монитора на чтение volatile-переменной и освобождения монитора - на запись той же самой переменной. Таким образом можно синхронизировать значения переменных, изменяемых из разных потоков без захвата монитора и связанной с этим блокировкой (см. ConcurrentHashMap, например).

  4. Вызов метода start() у потока происходит всегда до того, как произойдут любые действия в новом потоке. Это означает, что на момент старта потока он синхронизирован с тем потоком, которые его стартовал.

  5. Все инструкции в потоке гарантированно происходят до того, как метод join() на этом потоке успешно возвратится. Это означает, что если поток П1 ожидает поток П2 методом П2.join() и join() успешно завершается, то все изменения, произведенные П2, доступны из П1.

  6. Присвоение значений final-полям происходит до того, как объект будет нормально создан. Слово "нормально" здесь означает, что ссылка на объект не "утечет" из конструктора. Пример:

    Здесь ссылка на класс утекает через публикацию ее в статик-поле instance. В промежутке между (1) и (2) до поля instance.a может дотянуться другой поток и увидеть, что а==0, а еще через некоторое время увидеть, что final поле a изменило свое значение на a==5. Что бы этого не произошло, никогда не публикуйте ссылку на еще не созданный объект.

  7. Если final-поле является ссылкой на объект O1, то так же гарантируется, что после нормального завершения конструктора, все поля объекта O1 так же будут видны всем потокам. Таким образом, если final-поле является ссылкой, например, на массив, не нужно беспокоиться, что другие потоки увидят правильно значение ссылки, но неправильно содержимое массива ("правильное" здесь означает "правильное на момент завершения конструктора", а не "последнее присвоенное").

среда, 25 августа 2010 г.

Bean Validation, JSR 303. и Hibernate Validator.

Прослушал лекцию Васильева Михаила "Спецификация на программный интерфейс JSR 303, "Bean Validation" и ее реализация "Hibernate Validator".

Рассказ показался достаточно интересным.

В настоящий момент можно посмотреть в записи
https://academyit.webex.com/academyit/playback.php?FileName=http%3A//academy.it.ru/ru/videosem/Java.wrf

Я еще не сформировал точку зрения на предмет, но постепенно склоняюсь к мысли, что это что-то не то. Аргумент следующий: пока мы ставим стандартные аннотации типа @NotNull, @Size и тому подобные, код остается понятным. Но если мы начинаем писать свои аннотации со своими чекерами, то разбираться в коде, возможно, становится сложнее, чем если бы проверки стояли прямо в сеттерах.

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

Повторюсь, что окончательного мнения у меня еще нет.

вторник, 24 августа 2010 г.

JMock. Делегирование mock-методов объекту.

Общее правило организации тестирования такое: Если объект A использует объект B и объект B уже протестирован, то не надо при тестировании объекта А тестировать объект B. Надо тестировать только соблюдение предусловий по отношению к объекту А.

Допустим, A - это какая-то модель таблицы (реализация TableModel). B - контроллер, который заполняет модель А данными.
Если А протестирована, то остается проверить, что контроллер будет общаться с А только в edt-потоке (см. предыдущую статью).

Раз в поведении модели А мы уверены, то не нужно городить кучу Expectations, а достаточно проверить поток и делегировать выполнение метода в А. Для этого создаем вспомогательный Action:



И вот так им воспользуемся:



Небольшая тонкость: что бы случайно не передать в конструктор B не мок, а оригинал, мы требуем, что бы к моку обратились хотя бы один раз.

понедельник, 23 августа 2010 г.

Проверка SwingThreadPolicy с помощью JMock

Как известно, все операции с Swing компонентами надо осуществлять в особом EDT (Event Dispatcher Thread) потоке. Таким образом, при тестировании GUI-приложения необходимо проверять также и соблюдение Swing Thread Policy.
Обратно, обращение ко всем методам, способным вызвать блокировку, необходимо проводить в потоке, отличном от EDT во избежании подвисания графического интерфейса.

Для примера возьму задачу о получении данных откуда-то (возможно зависание) и передача их в swing-подсистему. Для этого определим два интерфейса:

Для отображения данных в swing-компоненте


И для получения данных откуда-то:



А вот и класс, который надо протестировать:




Два закоментированных поля олицетворяют две различные ошибки, которые должны быть покрыты тестами.

Непосредственно для тестов нам понадобятся два вспомогательных класса:






А вот и сам тест:




Собственно и все. Небольшая тонкость: метод doAll(Action... actions) возвращает значение от последнего Action в списке параметров, так что returnValue должен стоять последним.

JMock и Hamcrest 1.2 под maven.

В hamcrest версии 1.2 появились весьма нужные классы, упрощающие тестирование.
Мне к примеру очень полюбился матчер SamePropertyValuesAs, позволяющий легко и просто тестировать прилетающие в листнер события.

Однако, разработчики hamcrest'a не спешат выкладывать версию 1.2 в центральный мавеновский репозиторий. Как следствие, у jmock в зависимостях стоит hamcrest версии 1.1, что грустно.

Но выход есть!




Мы исключаем hamcrest из транзитивных зависимостей для jmock и добавляем его нужной версии в зависимости нашего проекта.
Не забудьте подключить репозитарий (http://forumarchivebuilder.googlecode.com/svn/repository).