среда, 26 сентября 2012 г.

Starting with zoneminder

I've been recently considering the way to establish some sort of surveillance system for my holiday house, which is 80 km away from my city of residence and left unattended during cold winter months.

Being a little biased towards open source software I was really excited to find out that there is an open source alternative for CCTV servers, named Zoneminder.

I decided to test it on my laptop.
In ubuntu 12.04 zoneminder package comes with its latest version - 1.25.0-1. Zoneminder requires apache2 server and mysql in order to store and present its data, making it possible for an easy remote http access. So these dependencies got installed along with zoneminder itself.

After the installation apache, mysql and zoneminder services were started without problems. But when attempting to access http://localhost/zm I was getting 404 error. The solution was found in zoneminder wiki and was pretty simple:
$ln -s /usr/share/zoneminder /var/www/zm
this adds link to zoneminder directory inside apache2 contents directory.

After this operation page opens without errors, but... it is blank! Again wiki helps with the solution - set short_open_tag to on in /etc/php5/apache2/php.ini and restart apache. In my case it was already set, so restart was enough:
sudo service apache2 restart

Now zoneminder console was accessible via http://localhost/zm and I proceeded with adding my Venus USB2.0 camera.

I used zoneminder documentation to set values for monitor. Overall it was simple process, the only parameters that brought confusion were 'Device Format' and 'Capture Palette' on Source tab. It took some time to figure out the proper values for my camera, thanks to this article I found that correct combination for me was
Device Format = PAL
Capture Palette = YUYV

But even after setting the values I was unable to see video. Zoneminder log (accessible from main zoneminder window - console) showed me the error:
Failed to open video device /dev/video0: Permission denied

Pretty much obvious, ls -la /dev/video0 showed that device is owned by user root and group video, while zoneminder was trying to access it as user www-data. Thus the solution was to add www-data in group video:
sudo usermod -a -G video www-data

After that another two errors were still residing in logs:
Shared data size conflict in shared_data for monitor Monitor-1, expected 328, got 316
 and
Got unexpected memory map file size 3073192, expected 9217192 

The first error is actually a bug in zoneminder and I solved it thanks to this article, the solution was to patch file /usr/share/perl5/ZoneMinder/Memory.pm at line 130:
-our $arch = int(3.2*length(~0));
+our $arch = 32;
 
For the second error the same guy helped with this article, the solution was to make linux kernel shmmax larger, for me 128MB was sufficient:
sudo sysctl -w kernel.shmmax=536870912

Obviously this value will be reset after reboot so to make it persistent add into /etc/sysctl.conf:
kernel.shmmax=536870912

After solving all the issues I played a little bit with my cheap camera and zoneminder including motion detection and exporting events to download them as zip files. So far everything works great and I find zoneminder to be really easy to use.

вторник, 1 ноября 2011 г.

Spring logging under maven

As we all know, Spring framework uses Commons Logging (sometimes called JCL - Jakarta Commons Logging) for its output. And I bet you are a successful developer who knows how cool SLF4J is. Can you spot the problem already?

OK, I'll ease your agony. How are we going to ask Spring to pollute our log files with its log output, provided we use SLF4J as a facade and, say, Log4J as implementation?

I assume you use maven 2 as build tool. The solution should work for Spring 2 and 3.

So here is the plan. First we use a nice library jcl-over-slf4j, which  implements the public API of JCL but using SLF4J underneath. Then we use another nice library slf4j-log4j12 to tell SLF4J to use Log4j as its underlying logging framework. Apart from these two dependencies we obviously need two more: SLF4J itself (slf4j-api) and Log4J itself (log4j). So we end up with following dependencies config in maven pom:

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  ...
  <dependencies>
    ...
      <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
          <version>1.6.3</version>
      </dependency>
      <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-log4j12</artifactId>
          <version>1.6.3</version>
          <scope>runtime</scope>
      </dependency>
      <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>jcl-over-slf4j</artifactId>
          <version>1.6.3</version>
          <scope>runtime</scope>
      </dependency>
      <dependency>
          <groupId>log4j</groupId>
          <artifactId>log4j</artifactId>
          <version>1.2.16</version>
          <scope>runtime</scope>
      </dependency>
  </dependencies>
</project>

But before celebrating the victory we have another thing left to solve. Removing commons-logging from classpath. Algorithm is following:
  1. make sure commons-logging is not directly specified in your pom as dependency
  2. run mvn dependency:tree and search for commons-logging in the output
  3. if not found goto 6
  4. for the higher-level dependency which brings commons-logging add the following configuration in pom:
    ...
      <dependencies>
          <dependency>
            <groupId>...</groupId>
            <artifactId>...</artifactId>
            <version>...</version>
            <exclusions>
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
          </dependency>
      </dependencies>
    ...
    
  5. goto 2
  6. get some champagne and start celebrating

Now you can comfortably use SLF4J with Spring logging enabled.

Useful links:
http://blog.springsource.com/2009/12/04/logging-dependencies-in-spring/
http://stackoverflow.com/questions/3387441/how-do-i-configure-spring-and-slf4j-so-that-i-can-get-logging

четверг, 13 октября 2011 г.

Обнаружим подводные камни быдлокода Sonar'ом

Все началось с простой задачи - прикрутить к мавен билду отчет о покрытии кода тестами. Погуглил.

Первым всплыла Emma. Плагин для эклипсика поставился быстро и отлично работал - можно было быстро увидеть покрытие кода конкретным тестом. Отличная фича для повседневной работы и быстрой проверки. Но по прежнему оставалась основная задача - узнать покрытие кода всеми тестами в мавен проекте.

И вот с использованием Emma в мавене возник серьезный затык. В распоряжении были три плагина:
У первых двух были подозрительно одинаковые goal-ы, возможно представляют собой одно и то же. Возможности очень узкие, документация беднейшая (для сонатапойвского все что можно было найти - это указанную по ссылке статью, в которой в примерах конфигов xml теги съедены). В итоге все попытки настроить плагины превращаются в битье головой об стену. Про поддержку покрытия межмодульного тестирования и говорить не приходится.
Третий плагин чрезвычайно стар - написан аж для первого мавена. Для написания плагина под второй мавен по словам разработчиков "there is some interest". Ок, вопросов не имеем.

Погоревав немного над унылостью еммо-поддержки в мавене, я смягчил свое сердце по отношению к ранее нагугленной платформе для комплексного анализа кода Sonar.
"Мне всего лишь нужно покрытие кода тестами, зачем ставить целую отдельную платформу?" - думал я раньше.
Но каково же было мое удивление, когда я смог за считанные минуты загрузить сонар, проанализировать с его помощью весь проект, и получить помимо покрытия кода юнит тестами всевозможные указания на проблемные места быдлокода и быдлодизайна.

Такое успешное начало сразу после мучительных часов прикручивания емма мавен плагинов сподвигло меня на дальнейшую настройку сонара. Используя инструкцию отсюда, настроил сонар на свой инстанс постгреса, настроил сонар как сервис линукса - все без проблем.

Далее уже залогинившись в систему сонара под дефолтным акком admin/admin сделал следующее:
  • поменял пароль данного аккаунта на... кхм... на секретное значение (Administrator -> My Profile)
  • выставил в качестве дефолтного профиля оценки качества кода 'Sonar way with Findbugs', так как изначальный профиль 'Sonar way' содержит намного меньшую базу данных косяков (Configuration -> Quality Profiles)
  • ВАЖНО! в сонаре имеется возможность просмотра исходников кода, поэтому если проект закрытый, необходимо настроить обязательный вход под каким-либо из используемых аккаунтов, т.е. запретить анонимусам смотреть что-либо в сонаре (Configuration -> General Settings -> Security -> Force user authentication)
  • поставил няшные плагины: Clirr, JaCoCo, Motion Chart, SIG Maintainability Model, Taglist, Timeline, Total Quality (установить и посмотреть описание всех плагинов можно в Configuration -> Update Center -> Available Plugins) Из них уже успел убедится в полезности JaCoCo и Timeline,первый добавляет поддержку JaCoCo - альтернативного дефолтной Cobertura тула для анализа покрытия кода тестами (о нем чуть ниже), второй позволяет заценить динамику изменения параметров качества кода во времени
  • настроил использование JaCoCo в своем проекте. Выбор JaCoCo был сделан из-за его возможности отражать покрытие как юнит тестов так и интеграционных тестов(читай тестов, использующих межмодульные связи). Пришлось немного повозиться. Настройки делал для мультимодульного проекта, имеющего родительский пом, в каждом модуле имелись юнит тесты, кроме того в отдельных модулях имелись тесты, использующие классы из других модулей.
    1. Сначала в самом сонаре ставим JaCoCo как дефолтный аналитик покрытия (Configuration -> General Settings -> Code Coverage -> Code coverage plugin = jacoco). Можно это не выставлять здесь. Но тогда придется при вызове мавена указывать значение в виде параметра.
    2. Настраиваем surefire плагин в родительском pom.xml чтобы он мог принимать параметры JVM:
      <project>
         ...
         <build>
            ...
            <plugins>
               ...
               <plugin>
                  <groupId>org.apache.maven.plugins</groupId>
                  <artifactId>maven-surefire-plugin</artifactId>
                  <configuration>
                     <argLine>${surefire.jvm.args}</argLine>
                  </configuration>
                  <inherited>true</inherited>
               </plugin>
            </plugins>
         </build>
      </project>
      
      
    3. Теперь начинается сам процесс. Он у нас состоит из двух шагов: прокатка билда с паралельным сбором информации о покрытии тестов и проход сонаром по проекту. Все команды выполняем, находясь в папке родительского мавен проекта.
    4. Шаг первый: билдим проект и собираем инфу о покрытии. Для этого нам необходимо выполнять тесты специальным JaCoCo агентом. Команда принимает следующий вид:
      JACOCO_FILE="jacoco.exec"
      JACOCO_DUMP="target/$JACOCO_FILE"
      JACOCO_AGENT="$MAVEN_REPO/repository/org/jacoco/org.jacoco.agent/0.5.3.201107060350/org.jacoco.agent-0.5.3.201107060350-runtime.jar"
      
      mvn -Dsurefire.jvm.args=-javaagent:$JACOCO_AGENT=destfile=$JACOCO_DUMP clean install
      
      Небольшие пояснения. Параметр surefire.jvm.args мы только что использовали при настройке Surefire, с помощью его значения мы говорим "эй, surefire! используй jacoco агент отсюда, а свои результаты сохраняй вот сюда". В моем примере я использую агент из мавен репозитория, но можно его и отдельно качнуть. Путь к сохраняемому файлу указан относительный благодаря чему для каждого модуля получится свой джакоко файл.
    5. Шаг полуторный. Прежде чем начнем сбор инфы для сонара необходимо сделать одну маленькую вещь - создать дополнительный джакоко файл, содержащий в себе все джакоко файлы модулей. Это позволит нам проанализировать межмодульное покрытие кода тестами. Если быть более точным, то в получившемся файле будет информация о покрытии любым тестом - как локальным юнит тестом так и тестом из другого модуля. Делаем:
      JACOCO_DUMP_IT="/tmp/$JACOCO_FILE"
      cat `find -name $JACOCO_FILE` > $JACOCO_DUMP_IT
      
      Как видно объединение представляет собой банальную конкатенацию.
    6. Ну и теперь второй шаг - вызываем сбор инфы для сонара
      SONAR_URL="http://localhost:9000"
      DB_DRIVER="org.postgresql.Driver"
      DB_URL="jdbc:postgresql://localhost:5432/sonar"
      DB_USER="user"
      DB_PASSWORD="password"
      
      mvn -e sonar:sonar -Dsonar.host.url=$SONAR_URL -Dsonar.jdbc.driver=$DB_DRIVER -Dsonar.jdbc.url=$DB_URL -Dsonar.jdbc.username=$DB_USER -Dsonar.jdbc.password=$DB_PASSWORD -Dsonar.jacoco.itReportPath=$JACOCO_DUMP_IT -Dmaven.test.skip -Dsonar.dynamicAnalysis=reuseReports
      
      Параметр sonar.jacoco.itReportPath - то что и позволяет нам разделить информацию о покрытии юнит тестами и тестами вообще. В своем примере я использую эту возможность не совсем по назначению. Предполагается что у вас имеется отдельный модуль для интеграционных тестов, который собирает все модули воедино и тестит их. Покрытие таких ИТ тестов и проверяется. У меня же интеграционные тесты разбросаны по разным модулям, поэтому мне более полезно будет знать об общем покрытии классов любым тестом неважно из какого модуля. Параметр maven.test.skip позволяет избежать повторного запуска тестов. Значение sonar.dynamicAnalysis=reuseReports указывает сонару не удалять уже имеющиеся после нашего первого шага джакоко файлы.
  • Добавил необходимые виджеты на странице Dashboard ( -> Dashboard -> Configure widgets). Имейте ввиду, что на дашборде ничто само по себе не появляется, после установки какого-либо плагина предполагающего доп. вывод, придется здесь добавить виджет. В частности фича покрытия ИТ тестами от джакоко предполагает добавление виджета.
  • Полазил, посмотрел результаты - ай нрайца! Ай люблю! 
Вот так относительно малой кровью получился полезный тул при разработке.  Можно продолжать накручивать возможности, в частности интегрировать Трак и Сонар, будет выводится какая-то инфа по тикетам. Но мне пока хватит и этого.
Для удобства запуска процесса сбора инфы на проекте советую поместить все команды в скриптец.
Вот весь скрипт целиком:
#! /bin/sh
#
#change parameters to fit your needs
SONAR_URL="http://localhost:9000"
DB_DRIVER="org.postgresql.Driver"
DB_URL="jdbc:postgresql://localhost:5432/sonar"
DB_USER="user"
DB_PASSWORD="password"
JACOCO_FILE="jacoco.exec"
JACOCO_DUMP="target/$JACOCO_FILE"
JACOCO_DUMP_IT="/tmp/$JACOCO_FILE"
MAVEN_REPO="/home/user/.m2"
JACOCO_AGENT="$MAVEN_REPO/repository/org/jacoco/org.jacoco.agent/0.5.3.201107060350/org.jacoco.agent-0.5.3.201107060350-runtime.jar"

mvn -Dsurefire.jvm.args=-javaagent:$JACOCO_AGENT=destfile=$JACOCO_DUMP clean install

cat `find -name $JACOCO_FILE` > $JACOCO_DUMP_IT

mvn -e sonar:sonar -Dsonar.host.url=$SONAR_URL -Dsonar.jdbc.driver=$DB_DRIVER -Dsonar.jdbc.url=$DB_URL -Dsonar.jdbc.username=$DB_USER -Dsonar.jdbc.password=$DB_PASSWORD -Dsonar.jacoco.itReportPath=$JACOCO_DUMP_IT -Dmaven.test.skip -Dsonar.dynamicAnalysis=reuseReports

rm $JACOCO_DUMP_IT

Напоследок ссылочки, из которых была почерпнута информация:
http://docs.codehaus.org/display/SONAR/The+2+minutes+tutorial
http://docs.codehaus.org/display/SONAR/Documentation
http://mojo.codehaus.org/sonar-maven-plugin/sonar-mojo.html
http://www.sonarsource.org/measure-code-coverage-by-integration-tests-with-sonar/
http://docs.codehaus.org/display/SONAR/JaCoCo+Plugin
http://www.eclemma.org/jacoco/trunk/doc/agent.html
http://docs.codehaus.org/display/SONAR/Advanced+parameters

среда, 22 июня 2011 г.

Java i18n - никакого мошенничества, только ловкость рук

В очередной раз старушка джава подкидывает прикол. На этот раз история у нас с интернационализацией, или просто i18n.

Как мы все знаем, при использовании стандартного подхода, тексты сообщений для различных языков размещаются в .properties файлах. Для каждого языка создается файл с соответствующим суффиксом. Кроме того, заботливые разработчики джавы настоятельно рекомендуют в доках:
You should always create a default properties file.
И даже пример приводят, где английский вариант помещен в качестве дефолтного и, соответственно, варианта файла с суффиксом для английского языка нету. Вот, взято оттуда же:

The PropertiesDemo sample program ships with three properties files:

LabelsBundle.properties
LabelsBundle_de.properties
LabelsBundle_fr.properties

Отлично! - подумал я. - Всё понятно, предоставишь язык de - выберется LabelsBundle_de.properties, предоставишь язык en, LabelsBundle_en.properties найден не будет и выберется LabelsBundle.properties!

Переполненный оптимизмом и радостью я сделал свой вариант
Messages.properties
Messages_ru.properties
где в дефолтном файле поместил английские тексты.
Протестировал локально, все замечательно работает. Загрузил на сервак.
Каков же был размер кирпича, когда я увидел, что несмотря на самые отчаяннейшие попытки кликать по ссылке "en" я получал один и тот же - русский вариант перевода.

Расследовав дело я убедился в той свинье, которую подложили мне летающие в облаках разработчики джавы.
Читаем хелп по ResourceBundle.getBundle:

Conceptually, getBundle uses the following strategy for locating and instantiating resource bundles:

getBundle uses the base name, the specified locale, and the default locale (obtained from Locale.getDefault) to generate a sequence of candidate bundle names. If the specified locale's language, country, and variant are all empty strings, then the base name is the only candidate bundle name. Otherwise, the following sequence is generated from the attribute values of the specified locale (language1, country1, and variant1) and of the default locale (language2, country2, and variant2):

* baseName + "_" + language1 + "_" + country1 + "_" + variant1
* baseName + "_" + language1 + "_" + country1
* baseName + "_" + language1
* baseName + "_" + language2 + "_" + country2 + "_" + variant2
* baseName + "_" + language2 + "_" + country2
* baseName + "_" + language2
* baseName

Вот она роковая часть. Вызывая ResourceBundle.getBundle("Messages", new Locale("en")) и ожидая что подхватится безсуффиксный файл заместо отсутствующего английского, я не знал, что разработчики джавы слишком умные и прежде чем юзать безсуффиксный файл ищут вот это:

...the default locale (language2, country2, and variant2)

* baseName + "_" + language2 + "_" + country2 + "_" + variant2
* baseName + "_" + language2 + "_" + country2
* baseName + "_" + language2

проверил что возвращает Locale.getDefault() на локальном томкате - английский, на серверном томкате - оказалось русский. Таким образом благодаря усилиям разработчиков джавы и их умным головам я никаким образом не смог бы заюзать безсуффиксный файл на сервере, хоть укажи ты китайский язык.

Спасибо вам, разработчики джавы. Теперь иду писать обработку передаваемой локали сам. Потому как менять дефолтную локаль томкатовской жвм для того, чтобы правильно работало одно приложение как-то не хочется. Кроме того, могли бы хотя бы в своих доках правильные примеры указывать, потому как нынешние приводят к багам.

вторник, 21 июня 2011 г.

matplotlib - чертим кодом

В гостях у нас новая модная фишка - библиотека питона для отображения 2D графиков и геометрических фигур. Я использовал ее для создания графиков в системе документирования проектов. Достаточно удобно, за исключением того факта, что отсутствуют какие-либо плагины для трака. Эх, как же было бы прекрасно писать в самой вики трака код, нажимать кнопку и получать готовую картинку. Как собственно я делал для графвиза. Но увы, на данный момент приходится сперва с помощью скрипта создавать изображение в каком-нибудь формате PNG, а уж затем впихивать эту картинку в трак. Может взять это за повод разработать плагин для трака?
Кстати раз уж упомянул, в чем же отличие Graphviz от matplotlib? Графвиз, как свидетельствует корень слова, специализируется на графиках. Это высокоуровневый движок предназначенный для отрисовки рафиков, описание которых не указывает форму отрисовки, лишь отношение между графами. Матплотлиб это более низкоуровневый движок, где отрисовка детально описывается кодом, и нарисовать можно все что угодно.

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

1. рисование многоугольников
matplotlib чаще всего используют для графиков функций, наборов данных и тп. Поэтому пример для отрисовки многоугольников по координатам не так-то просто найти. Вот он.

2. мучаем оси координат
По-умолчанию заботливый матплотлиб дарит нам отметки осей координат на всех четырех сторонах изображения. Что же можно с этим сделать...
a) убираем отметки (так называемые ticks) с верхней оси:

ax.xaxis.set_ticks_position('bottom')

где ax - объект типа matplotlib.axes.Axes , соответствующий метод для yaxis здесь.

б) убираем ось полностью

ax.yaxis.set_visible(False)

метод наследуется классом оси от класса matplotlib.artist.Artist

в) устанавливаем границы изображения по осям

plt.axis([0, 7, 0.5, 2.5])


г) указываем границы отметок-ticks и как их подписать

import pylab
...
pylab.xticks( pylab.arange(8), ('', 't1', '', 't2', '', 't1+d1','t2+d2') )

а что же это за модуль такой pylab? Это всего лишь процедурный интерфейс к другим различным модулям, в том числе к matplotlib и numpy. Достаточно посмотреть исходный код pylab чтобы увидеть все бесстыдство сплошных импортов и ни единого кусочка собственного кода. Однако это должно быть удобным. Так или иначе на моей убунточке матплотлиб установка включает в себя pylab, выбора тут нету.

д) указываем, где нужно разместить отметки-ticks (если дефолтные предположения матплотлиб рисуют ненужные тики или не рисуют нужные)

ax.xaxis.set_major_locator(matplotlib.ticker.IndexLocator(1,0))


3. меняем размеры изображения
Размеры картинки при работе с матплотлиб могут пониматься в двух смыслах. В векторном и в растровом. Изначально мы задаем размеры в векторном смысле. Что определяют эти размеры? Они определяют в основном относительное расположение элементом рисунка на координатной оси (при этом некоторые детали сохраняют свои размеры, например текст). Можно сказать что на этом этапе мы просто сжимаем/разжимаем координаты используя категории дюймов, сантиметров и тп. Второй и заключительный этап - преобразование вектора в растр, здесь мы просто указываем DPI (хотя наверное это правильнее было бы назвать pixels per inch) и все что мы наваяли в векторе преобразуется в растр. Вот кусок кода:

defaultSize = fig.get_size_inches() #получаем размер нашего изображения
pylab.savefig("plot01.png", bbox_inches="tight",dpi=75) #сохраняем как png с dpi 75

#уменьшаем изображение по вертикали, при этом текст например сохраняет свои размеры
fig.set_size_inches(defaultSize[0], defaultSize[1] * 0.4)
pylab.savefig("plot02.png", bbox_inches="tight",dpi=75) #вновь сохраняем с теми же dpi


Вот пока и всё. Читаем доки с сайта, смотрим питоновские help(...) и чертим с удовольствием!

пятница, 10 июня 2011 г.

Maven и забота о детях

У меня мультимодульный проект в мавене 2, в котором имеется главный родительский модуль типа pom. Для удобства управления версиями, само указание версии для дочерних модулей было опущено, что, как мы все знаем, приводит к наследования версии родительского модуля.
Однако хардкод версии все же прокрадывается в дочерние модули через секцию <parent>:
<parent>
    <artifactId>parent</artifactId>
    <groupId>com.project</groupId>
    <version>0.1-SNAPSHOT</version>
</parent>
Да-да, именно так. Мы вынуждены указывать версию родителя, которую используем. Возможно кому-то и посчастливиться воспользоваться этой фичей возможности использования разных версий родителя, но это явно не для моего быдло проектика. Я всегда хочу использовать последнюю версию родителя.
И вот мы наконец подошли к проблеме. При обновлении версии в родительском поме, как обновить ссылку на версию родителя во всех дочерних помах?

Добрый товарищ dwh указал на maven-release-plugin и его возможность release:update-versions. Справедливости ради стоит отметить что такое же решение предлагают и на форумах. Но сразу же появились проблемы. Сначала оказалось, что в моем супер поме указана староватая бета-версия релиз плагина, не имеющая указанной возможности. После попытки обновится на более новую релиз плагин начал капризно требовать scm настроек, которые мне совершенно не сдались. В итоге пришлось отказаться от идеи релиз плагина.

Намного лучшим решением оказался плагин versions-maven-plugin из проекта Mojo. Этот плагин и специализируется на работе с версиями (в отличие от релиз плагина, первоочередная задача которого работа с релизами). Добавив его (versions плагин) в plugins секцию родительского пома, а затем вызвав mvn versions:update-parent -DallowSnapshots=true я решил свою проблему! :D Опция allowSnapshots=true позволяет работать в том числе и с snapshot версиями, без этой опции snapshot версии будут игнорироваться.

среда, 23 марта 2011 г.

Private атрибуты - не помеха для JUnit

Правильно ли использовать в юнит-тестах какие-либо private атрибуты (методы или поля) класса? Вопрос философский, сформулировать который можно и так: blackbox vs whitebox testing. В контексте общего понимания вопроса выбор скорее всего будет за blackbox тестированием. Оно красиво, элегантно и соответствует правильным идеям. Однако реальность сложнее и иногда то, что элегантно и красиво тупо не работает.
Если возникла необходимость вызвать какие-либо приват методы тестируемого класса, подходящим решением (не требующим модификации самого класса) является использование reflection, хорошо описанное здесь.
У меня была несколько иная задача. Требовалось вытянуть из класса приватные статик филды. Вот как это было реализовано:
java.lang.reflect.Field field = MyClass.class.getDeclaredField("STATIC_FIELD_NAME");
field.setAccessible(true);
String value = (String) field.get(null);

Все должно быть понятно из самого кода. Единственное - что это за null там такой в методе get. Это означает что мы хотим получить статик филд, если бы был инстанс филд, то вместо null передали бы объект, значение филда которого нам нужно.