<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Олег Елифантьев</title>
	<atom:link href="http://elifantiev.ru/feed/" rel="self" type="application/rss+xml" />
	<link>http://elifantiev.ru</link>
	<description>Записки программиста...</description>
	<lastBuildDate>Tue, 24 Jan 2012 20:12:30 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Android. Выполнение задач в бэкграунде.</title>
		<link>http://elifantiev.ru/android-running-background-task/</link>
		<comments>http://elifantiev.ru/android-running-background-task/#comments</comments>
		<pubDate>Mon, 23 Jan 2012 18:33:45 +0000</pubDate>
		<dc:creator>Olegas</dc:creator>
				<category><![CDATA[IT]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">http://elifantiev.ru/?p=304</guid>
		<description><![CDATA[ На Stackoverflow часто встречаются вопросы по выполнению на Android фоновых задач, в т.ч. и повторяющихся с заданным промежутком времени. Как правило, первое, что используется, это Service.
Такой подход в некоторых случаях может привести к тормозам и низкой скорости ответа пользовательского интерфейса. Расскажу когда так бывает и как с этим бороться&#8230;

Почему может &#171;тормозить&#187; Service
Каждое Android-приложение по-умолчанию [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://elifantiev.ru/wp-content/uploads/2011/05/Task-Scheduler.png"><img class="alignleft size-full wp-image-307" style="margin: 5px;" title="Task-Scheduler" src="http://elifantiev.ru/wp-content/uploads/2011/05/Task-Scheduler.png" alt="" width="129" height="141" /></a> На Stackoverflow часто встречаются вопросы по выполнению на Android фоновых задач, в т.ч. и повторяющихся с заданным промежутком времени. Как правило, первое, что используется, это <a href="http://developer.android.com/reference/android/app/Service.html">Service</a>.</p>
<p>Такой подход в некоторых случаях может привести к тормозам и низкой скорости ответа пользовательского интерфейса. Расскажу когда так бывает и как с этим бороться&#8230;<br />
<span id="more-304"></span></p>
<h3>Почему может &laquo;тормозить&raquo; Service</h3>
<p>Каждое Android-приложение по-умолчанию запускается в отдельном процессе. В каждом процессе запускаются потоки (Thread). По-умолчанию все компоненты приложения (Activity, Service, BroadcastReceiver) запускаются в одном &laquo;main&raquo; потоке (он же UI-thread). Если внутри сервиса запустить, например, долгий сетевой вызов или какую-то тяжелую инициализацию, мы получим тормоза всего приложения, его интерфейса и, скорее всего, предложение сделать Force close&#8230; Впрочем, работа службы в том же потоке, что и остальное приложение, имеет свои плюсы &#8211; у вас есть доступ к элементам интерфейса. Если бы служба работала в другом потоке, доступ к UI у вас бы отсутствовал.</p>
<h3>Что делать?</h3>
<p>Для решения данной проблемы стоит для тяжелых задач использовать отдельный поток. На самом деле его даже не обязательно создавать внутри службы&#8230;</p>
<p>Для запуска задачи в отдельном потоке можно воспользоваться следующими средствами SDK:</p>
<ul>
<li>создать Thread</li>
<li>использовать AsyncTask</li>
</ul>
<h3>Запускаем свой поток</h3>
<p>Поток создать просто&#8230;</p>
<pre class="brush: java">Thread myThread = new Thread(new Runnable() {
    @Override
    pubic void run() {
        doLongAndComplicatedTask();
    }
});

myThread.start(); // запускаем</pre>
<p>Все просто, но проблемы начинаются, когда после выполнения длинного задания нам захочется обновить UI.</p>
<pre class="brush: java">final TextView txtResult = (TextView)findViewById(R.id.txtResult);
Thread myThread = new Thread(new Runnable() {
    @Override
    public void run() {
        txtResult.setText(doLongAndComplicatedTask());
    }
});

myThread.start();</pre>
<p>В результате выполнения получим ошибку. &laquo;Чужой&raquo; поток попытался обратиться к UI! Как вылечить? Надо использовать Handler. Доработаем код&#8230;</p>
<pre class="brush: java">final Handler myHandler = new Handler(); // автоматически привязывается к текущему потоку.
final TextView txtResult = (TextView)findViewById(R.id.txtResult);
Thread myThread = new Thread(new Runnable() {
    final String result = doLongAndComplicatedTask();
    myHandler.post(new Runnable() {  // используя Handler, привязанный к UI-Thread
        @Override
        public void run() {
            txtResult.setText(result);         // выполним установку значения
        }
    });
});

myThread.start();</pre>
<h3>AsyncTask &#8211; все проще</h3>
<p>Для реализации подобных задач в Android SDK имеет встроенное средство &#8211; <a href="http://developer.android.com/reference/android/os/AsyncTask.html">AsyncTask</a>. Данный класс позволяет не думать о том, в каком потоке выполняется ваш код, все происходит автоматически. Рассмотрим пример выше переписанный на AsyncTask.</p>
<pre class="brush: java">class LongAndComplicatedTask extends AsyncTask&lt;Void, Void, String&gt; {

    @Override
    protected String doInBackground(Void... noargs) {
        return doLongAndComplicatedTask();
    }

    @Override
    protected void onPostExecute(String result) {
        txtResult.setText(result);
    }
}

AsyncTask longTask = new LongAndComplicatedTask(); // Создаем экземпляр
longTask.execute(); // запускаем</pre>
<p>Метод doInBackground будет выполнен в отдельном потоке, результат его выполнения будет передан в метод onPostExecute, который, в свою очередь будет выполнен на UI-Thread&#8217;е<br />
Следует помнить, что:</p>
<ul>
<li>AsyncTask может выполняться лишь раз. Для повторного запуска нужно пересоздать класс;</li>
<li>execute() должен быть выполнен на UI-Thread&#8217;е.</li>
</ul>
<p>А что делать, если задачу нужно выполнять регулярно, через определенные промежутки времени&#8230;</p>
<h3>Таймер. Самый простой подход к периодическому запуску.</h3>
<p>Java предоставляет <a href="http://developer.android.com/reference/java/util/Timer.html">Timer</a> для запуска повторяющихся задач. Сделаем так, чтобы AsyncTask из предыдущего примера выполнялся раз в минуту&#8230;</p>
<pre class="brush: java">Timer myTimer = new Timer(); // Создаем таймер
final Handler uiHandler = new Handler();
final TextView txtResult = (TextView)findViewById(R.id.txtResult);
myTimer.schedule(new TimerTask() { // Определяем задачу
    @Override
    public void run() {
        final String result = doLongAndComplicatedTask();
        uiHandler.post(new Runnable() {
            @Override
            public void run() {
                txtResult.setText(result);
            }
        });
    });
}, 0L, 60L * 1000); // интервал - 60000 миллисекунд, 0 миллисекунд до первого запуска.</pre>
<p>Стоит заметить, что все упрощается если &laquo;долгоиграющая&raquo; задача не требует доступ к UI. В этом случае не требуются ни Handler&#8217;ы, ни AsyncTask&#8217;и.</p>
<p>Кстати, у таймера есть еще метод scheduleAtFixedRate(). Различия между ним и schedule() описаны в документации.</p>
<h4>Более гибкий способ. ScheduledThreadPoolExecutor.</h4>
<p>Класс <a href="http://developer.android.com/reference/java/util/concurrent/ScheduledThreadPoolExecutor.html">ScheduledThreadPoolExecutor</a> указан как рекомендуемая альтернатива использованию Timer. Данный класс позволяет организовать пул потоков и планировать выполняемые задачи относительно него. Т.е. класс для организации очереди заданий один, но тем не менее он может выполнять одновременно несколько заданий, если это позволяет имеющееся количество доступных потоков. Более подробно о преимуществах &#8211; <a href="http://developer.android.com/reference/java/util/concurrent/ThreadPoolExecutor.html">в документации</a>.</p>
<p>Для каждого заплаированного задания доступен его &laquo;дескриптор&raquo; &#8211; <a href="http://developer.android.com/reference/java/util/concurrent/ScheduledFuture.html">ScheduledFuture</a> с помощью которого можно, например, отменить выполнения одного конкретного задания не трогая весь остальной пул.</p>
<h3>Задание со звездочкой. Велосипед. Thread, Looper, Handler.</h3>
<p>А еще можно собрать свой <span style="text-decoration: line-through;">велосипед</span> поток с очередью.</p>
<pre class="brush: java">public class LoopingThread extends Thread {
    private CountdownLatch syncLatch = new CountdownLatch(1);
    private Handler handler;

    public LoopingThread() {
        super();
        start();
    } 

    @Override
    public void run() {
        try {
            Looper.prepare();
            handler = new Handler();
            syncLatch.countDown();
            Looper.loop();
        } catch(Exception e) {
            Log.d("LoopingThread", e.getMessage());
        }
    }

    public Handler getHandler() {
        syncLatch.await();
        return handler;
    }
}

Thread loopThread = new LoopingThread(); // будет выполняться вечно
loopThread.getHandler().post(new Runnable() {
    @Override
    public void run() {
        doLongAndComplicatedTask();
    }
});</pre>
<p>Данный поток, будучи единожды запущенным, выполняется вечно. Для общения с ним (отправки заданий) можно использовать метод getHandler() для получения хандлера и дальнейшей отправкой &laquo;событий&raquo; в него. CountdownLatch используется для синхронизации, чтобы поток, желающий получить Handler, не получил его ранее того момента, когда поток-работник запустится и Handler буде создан.</p>
]]></content:encoded>
			<wfw:commentRss>http://elifantiev.ru/android-running-background-task/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Почему Mac?</title>
		<link>http://elifantiev.ru/why-mac/</link>
		<comments>http://elifantiev.ru/why-mac/#comments</comments>
		<pubDate>Sun, 22 Jan 2012 19:54:25 +0000</pubDate>
		<dc:creator>Olegas</dc:creator>
				<category><![CDATA[IT]]></category>
		<category><![CDATA[Mac]]></category>

		<guid isPermaLink="false">http://elifantiev.ru/?p=381</guid>
		<description><![CDATA[Когда я выбирал для себя модель будущего ноутбука я советовался с друзьями и знакомыми. Многие, услышав, что я выбрал ноутбук стоимостью порядка 40к и узнав что это не Mac искренне удивлялись &#8211; &#171;За такие деньги и не Mac? Если есть столько денег &#8211; бери только Mac&#187;. Никто толком не мог объяснить почему. Далее &#171;ну, это [...]]]></description>
			<content:encoded><![CDATA[<p>Когда я выбирал для себя модель будущего ноутбука я советовался с друзьями и знакомыми. Многие, услышав, что я выбрал ноутбук стоимостью порядка 40к и узнав что это не Mac искренне удивлялись &#8211; &laquo;За такие деньги и не Mac? Если есть столько денег &#8211; бери только Mac&raquo;. Никто толком не мог объяснить почему. Далее &laquo;ну, это же круто&raquo; обычно дело не шло.</p>
<p>За время пользования я обнаружил для себя некоторые субъективные достоинства но есть один объективно важный для разработчика плюс. На маке все есть. Покупая Mac в нем уже есть из коробки</p>
<ul>
<li>Python</li>
<li>Perl</li>
<li>PHP</li>
<li>Ruby</li>
<li>&#8230; ну и еще много всего</li>
</ul>
<p>К чему бы все это богатство нужно если вы, например, PHP-программист, веб-разработчик которому это все барахло совсем даже поровну, что есть что нет&#8230; Все дело в сторонних инструментах. Возьмем например <a href="http://sass-lang.com/">SASS</a>. Написан он на Ruby. И вот на винде тебе нужно пойти, найти и скачать инстяллятор, накатить это все, потом уже накатить  SASS&#8230; На Mac ты просто пишешь в консоли <strong>gem install sass</strong> и начинаешь пробовать.</p>
<p>Та же песня со всякими вещами типа Node.JS и NPM к нему. На винде нужно уметь плясать с правильной моделью бубна чтобы все это зажило (до 6 версии по крайней мере это было так). Здесь ты просто выполняешь не сильно сложные инструкции с сайти или качаешь готовый дистр и уже можно начинать экспериментировать с новым инструментом.</p>
]]></content:encoded>
			<wfw:commentRss>http://elifantiev.ru/why-mac/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Непонятная проблема при отдаче статического контента&#8230;</title>
		<link>http://elifantiev.ru/static-content-delivery-problem/</link>
		<comments>http://elifantiev.ru/static-content-delivery-problem/#comments</comments>
		<pubDate>Sat, 19 Nov 2011 19:18:16 +0000</pubDate>
		<dc:creator>Olegas</dc:creator>
				<category><![CDATA[Без рубрики]]></category>

		<guid isPermaLink="false">http://elifantiev.ru/?p=370</guid>
		<description><![CDATA[Необычную проблемы обнаружили на работе&#8230;
Есть сайт &#8211; статика + ISAPI-расширение. Статика на 90% состоит из XML и JS. Ее много.
Вот диаграмма загрузки кучи статики в Firefox. Как видно &#8211; все в порядке.

А теперь то же самое в IE8.

На обоих изображених подчеркнуты запросы с одинаковым размером (чтобы было проще ориентироваться). Порядок разный потому что вызывают их [...]]]></description>
			<content:encoded><![CDATA[<p>Необычную проблемы обнаружили на работе&#8230;</p>
<p>Есть сайт &#8211; статика + ISAPI-расширение. Статика на 90% состоит из XML и JS. Ее много.</p>
<p>Вот диаграмма загрузки кучи статики в Firefox. Как видно &#8211; все в порядке.<br />
<a href="http://elifantiev.ru/wp-content/uploads/2011/11/http_analyzer_FF-1.jpg"><img class="size-medium wp-image-372 alignnone" title="Диаграмма загрузки Firefox" src="http://elifantiev.ru/wp-content/uploads/2011/11/http_analyzer_FF-1-300x300.jpg" alt="Диаграмма загрузки Firefox" width="300" height="300" /></a></p>
<p>А теперь то же самое в IE8.</p>
<p><a href="http://elifantiev.ru/wp-content/uploads/2011/11/http_analyzer_IE.jpg"><img class="size-medium wp-image-373 alignnone" title="Диаграмма загрузки IE" src="http://elifantiev.ru/wp-content/uploads/2011/11/http_analyzer_IE-298x300.jpg" alt="Диаграмма загрузки IE" width="298" height="300" /></a></p>
<p>На обоих изображених подчеркнуты запросы с одинаковым размером (чтобы было проще ориентироваться). Порядок разный потому что вызывают их различне асинхронные процессы и это (порядок) для работы приложения не важен.</p>
<p>Видно 3 проблемных места. В точке 1 запросы начали подтормаживать (темно зеленый &#8211; время отдачи контента, по размеру видно что отдавать там нечего). Следующая пачка параллельных запросов (точка 2) зависает еще сильнее (до 5 сек.), и снова по размеру понятно что не на чем там столько сидеть. Следующая проблемная точка (3) висит уже не на отдаче, а на приеме сервером (?, в легенде это называется &laquo;send first to last&raquo;) пакета данных (POST) и снова по размеру понятно что отправлять там целых 1.5 сек. совершенно нечего&#8230;</p>
<p>На сервере IIS 7.5, замеры проводились на одной и той же клиентской машине в одно и то же время. Проблема не зависит от клиентской машины и проявляется на других маинах в сети аналогичным образом. В Event log&#8217;е винды пусто.</p>
]]></content:encoded>
			<wfw:commentRss>http://elifantiev.ru/static-content-delivery-problem/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Мошеннические Android-приложения&#8230;</title>
		<link>http://elifantiev.ru/fraud-android-apps/</link>
		<comments>http://elifantiev.ru/fraud-android-apps/#comments</comments>
		<pubDate>Mon, 26 Sep 2011 21:29:28 +0000</pubDate>
		<dc:creator>Olegas</dc:creator>
				<category><![CDATA[IT]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">http://elifantiev.ru/?p=346</guid>
		<description><![CDATA[Поскольку я являюсь разработчиком Android-приложения, то слежу за появлением новых ссылок на него с помощью подписок Google. Сегодня утром я с радостью обнаружил в ящике письмо, в котором присутствовала новая ссылка на мое приложение. Все отлично! И отзыв положительный, и количество загрузок отличное от 0 вот только версия почему-то старая и&#8230; размер APK целых 3 [...]]]></description>
			<content:encoded><![CDATA[<p>Поскольку я являюсь разработчиком <a href="http://hypocampo.ru/">Android-приложения</a>, то слежу за появлением новых ссылок на него с помощью подписок Google. Сегодня утром я с радостью обнаружил в ящике письмо, в котором присутствовала <a rel="nofollow" href="http://androides-os.com/programm/322-hypocampo-geoplanirovschik.html">новая ссылка</a> на мое приложение. Все отлично! И отзыв положительный, и количество загрузок отличное от 0 вот только версия почему-то старая и&#8230; размер APK целых 3 мегабайта против оригинальных ~200К</p>
<p>После загрузки и распаковки APK (вспоминаем что APK это просто ZIP-архив) в нем обнаружилась &laquo;набивка&raquo; из 153 PNG файлов (переименованных зачем-то в .temp) со <a href="http://lurkmore.ru/%D0%A1%D0%B2%D0%B8%D0%B4%D0%B5%D1%82%D0%B5%D0%BB%D1%8C_%D0%B8%D0%B7_%D0%A4%D1%80%D1%8F%D0%B7%D0%B8%D0%BD%D0%BE">свидетелем из Фрязино</a> и собственно &laquo;приложение&raquo;&#8230;</p>
<p>Как и следовало ожидать, от оригинального приложения там ничего не осталось. Ни иконки, ни, тем более, кода. А при дальнейшем рассмотрении (похоже что) ВСЕ файлы на <strong>androides-os.com</strong> &#8211; одна и та же программа! Я скачал несколько разных программ из разных разделов и все они были одного размера и с одинаковым содержимым!</p>
<p>Внутри оказалось нечто, которое по беглому анализу ресурсов оказалось приложением, запрашивающим активацию путем отправки SMS. Попробуем разобраться, куда и что отправляется&#8230;</p>
<p><span id="more-346"></span></p>
<p>Чистый Java-код нам из APK не получить, но можно получить набор инструкций VM Dalvik, который вполне читается глазами без дополнительного софтового обеспечения. С помощью утилиты dexdump из Android SDK сдампим набор инструкций Dalvik в файл&#8230;</p>
<pre>dexdump -d -f -h classes.dex &gt; dump.dump</pre>
<p>Поскольку предполагается, что приложение отправляет SMS, поищем в дампе строку &laquo;<strong>sendTextMessage</strong>&raquo; &#8211; это функция SDK, предназначенная для отправки текстовых сообщений. Поиск оказывается успешным, находится одно вхождение в методе <strong>activate()</strong></p>
<p>Вкратце основные детали метода (получено путем ручной &laquo;декомпиляции&raquo; dalvik-инструкций):</p>
<pre class="brush: java">        // private HashMap&lt;String, ActivationScheme&gt; activationSchemes;
        // private static String CURRENT_ACTIVATION_SCHEME = "1";

        ActivationScheme o = activationSchemes.get(CURRENT_ACTIVATION_SCHEME);

        ArrayList&lt;Pair&lt;String, String&gt;&gt; l = o.list;
        // first - номер куда слать
        // second - что слать?

        for(Pair o2: l) {
            StringBuilder b;
            String sec = String.valueOf(o2.second); // WTF???
            b = new StringBuilder(sec);
            b.append("+");
            String s3 = schemes.get("2"); // private HashMap&lt;String, String&gt; schemes;
            b.append(s3);
            String result = b.toString(); // "pair.second"+schemes[2]
            String frst = o2.first; // v1
            // mgr == TelephonyManager
            mgr.sendTextMessage(frst, null, result, /* PendingIntent.getBroadcast(...) */, null);
        }</pre>
<p>Т.е. имеется некий набор &laquo;схем активации&raquo; состоящих из списка пар строк, одна из которых номер на который слать СМС, а другая &#8211; что собственно слать.<br />
Попробуем найти схемы активации&#8230; в дампе имеется метод <strong>initActivationSchemes</strong> где можно найти вот такой код:</p>
<pre class="brush: java">        ArrayList al = new ArrayList();
        if("250".equals(currentMCC)) { // MCC == Mobile Country Code

            ArrayList&lt;Pair&lt;String, String&gt;&gt; aP = new ArrayList&lt;Pair&lt;String, String&gt;&gt;(); 

            Pair&lt;String, String&gt; p = new Pair&lt;String, String&gt;("4129", "bb031");
            aP.add(p);

            p = new Pair&lt;String, String&gt;("4129", "bb031");
            aP.add(p);

            ActivationScheme sc = new ActivationScheme(aP);
            activationScheme.put(CURRENT_ACTIVATION_SCHEME, sc);
        }</pre>
<p>Т.е. для России (<a href="http://ru.wikipedia.org/wiki/Mobile_Country_Code">MCC == 250</a>) создается активационная схема из отправки двух СМС на номер 4129&#8230; Метод наряду с MCC 250 имеет и другие коды стран, и, соответственно, схемы активации для них. Для каждой страны подобрали определенный платный номер.</p>
<p>Тут есть некоторая непонятка. Несмотря на то, что 250 &#8211; Россия, номер 4129 (по крайней мере по информации которую я смог найти) пробивается как Украинский. Возможно я допустил ошибку при &laquo;декомпиляции&raquo; и случайно &laquo;записал&raquo; в Российский блок коды для Украины&#8230;</p>
<p>Собственно как выглядит приложение&#8230;<br />
Install &#8211; это оно:</p>
<p><a href="http://elifantiev.ru/wp-content/uploads/2011/09/install-1.png"><img class="size-thumbnail wp-image-354 alignnone" title="Install - это оно." src="http://elifantiev.ru/wp-content/uploads/2011/09/install-1-150x150.png" alt="" width="150" height="150" /></a><br />
При запуске все очень незамысловато:</p>
<p><a href="http://elifantiev.ru/wp-content/uploads/2011/09/install-2.png"><img class="alignnone size-thumbnail wp-image-355" title="При запуске все незамысловато..." src="http://elifantiev.ru/wp-content/uploads/2011/09/install-2-150x150.png" alt="" width="150" height="150" /></a><br />
Есть даже &laquo;оферта&raquo;:</p>
<p><a href="http://elifantiev.ru/wp-content/uploads/2011/09/install-3.png"><img class="alignnone size-thumbnail wp-image-356" title="&quot;Оферта&quot;" src="http://elifantiev.ru/wp-content/uploads/2011/09/install-3-150x150.png" alt="" width="150" height="150" /></a><br />
Такая же &laquo;оферта&raquo; судя по всему доступна вот тут: <a rel="nofollow" href="http://depositfmobi.ru/ofert">http://depositfmobi.ru/ofert</a><br />
На сайт вышел по названию package приложения: com.depositmobi.</p>
<p>Что интересно, это depositmobi и <a rel="nofollow" href="http://depositmobi.com/search.php?p=q&amp;s=236&amp;q=orange%20anna%20fgshah">&laquo;QIP.Файлы&raquo;</a> и <a rel="nofollow" href="http://depositmobi.ru/search.php?p=r&amp;s=278&amp;q=Asphalt%206%3A%20Adrenaline%20HD">&laquo;Rapidshare&raquo;</a> одновременно!</p>
<p>Что в итоге? Очень просто способ мошенничества. Делаем сайт, прокачиваем для него SEO по нужным ключевым словам, накачиваем туда тематических новостей и заполняем его простейшей программой отправки СМС на платные номера снабдив ее &laquo;офертой&raquo; чтобы прикрыть задницу&#8230; Можно ли что-то сделать с такими товарищами законными путями?</p>
]]></content:encoded>
			<wfw:commentRss>http://elifantiev.ru/fraud-android-apps/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Посетил YaC 2011</title>
		<link>http://elifantiev.ru/yac-2011/</link>
		<comments>http://elifantiev.ru/yac-2011/#comments</comments>
		<pubDate>Tue, 20 Sep 2011 21:05:08 +0000</pubDate>
		<dc:creator>Olegas</dc:creator>
				<category><![CDATA[Без рубрики]]></category>

		<guid isPermaLink="false">http://elifantiev.ru/?p=342</guid>
		<description><![CDATA[Посетил YaC 2011 (Yet another Сonference) от Яндекс. Это была вторая чисто техническая конференция, на которой мне посчастливилось поприсутствовать. Первая &#8211; ADD (Application Developer Days) 2010 в Ярославле. Далее кратко по пунктам&#8230;

Это было супер офигенски круто. Организация, доклады, атмосфера &#8211; все на высоком уровне. Спасибо оргам. Робот Марвин свободно ездящий по территории &#8211; это 5. Мешки, [...]]]></description>
			<content:encoded><![CDATA[<p>Посетил YaC 2011 (Yet another Сonference) от Яндекс. Это была вторая чисто техническая конференция, на которой мне посчастливилось поприсутствовать. Первая &#8211; ADD (Application Developer Days) 2010 в Ярославле. Далее кратко по пунктам&#8230;</p>
<ul>
<li>Это было супер офигенски круто. Организация, доклады, атмосфера &#8211; все на высоком уровне. Спасибо оргам. <a href="http://twitter.com/#!/search/%23htc%20%23yac2011">Робот Марвин</a> свободно ездящий по территории &#8211; это 5. Мешки, юла-стулья, тематические мероприятия (&laquo;Настрой Ngninx за 10 минут&raquo;) и стенды &#8211; просто супер.</li>
<li>Доклад Алексея Воинова про экзотические языки &#8211; замечателен, смело меняем название на &laquo;Зачем программисту ходить на технические конференции&raquo; и вкладываем в уши всем работодателям, кто еще сопротивляется отпускать своих сотрудников, мотивируя практической бесполезностью происходящего.</li>
<li>Роботы, тестирующие веб-сервисы от Артема Ерошенко &#8211; супер, отличная идея методики автоматизированного тестирования верстки.</li>
<li>Познакомился с Дмитрием <a href="http://twitter.com/#!/dr_zhest">@dr_zhest</a> Жестилевским. Отлично выпили пива и пообщались в кабаке после мероприятия =)</li>
<li>Из грусти: WiFi &#8211; его качество немного скрашивало позитив. Если на его отсутствие во время докладов можно было смело забить, то отсутствие его на мастер-классах по BEM сделало участие в них крайне сложным. Возможно помогли бы шнурки или отдельная запароленная сеть с выдачей доступов на самом мастер-классе. Впрочем, как правильно заметил bobuk на круглом-столе &#8211; WiFi всегда проблема на подобных мероприятиях.</li>
<li>И все же, то, как Яндекс в BEM использует NodeJS, натолкнуло на определенные практические мысли относительно текущего рабочего проекта. Profit!</li>
</ul>
<p>Еще раз Спасибо всем организаторам и докладчикам за офигенское мероприятие. Ждем YaC 2012!</p>
]]></content:encoded>
			<wfw:commentRss>http://elifantiev.ru/yac-2011/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Делаем простейший сборщик ошибок для Android</title>
		<link>http://elifantiev.ru/simple-android-error-reporter/</link>
		<comments>http://elifantiev.ru/simple-android-error-reporter/#comments</comments>
		<pubDate>Mon, 11 Jul 2011 19:50:14 +0000</pubDate>
		<dc:creator>Olegas</dc:creator>
				<category><![CDATA[IT]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">http://elifantiev.ru/?p=330</guid>
		<description><![CDATA[При разработке приложения неизбежно приходится сталкиваться с ошибками в коде и/или окружении. И очень печально когда подобные ошибки встречаются не на тестовом телефоне/эмуляторе а у живых пользователей. Еще печальнее если это не ваш друг бета-тестер и толком никто не может объяснить что и где свалилось.
Обычно при внезапном падении приложения Android предлагает отправить отчет об ошибке, [...]]]></description>
			<content:encoded><![CDATA[<p>При разработке приложения неизбежно приходится сталкиваться с ошибками в коде и/или окружении. И очень печально когда подобные ошибки встречаются не на тестовом телефоне/эмуляторе а у живых пользователей. Еще печальнее если это не ваш друг бета-тестер и толком никто не может объяснить что и где свалилось.</p>
<p>Обычно при внезапном падении приложения Android предлагает отправить отчет об ошибке, где будет и подробный стэк-трейс и информация о версии вашего приложения. К сожалению пользователи не всегда нажимают кнопку &laquo;отправить отчет&raquo; а для дебаг-приложений или приложений не из маркета такая функциональность и вовсе недоступна.</p>
<p>Что же делать? На помощь приедет возможность языка Java обрабатывать исключения (Exceptions), в том числе и непойманные (unhandled).</p>
<p><span id="more-330"></span></p>
<p>Класс Thread имеет статический метод <a href="http://developer.android.com/reference/java/lang/Thread.html#setDefaultUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler)">setDefaultUncaughtExceptionHandler</a>. Данный метод позволяет установить собственный класс-обработчик непойманных исключений. Класс-обработчик должен имплементировать интерфейс <a href="http://developer.android.com/reference/java/lang/Thread.UncaughtExceptionHandler.html">Thread.UncaughtExceptionHandler</a>. Каркас обработчика может выглядеть примерно так:</p>
<pre class="brush: java">public class TryMe implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread thread, Throwable throwable) {
        Log.d("TryMe", "Something wrong happened!");
    }
}</pre>
<p>Единственный метод принимает на вход Thread &#8211; поток, в котором произошло исключение, и Throwable &#8211; само исключение. Приведенная выше реализация просто выводит в лог сообщение без каких либо деталей&#8230; Попробуем воспользоваться&#8230;</p>
<pre class="brush: java">public class MainActivity extends MapActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        Thread.setDefaultUncaughtExceptionHandler(new TryMe());

        Integer a=1;
        if(true)
            a=null;
        int x = 6;
        x=x/a;  // Exception here!
    }
}</pre>
<p>После запуска вышеприведенного кода мы (ура!) получим сообщение в логе&#8230; и черный экран. Установив наш собственный обработчик мы удалил штатный обработчик ОС Android и теперь нам больше не предлагают закрыть приложение.</p>
<p>Исправим положение</p>
<pre class="brush: java">public class TryMe implements Thread.UncaughtExceptionHandler {

    Thread.UncaughtExceptionHandler oldHandler;

    public TryMe() {
        oldHandler = Thread.getDefaultUncaughtExceptionHandler(); // сохраним ранее установленный обработчик
    }

    @Override
    public void uncaughtException(Thread thread, Throwable throwable) {
        Log.d("TryMe", "Something wrong happened!");
        if(oldHandler != null) // если есть ранее установленный...
            oldHandler.uncaughtException(thread, throwable); // ...вызовем его
    }
}</pre>
<p>Теперь мы видим и сообщение в логе, и привычное системное сообщение.</p>
<p>Неудобно устанавливать обработчик в Activity. Хоть он и будет установлен а все потоки, но Activity может быть несколько и несколько же стартовых. А еще могут быть сервисы&#8230; В этом случае лучше всего устанавливать обработчик при инициализации приложения. Примерно вот так:</p>
<pre class="brush: java">public class MyApplication extends Application {
    @Override
    public void onCreate() {
        Thread.setDefaultUncaughtExceptionHandler(new TryMe());
        super.onCreate();
    }
}</pre>
<p>При этом нужно не забыть прописать новый класс приложения в манифест. Примерно вот так:</p>
<pre class="brush: xml">&lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="my.package"&gt;
    &lt;application
        android:name="MyApplication" ...</pre>
<p>Теперь при старте приложения (не важно какого его компонента) будет установлен обработчик исключений.</p>
<p>Конечно выводить сообщение в лог это не серьезно. Нужно собирать больше информации. Какая версия приложения? Какое исключение не обработано? Какое другое исключение привело к выбросу фатального? В каком потоке? Какой был стэк? Всю эту информацию можно получить. Код простейшего обработчика исключений получающий и сохраняющий на SD-карту всю вышеуказанную информацию размещен на <a href="https://github.com/Olegas/RoboErrorReporter">GitHub</a>.</p>
<p>Приведенная реализация сохраняет информацию об необработанном исключении в файл на SD-карте в папку /Android/data/your.app.package.name/files/ (<a href="http://developer.android.com/guide/topics/data/data-storage.html#filesExternal">так велит Dev Guide</a>) в файлах вида stacktrace-dd-MM-yy.txt. Для работы в манифесте приложения требуется разрешение WRITE_EXTERNAL_STORAGE.</p>
<p>Естественно это не единственное подобное решение.</p>
<p><a href="http://www.flurry.com/">Flurry </a>- аналитика для мобильных приложений, содержит свой обработчик ошибок. <a href="http://code.google.com/p/acra/">ACRA </a>- библиотека для Android, собирает данные об ошибках и постит их на GoogleDocs. <a href="http://code.google.com/p/android-remote-stacktrace/">Android-remote-stacktrace</a> &#8211; аналогичная библиотека, шлет данные на пользовательский скрипт-приемник. Также много полезного можно получить <a href="http://stackoverflow.com/questions/601503/how-do-i-obtain-crash-data-from-my-android-application">в этом вопросе</a> на StackOverflow</p>
]]></content:encoded>
			<wfw:commentRss>http://elifantiev.ru/simple-android-error-reporter/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Занятный баг с TabHost в Android 2.1</title>
		<link>http://elifantiev.ru/android-2-1-tabhost-bu/</link>
		<comments>http://elifantiev.ru/android-2-1-tabhost-bu/#comments</comments>
		<pubDate>Wed, 04 May 2011 13:24:36 +0000</pubDate>
		<dc:creator>Olegas</dc:creator>
				<category><![CDATA[IT]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">http://elifantiev.ru/?p=299</guid>
		<description><![CDATA[Обнаружили с коллегой занятный баг в Android, связанный с работой TabHost.
Суть в следующем. Если после onCreate в вашей TabActivity внутри TabHost не будет создано ни одной корректной вкладки все хозяйство с треском осыпается. Причем стэк очень интересный. По нему сложно выявить причину ибо в нем нет ничего, кроме кода ядра ОС.
Выглядит примерно так

Exception: java.lang.NullPointerException
Message: null
Stacktrace:
 [...]]]></description>
			<content:encoded><![CDATA[<p>Обнаружили с коллегой занятный баг в Android, связанный с работой TabHost.</p>
<p>Суть в следующем. Если после onCreate в вашей TabActivity внутри TabHost не будет создано ни одной корректной вкладки все хозяйство с треском осыпается. Причем стэк очень интересный. По нему сложно выявить причину ибо в нем нет ничего, кроме кода ядра ОС.</p>
<p>Выглядит примерно так</p>
<pre class="brush: plain">
Exception: java.lang.NullPointerException
Message: null
Stacktrace:
        android.widget.TabWidget.dispatchDraw(TabWidget.java:241)
        android.view.ViewGroup.drawChild(ViewGroup.java:1565)
        android.view.ViewGroup.dispatchDraw(ViewGroup.java:1294)
        android.view.ViewGroup.drawChild(ViewGroup.java:1565)
        android.view.ViewGroup.dispatchDraw(ViewGroup.java:1294)
        android.view.ViewGroup.drawChild(ViewGroup.java:1565)
        android.view.ViewGroup.dispatchDraw(ViewGroup.java:1294)
        android.view.View.draw(View.java:6588)
        android.widget.FrameLayout.draw(FrameLayout.java:383)
        android.view.ViewGroup.drawChild(ViewGroup.java:1567)
        android.view.ViewGroup.dispatchDraw(ViewGroup.java:1294)
        android.view.View.draw(View.java:6588)
        android.widget.FrameLayout.draw(FrameLayout.java:383)
        com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:1869)
        android.view.ViewRoot.draw(ViewRoot.java:1384)
        android.view.ViewRoot.performTraversals(ViewRoot.java:1149)
        android.view.ViewRoot.handleMessage(ViewRoot.java:1668)
        android.os.Handler.dispatchMessage(Handler.java:130)
        android.os.Looper.loop(Looper.java:154)
        android.app.ActivityThread.main(ActivityThread.java:4416)
        java.lang.reflect.Method.invokeNative(Native Method)
        java.lang.reflect.Method.invoke(Method.java:552)
        com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:910)
        com.android.internal.os.ZygoteInit.main(ZygoteInit.java:668)
        dalvik.system.NativeStart.main(Native Method)
</pre>
<p>Лечится, очевидно, корректной подготовкой табов в onCreate. Что интересно, в 2.2 такого бага уже нет.</p>
]]></content:encoded>
			<wfw:commentRss>http://elifantiev.ru/android-2-1-tabhost-bu/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Интеграционное тестирование web-приложения с Selenium WebDriver</title>
		<link>http://elifantiev.ru/selenium-webdriver-integration-testing/</link>
		<comments>http://elifantiev.ru/selenium-webdriver-integration-testing/#comments</comments>
		<pubDate>Thu, 17 Feb 2011 11:26:08 +0000</pubDate>
		<dc:creator>Olegas</dc:creator>
				<category><![CDATA[IT]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[JUnit]]></category>
		<category><![CDATA[Selenium]]></category>
		<category><![CDATA[WebDriver]]></category>

		<guid isPermaLink="false">http://elifantiev.ru/?p=261</guid>
		<description><![CDATA[Интеграционное тестирование (в отличие от Unit- или модульного тестирования) это тестирование не отдельных атомарных компонентов системы (классов) а результата их взаимодействия между собой в какой-либо среде.
Волею судеб я занимаюсь разработкой своего рода интерфейсного фреймворка заточенного на определенные корпоративные нужды. Среда исполнения фреймворка &#8211; браузер, а по сему язык &#8211; JavaScript.
О том, как можно Unit-тестировать JavaScript [...]]]></description>
			<content:encoded><![CDATA[<p>Интеграционное тестирование (в отличие от Unit- или модульного тестирования) это тестирование не отдельных атомарных компонентов системы (классов) а результата их взаимодействия между собой в какой-либо среде.</p>
<p>Волею судеб я занимаюсь разработкой своего рода интерфейсного фреймворка заточенного на определенные корпоративные нужды. Среда исполнения фреймворка &#8211; браузер, а по сему язык &#8211; JavaScript.</p>
<p>О том, как можно Unit-тестировать JavaScript я <a href="http://elifantiev.ru/js-unit-testing-and-codecoverag/">писал ранее</a>, сейчас же расскажу о процессе интеграционного тестирования, применяемого в команде.</p>
<h3>Selenium</h3>
<p>С давних времен известен инструмент тестирования веб-приложений/страниц в браузере &#8211; <a href="http://seleniumhq.org/">Selenium</a>. В плане его применения есть два основных пути, а именно:</p>
<ol>
<li>написание TestSuite в SeleniumIDE и прогон их через SeleniumTestRunner, или</li>
<li>использование WebDriver</li>
</ol>
<p>WebDriver это новая &laquo;фишка&raquo; Selenium, появившаяся во второй ветке продукта. Основная его суть &#8211; можно гонять тесты, описанные в коде (C#, Java), в разных браузерах и/или в виртуальной среде исполнения.</p>
<h4>WebDriver</h4>
<p><a href="http://code.google.com/p/selenium/">Selenium WebDriver</a> это набор &laquo;биндингов&raquo; к разным языкам (C#, Java), позволяющий отдавать различные команды &laquo;подчиненному&raquo; браузеру.</p>
<p>Для каждого браузера имеется своя реализация WebDriver (FireFoxDriver, InternetExplorerDriver, ChromeDriver &#8211; сейчас включены в поставку, OperaSoftware <a href="http://habrahabr.ru/company/opera/blog/113458/">разработали OperaDriver</a>). Существует также &laquo;виртуальный&raquo; HtmlUnitDriver. В отличии от &laquo;браузерных&raquo; реализаций он не требует установленного браузера и за счет этого работает быстрее и платформонезависим, но есть и минусы &#8211; HtmlUnitDriver имеет &laquo;свою&raquo; реализацию JavaScript и потому поведение &laquo;богатых&raquo; веб-приложений может в нем отличаться. Для своих задач мы используем &laquo;браузерные&raquo; реализации, это позволяет проверить приложение именно в той среде, в которой оно будет исполняться впоследствии.</p>
<p>Кратко общая суть работы с WebDriver может быть описана так:</p>
<ul>
<li>реализуется код, использующий какую-либо имплементацию WebDriver. Данный код выполняет какие-либо действия с веб-страницей и сравнивает результат с эталонным</li>
<li>WebDriver транслирует команды в запущенный браузер (при использовании &laquo;браузерной&raquo; реализации) и сообщает результаты &laquo;обратно в код&raquo;</li>
</ul>
<h4>Что умеет WebDriver</h4>
<p>Ниже рассматривается &laquo;браузерная&raquo; реализация, суть расширение класса RemoteWebDriver (реализует интерфейс WebDriver).</p>
<ul>
<li>поиск элементов: findElement(s)By*
<ul>
<li>CssSelector</li>
<li>ClassName</li>
<li>Id</li>
<li>LinkText</li>
<li>TagName</li>
<li>XPath</li>
</ul>
</li>
<li>загрузка страницы, получение контента страницы</li>
<li>исполнение произвольного JavaScript</li>
<li>осуществление операций Drag-n-Drop</li>
</ul>
<p>C &laquo;найденными&raquo; элементами (интерфейс WebElement)</p>
<ul>
<li>получение текста (text)</li>
<li>получение значения (value)</li>
<li>click по элементу</li>
<li>ввод с клавиатуры (клавиша, сочетание клавиш, последовательность клавиш/сочетаний)</li>
</ul>
<h3>Среда исполнения тестов</h3>
<p>В качестве языка для написания тестов была выбрана Java. Среда для исполнения &#8211; JUnit4.</p>
<p>Базовый абстрактный класс веб-тестов.</p>
<pre class="brush: java">@Ignore
abstract public class AbstractWebTest {

    protected static RemoteWebDriver _driver;
    // расположение тестовой страницы
    private String testPageLocation =
                String.format(
                        "http://%s:%s/test.html",
                        System.getProperty("test.httproot"),         // Web-сервер ...
                        System.getProperty("test.httpport", "80")   // и порт
                );
    // Используемая имплементация WebDriver
    private static String driverName =
                System.getProperty(
                        "test.driver",
                        "org.openqa.selenium.firefox.FirefoxDriver");

    /**
     * Перед каждым набором тестов - создаем инстанс драйвера.
     * Это автоматически запустит браузер
     */
    @BeforeClass
    public static void setUpDriver()
                throws ClassNotFoundException,
                          IllegalAccessException,
                          InstantiationException {
        _driver = (RemoteWebDriver) Class.forName(driverName).newInstance();
    }

    /**
     * Перед каждым тестом - открываем тестовую страницу
     */
    @Before
    public void setUp() {
        _driver.get(testPageLocation);
    }

    /**
     * После каждого набора тестов - закрываем инстанс дарйвера (закрываем браузер)
     */
    @AfterClass
    public static void tearDown() {
        _driver.close();
    }
}</pre>
<p>Конкретный класс с набором тестов (для простоты убраны некоторые проверки, например на то, что элемент по CSS-селектору действительно найден и доступен на странице)</p>
<pre class="brush: java">public class TestMoneyField extends AbstractWebTest {

    /**
     * При рендеринге поле ввода денежной суммы должно показать 0.00
     */
    @Test
    public void testRendering() {
        WebElement content =
           _driver.findElementByCssSelector("#FieldMoney .input-text-field");
        Assert.assertEquals("0.00", content.getValue());
    }

    /**
     * Проверим форматирование "триад"
     */
    @Test
    public void testInputWithoutDot() {

        WebElement content =
           _driver.findElementByCssSelector("#FieldMoney .input-text-field");
        content.sendKeys("999999");
        Assert.assertEquals("999 999.00", content.getValue());
    }
}</pre>
<p>Все тесты запускаются с помощью отдельного таска Ant-билда:</p>
<pre class="brush: xml">&lt;target name="integrationtest" depends="init, buildtests, deploytests"&gt;
   &lt;junit haltonfailure="false"&gt;
      &lt;sysproperty key="test.driver" value="org.openqa.selenium.firefox.FirefoxDriver" /&gt;
      &lt;classpath&gt;
         &lt;pathelement location="${path.to.tests.jar}"/&gt;
      &lt;/classpath&gt;
      &lt;batchtest&gt;
         &lt;fileset dir="${path.to.compiled.test.classes}"&gt;
            &lt;include name="**/tests/Test*.class" /&gt;
         &lt;/fileset&gt;
      &lt;/batchtest&gt;
   &lt;/junit&gt;

   &lt;junit haltonfailure="false"&gt;
      &lt;sysproperty key="test.driver" value="org.openqa.selenium.ie.InternetExplorerDriver" /&gt;
      &lt;classpath&gt;
         &lt;pathelement location="${path.to.tests.jar}"/&gt;
      &lt;/classpath&gt;
      &lt;batchtest&gt;
         &lt;fileset dir="${path.to.compiled.test.classes}"&gt;
            &lt;include name="**/tests/Test*.class" /&gt;
         &lt;/fileset&gt;
      &lt;/batchtest&gt;
   &lt;/junit&gt;
&lt;/target&gt;</pre>
<p>Данный таск прогонит все известные тесты из классов, чьи имена начинаются с Test под браузерами Firefox и InternetExplorer. В зависимостях таски с базовой инициализацией, компиляцией и выгрузкой скомпилированных тестов на тестовую площадку.</p>
<h3>Фишки-плюшки</h3>
<p>Некоторые &laquo;браузерные&raquo; реализации (Firefox, Opera, Chrome) поддерживают снятие скриншотов. Это может быть полезно дабы зафиксировать визуальное состояние, в котором пребывала тестовая страница в момент, когда тест не прошел. Для этого подойдет функционал JUnit4 &#8211; TestWatchman.</p>
<pre class="brush: java">@Ignore
abstract public class WISbisTest {

    // Папка для скриншотов
    private String screenshotDir =
            System.getProperty("test.screenshotDir", "");

    @Rule
    public MethodRule watchman = new TestWatchman() {

        /**
         * Будет вызван при каждом "проваленном" тесте
         * @param e Брошенное тестом исключение
         * @param method Тест-метод
         */
        @Override
        public void failed(Throwable e, FrameworkMethod method) {
            if(_driver instanceof TakesScreenshot &amp;&amp; !screenshotDir.equals("")) {
                String browserName = _driver.getClass().getName();
                String testSuiteName = method.getMethod().getDeclaringClass().getName();
                browserName =
                        browserName.substring(browserName.lastIndexOf('.') + 1);
                testSuiteName =
                        testSuiteName.substring(testSuiteName.lastIndexOf('.') + 1);

                byte[] screenshot =
                        ((TakesScreenshot)_driver).getScreenshotAs(OutputType.BYTES);
                try {
                    FileOutputStream stream =
                            new FileOutputStream(
                                    new File(
                                        String.format("%s/screenshot_%s_%s_%s.png",
                                                screenshotDir,
                                                browserName,
                                                testSuiteName,
                                                method.getName())));
                    stream.write(screenshot);
                    stream.close();
                } catch (IOException e1) {
                    e1.printStackTrace(System.out);
                }
            }
        }
    };
    // все остальное...

}</pre>
<p>Добавим переменную с путем к папке со скриншотами в Ant-билд</p>
<pre class="brush: xml">   &lt;junit haltonfailure="false"&gt;
      &lt;sysproperty key="test.driver" value="org.openqa.selenium.firefox.FirefoxDriver" /&gt;
      &lt;sysproperty key="test.screenshotDir" value="${screenshotsDir}" /&gt;
      &lt;classpath&gt;
         &lt;pathelement location="${path.to.tests.jar}"/&gt;
      &lt;/classpath&gt;
      &lt;batchtest&gt;
         &lt;fileset dir="${path.to.compiled.test.classes}"&gt;
            &lt;include name="**/tests/Test*.class" /&gt;
         &lt;/fileset&gt;
      &lt;/batchtest&gt;
   &lt;/junit&gt;</pre>
<h3>Интеграция</h3>
<p>В текущей реализации Ant-билд гоняется через Jetbrains TeamCity. Запуск билда настроен на сброс кода в SVN. Интеграционные тесты &#8211; часть общей процедуры тестирования. При провале любого из интеграционных тестов снимается скриншот и публикуется как &laquo;артефакт&raquo; билда &#8211; можно видеть не только какие тесты &laquo;отъехали&raquo; после сброса в транк какого-либо функционала, но и увидеть &laquo;как&raquo; они &laquo;отъехали&raquo;.</p>
<p>В настоящее время используется тестирование под IE и Firefox, Chrome не подключен по причине некоторых трудностей с интеграцией (судя по всему в ChromeDriver присутствуют некоторые ошибки, не позволяющие нормально искать элементы на странице в некоторых случаях &#8211; по состоянию на 2.0b1, сейчас доступна 2.0b2 но работу с ней пока не пробовали)</p>
]]></content:encoded>
			<wfw:commentRss>http://elifantiev.ru/selenium-webdriver-integration-testing/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>JavaScript. Юнит-тестирование и CodeCoverage</title>
		<link>http://elifantiev.ru/js-unit-testing-and-codecoverag/</link>
		<comments>http://elifantiev.ru/js-unit-testing-and-codecoverag/#comments</comments>
		<pubDate>Wed, 06 Oct 2010 18:58:31 +0000</pubDate>
		<dc:creator>Olegas</dc:creator>
				<category><![CDATA[IT]]></category>
		<category><![CDATA[CodeCoverage]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Unit-test]]></category>

		<guid isPermaLink="false">http://elifantiev.ru/?p=190</guid>
		<description><![CDATA[В этой заметке расскажу о своем опыте юнит-тестирования JS-кода, опыте использования среды выполнения тестов js-test-driver, ее возможности code coverage и скручивании ежа с ужом, а именно данных о code coverage от js-test-driver и генератора отчетов о покрытии PHP_CodeCоverage. Сам результат скручивания можно будет поглядеть и потрогать руками&#8230;

Итак, потребовалось реализовать юнит-тестирование для JS-кода. В качестве среды [...]]]></description>
			<content:encoded><![CDATA[<p>В этой заметке расскажу о своем опыте юнит-тестирования JS-кода, опыте использования среды выполнения тестов js-test-driver, ее возможности code coverage и скручивании ежа с ужом, а именно данных о code coverage от js-test-driver и генератора отчетов о покрытии PHP_CodeCоverage. Сам результат скручивания можно будет поглядеть и потрогать руками&#8230;<br />
<span id="more-190"></span><br />
Итак, потребовалось реализовать юнит-тестирование для JS-кода. В качестве среды для выполнения и фреймворка для написания тестов был выбран <a href="http://code.google.com/p/js-test-driver/" target="_blank">js-test-driver</a>. Причины таковы:</p>
<ul>
<li>есть в виде <a href="http://plugins.jetbrains.net/plugin/?webide&amp;id=4468" target="_blank">плагина</a> для применяемой командой IDE &#8211; <a href="http://www.jetbrains.com/phpstorm/" target="_blank">PhpStorm</a> (к сожалению в настоящий момент плагин не работает на текущей платформе PhpStorm, о чем есть соответствующий <a href="http://code.google.com/p/js-test-driver/issues/detail?id=82" target="_blank">тикет</a> в статусе Started)</li>
<li>умеет выполнять тесты сам в нескольких браузерах</li>
<li>умеет давать отчеты о Code coverage</li>
</ul>
<p>Несмотря на то что плагин для IDE сейчас неработоспособен, &laquo;пощупать&raquo; технологию можно из консоли. Сервер и среда исполнения запускаются отдельно из консоли и могут гонять тесты &laquo;в ручном режиме&raquo; по пинку пользователя.</p>
<h3>Попробуем в деле</h3>
<p>Код, который будем тестировать</p>
<pre class="brush: javascript">var greeter = function(toSay){
  this.whatToSay = toSay;
}

greeter.prototype.say = function(sayBye){
  if(sayBye == true)
    return "Goodbye " + this.whatToSay;
  else
    return "Hello " + this.whatToSay;
}</pre>
<p>И тест</p>
<pre class="brush: javascript">var testCase = new TestCase("Say");

testCase.prototype.testCase1 = function(){
  var i = new greeter('test');
  assertEquals("Hello test", i.say(false));
};</pre>
<p>Файловая структура</p>
<pre class="brush: plain">  \jstd
    \plugins
      coverage.jar
    code.js
    test.js
    conf
    jstestdriver.jar</pre>
<p>Конфигурация для запуска (файл conf)</p>
<pre class="brush: plain">load:
 - code.js
 - test.js

server: http://localhost:4224</pre>
<h4>Запускаем</h4>
<p>Сперва стартуем сервер</p>
<pre class="brush: plain">H:\jstd>java -jar jstestdriver.jar --port 4224</pre>
<p>Запускаем браузер, идем на http://localhost:4224, овладеваем браузером. Запускаем прогон тестов.</p>
<pre class="brush: plain">
H:\jstd>java -jar JsTestDriver.jar --config conf --tests all
..
Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (1,00 ms)
  Chrome 6.0.472.63 Windows: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (1,00 ms)
  Safari 525.28.1 Windows: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (1,00 ms)
</pre>
<p>Видим, что прогнали всего 2 теста. В т.ч. в браузере Chrome &#8211; 1, и он прошел успешно, в браузере Safari &#8211; также 1 и также успешно. Все замечательно. </p>
<h3>А что там было насчет code coverage?</h3>
<p>CodeCoverage <a href="http://code.google.com/p/js-test-driver/wiki/CodeCoverage" target="_blank">подключается</a> отдельным <a href="http://code.google.com/p/js-test-driver/downloads/detail?name=coverage-1.2.2.jar&amp;can=2&amp;q=" target="_blank">плагином</a>. Данные о покрытии могут либо отображаться в виде статической информации (файл такой-то покрыт на N%) по окончании исполнения тестов, либо могут выгружаться в файл формата <a href="http://ltp.sourceforge.net/coverage/lcov.php">LCOV</a>. Авторы предлагают генерировать визуальные отчеты с помощью тулзы genhtml. Беглый поиск портированных под Win32 результатов не дал, поднимать Cygwin или отдельную машину для построения отчетов не хочется&#8230;</p>
<h4>Запустим тесты с code coverage</h4>
<p>Подключим плагин. Отредактируем конфигурационный файл (conf).</p>
<pre class="brush: plain">
load:
 - code.js
 - test.js

server: http://localhost:4224

plugin:
 - name: "coverage"
   jar: "plugins/coverage.jar"
   module: "com.google.jstestdriver.coverage.CoverageModule"
</pre>
<p>Запустим тесты</p>
<pre class="brush: plain">
H:\jstd>java -jar JsTestDriver.jar --config conf --tests all
Safari: Runner reset.
.Safari: Runner reset.
Chrome: Runner reset.
.Chrome: Runner reset.

Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (1,00 ms)
  Chrome 6.0.472.63 Windows: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (1,00 ms)
  Safari 525.28.1 Windows: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (0,00 ms)

H:/jstd/code.js: 83.33333% covered
H:/jstd/test.js: 100.0% covered
</pre>
<p>Видим, что дополнительно к результатам появилась информация о покрытии кода. От такого отчета толку чуть более чем никакого. Начнем сохранять результаты тестов в файл.</p>
<pre class="brush: plain">
H:\jstd>java -jar JsTestDriver.jar --config conf --tests all --testOutput ./out
Safari: Runner reset.
.Safari: Runner reset.
Chrome: Runner reset.
.Chrome: Runner reset.

Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (1,00 ms)
  Chrome 6.0.472.63 Windows: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (1,00 ms)
  Safari 525.28.1 Windows: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (1,00 ms)
</pre>
<p>Теперь информации о покрытии не видно совсем, но в папке ./out появился файл с покрытием в формате LCOV.</p>
<h4>Формат LCOV</h4>
<p>Формат lcov-файла, генерируемого js-test-driver предельно прост.</p>
<pre class="brush: plain">SF:H:/jstd/code.js
DA:1,2
DA:2,2
DA:5,2
DA:6,2
DA:7,0
DA:9,2
end_of_record
SF:H:/jstd/test.js
DA:1,2
DA:3,2
DA:4,2
DA:5,2
end_of_record</pre>
<p>SF &#8211; файл, для которого приводятся данне далее, DA &#8211; данные о покрытии (DA:Строка,СколькоРазВыполнена).</p>
<h3>Генерируем красивый отчет: PHP_CodeCoverage</h3>
<p><a href="http://www.phpunit.de/" target="_blank">PHPUnit</a> &#8211; фреймворк дле реализации юнит-тестирования для PHP, имеет возможность генерировать отчеты о code coverage. <a href="http://github.com/sebastianbergmann/php-code-coverage" target="_blank">Модуль</a>, занимающийся CodeCoverage, очень легко отделяем и очень аккуратно реализован. В состав входит интерфейс <a href="http://github.com/sebastianbergmann/php-code-coverage/blob/master/PHP/CodeCoverage/Driver.php" target="_blank">PHP_CodeCoverage_Driver</a>, классы, имплементирующие его, могут служить источником данных о Code coverage для прочих компонентов проекта (построитель отчетов в т.ч.).</p>
<h4>Xdebug. Как он отдает данные о покрытии?</h4>
<p>Для файла&#8230;</p>
<pre class="brush: php">&lt;?php
xdebug_start_code_coverage();

function a() {
    $x = 10;
}

$b = 30;

var_dump(xdebug_get_code_coverage());</pre>
<p>Получим результат&#8230;</p>
<pre class="brush: php">array(
  'Z:\home\test\www\test.php' =&gt;
    array(
      4 =&gt; 1
      8 =&gt; 1
      10 =&gt; 1
    )
);</pre>
<p>Видно что форматы очень похожи, можно сделать <a href="http://code.google.com/p/lcov-code-coverage/" target="_blank">свой драйвер</a></p>
<p>Ниже &#8211; простой пример кода, генерирующего отчет о покрытии. Предполагается, что данные о покрытии находятся в файле coverage.dat. Отчет будет расположен в папке CodeCoverageReport.</p>
<pre class="brush: php">&lt;?php

include('PHP/CodeCoverage.php');
include('PHP/CodeCoverage/Driver/Lcov.php');
include('PHP/CodeCoverage/Report/HTML.php');

// ./lcov_coverage.dat contains ine coverage report in LCOV format

$coverage = new PHP_CodeCoverage(new PHP_CodeCoverage_Driver_Lcov('./coverage.dat'));

$coverage-&gt;start('mytest');
$coverage-&gt;stop();

$writer = new PHP_CodeCoverage_Report_HTML();
$writer-&gt;process($coverage, 'CodeCoverageReport');</pre>
<p>Что получается, можно посмотреть <a href="http://elifantiev.ru/examples/CodeCoverageReport/" target="_blank">здесь</a>. Можно посмотреть на отчет и увидеть, что наш сложный пример не полностью покрыт тестами, пропущена одна ветка и ее надо срочно покрыть тестами.</p>
]]></content:encoded>
			<wfw:commentRss>http://elifantiev.ru/js-unit-testing-and-codecoverag/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Пробки на Я.Картах &#8211; изменился адрес сервера с тайлами</title>
		<link>http://elifantiev.ru/tjams-tiles-server-chang/</link>
		<comments>http://elifantiev.ru/tjams-tiles-server-chang/#comments</comments>
		<pubDate>Fri, 02 Jul 2010 17:38:09 +0000</pubDate>
		<dc:creator>Olegas</dc:creator>
				<category><![CDATA[IT]]></category>
		<category><![CDATA[YMaps]]></category>

		<guid isPermaLink="false">http://elifantiev.ru/?p=188</guid>
		<description><![CDATA[Недавно Яндекс поменял адрес сервера с тайлами пробок. Теперь вместо trf.maps.yandex.net надо использовать jgo.maps.yandex.net.
Спасибо за багрепорт и новый хост проекту http://chelmaps.ru
]]></description>
			<content:encoded><![CDATA[<p>Недавно Яндекс поменял адрес сервера с тайлами пробок. Теперь вместо trf.maps.yandex.net надо использовать jgo.maps.yandex.net.</p>
<p>Спасибо за багрепорт и новый хост проекту <a href="http://chelmaps.ru">http://chelmaps.ru</a></p>
]]></content:encoded>
			<wfw:commentRss>http://elifantiev.ru/tjams-tiles-server-chang/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

