Если нельзя, но очень хочется, то нужно обязательно и ничего в мире не стоит того, чтобы делать из этого проблему!


Интересна Java? Кликай по ссылке и изучай!
Если тебе полезно что-то из того, чем я делюсь в своем блоге - можешь поделиться своими деньгами со мной.
с пожеланием
столько времени читатели провели на блоге - 
сейчас онлайн - 

среда, 21 ноября 2012 г.

Очаровательная музыка

Вот понравилась мелодия Ephemeral Feeling - Sad Beautiful Piano, решил воспроизвести midi и потом по ней учиться.


Пока вот что получилось.

Может кому-то пригодится.

"Если учителя можно заменить машиной, то такого учителя стоит заменить"

Валерка, спасибо за линк!
Очень понравилось "Вопрос от экспериментатора бухгалтерше: - а ты не могла бы подтянуть детей по биотехнологии? - но как, я ведь в этом ничего не понимаю - не важно. используй бабушкин метод - как это? - тебе просто стоит стать у них за спиной и бесконечно восхищаться ими"

воскресенье, 18 ноября 2012 г.

Как мокать Bean'ы внутри Spring context запущеного Jetty?

Есть Web приложение на Spring (MVC + IoC). Есть Jetty сервер, на котором оно ранится. Web приложение в своей Model содержит ряд сервисов (Spring Beans), которыми пользуются контроллера.

Задача: Написать функциональный тест с испольованием WebDriver, но так, чтобы из теста была возможность мокать реальные сервиса модели.

Updated: Есть более продвинутая имплементация этой задачи, вот тут. Все что дальше по тексту этого поста - на память :)

Например в приложении есть 1 сервис MyService
package com.services;

import org.springframework.stereotype.Component;

/**
 * User: apofig
 * Date: 11/18/12
 * Time: 9:49 PM
 */
@Component("myService")
public class MyService {

    public String getString() {
        return "Hello from " + MyService.class.getSimpleName() + "!";
    }

}
Он инджектится в контроллер MyController с помощью @Autowired аннотации Spring'ом.
package com.controller;

import com.services.MyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * User: apofig
 * Date: 9/20/12
 * Time: 1:37 PM
 */
@Controller
@RequestMapping("/")
public class MyController {

    @Autowired
    private MyService service;

    @RequestMapping(method = RequestMethod.GET)
    public String savePlayerGame(Model model) {
        model.addAttribute("string", service.getString());
        return "view";
    }
}
Контроллер отправляет данные на jsp, которая рисует сообщение на экране
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html;">
    <title>Main page</title>
</head>
<body>
    <c:if test="${string!=null}">
        <span id="message">${string}</span>
    </c:if>
<body>
</html>
Во время старта приложения я хочу, чтобы там был не реальный MyService, а его мок (или шпион), которым я буду рулить из теста вот так
public class IntegrationTest {
    private String url = "http://localhost:8080/";
    private WebDriver driver = new HtmlUnitDriver(true);
    private MyService service;

    @Test
    public void shouldGetMessageFromService() throws IOException, InterruptedException {
        when(service.getString()).thenReturn("Hi from mock!");

        driver.get(url);

        String message = driver.findElement(By.id("message")).getText();
        assertEquals("Hi from mock!", message);
    }
}
Зачем это делать? Если писать полностью функиональные тесты, тогда чтобы протестить, скажем, удаление пользователя из системы надо будет вначале создать этого пользователя по всем правилам. Но если иметь возможность мокать модель, тогда лишние действия не потребуются - мы всего лишь проверим, что до модели дошел запрос о удалении юзера и все.

Итак я долго (дня два) искал возможность это сделать. Google почти молчит. Самое близкое, что я нашел - библиотека Springockito, но нашел я ее тогда, когда практически моя версия была рабочей.

Как я решал проблему? При старте Jetty сервера есть возможность подслушать момент инициплизации контекста.
    Server server = new Server(0);
    WebAppContext context = new WebAppContext("src/main/webapp", "");
    context.addEventListener(new ServletContextListener() {
        @Override
        public void contextInitialized(ServletContextEvent sce)
            ServletContext servletContext = sce.getServletContext(); 
            // вот тут       
        }
    }
    server.setHandler(context);
    server.start();
    int port = server.getConnectors()[0].getLocalPort();
    String url = "http://localhost:" + port;
Я так же знаю, что после окончательного старта сервера в ServletContext будет находиться Spring'овый WebApplicationContext, но в момент отработки ServletContextListener его там еще нет.

После старта сервера я могу его получить вот так
    WebApplicationContext applicationContext = 
        WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
Имея на руках Spring'овый контекст я могу поковырять его - могу удалить существующий бин и заменить его моком, так что всякий раз когда клиент будет делать context.getBean(name) он будет получать мой мок. Вперед!
    private static Object mocking(WebApplicationContext webAppContext, String beanName, boolean isMockOfSpy) {
        Object realBean = webAppContext.getBean(beanName);

        AbstractRefreshableApplicationContext context = (AbstractRefreshableApplicationContext) webAppContext;
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();

        Object newBean = null;
        if (isMockOfSpy) {
            newBean = mock(realBean.getClass());
        } else {
            newBean = spy(realBean);
        }

        beanFactory.removeBeanDefinition(beanName);
        beanFactory.registerSingleton(beanName, newBean);
        return beanFactory.getBean(beanName);
    }
Конечно, тут не помешает пару проверок (для ClassCast), потому как контекст мог быть не обязательно XmlWebApplicationContext как у меня - а там иди знай, что вернется.

Но это не все. В момент, когда я могу провернуть такую махинацию Spring уже проинджектил все бины в поля зависимых классов по аннотации @Autowired. И хоть я могу получить мок с помощью context.getBean(name), меня это не устраивает.

Несколько часов дебага с кондишенал брейкопинтами и я поймал момент, когда WebApplicationContext уже имеет на руках все бины, но еще не успел расставить их куда велено. Любопытно, что этот момент - такой же как и мой обработчик ServletContextListener.

В момент когда я добавляю свой обработчик, список обработчиков WebAppContext пуст (т.е. я первым слушаю) - тут нигде нет никакого WebApplicationContext. Даже в момент, когда мой обработчик отрабатывает, WebApplicationContext'а так же нигде нет.
    Server server = new Server(0);
    WebAppContext context = new WebAppContext("src/main/webapp", "");
    // Spring контекст недуступен
    context.addEventListener(new ServletContextListener() {
        @Override
        public void contextInitialized(ServletContextEvent sce)
            ServletContext servletContext = sce.getServletContext();        
            // Spring контекст так же недуступен
        }
    }
    server.setHandler(context);
    server.start();
    int port = server.getConnectors()[0].getLocalPort();
    String url = "http://localhost:" + port;
Обыно он хранится в _contextAttributes, но тут пока пусто
Но я точно знаю, что он там будет спустя некоторое время (ибо додебажился).
Через некоторое время, в очередь, после моего ServletContextListener обработчика, выстрится ряд других обработчиков. Последним из них будет Spring'овый. И вот когда он отработает, то WebApplicationContext появится внутри ServletContext.В этот момент бины будут уже готовые, но еще пока не расставленные по зависимым классам.

Мне надо как-то стать пятым в очереди, и сделал я это так
    private ServletContext servletContext;
    private WebApplicationContext applicationContext;

    public String start() throws Exception {
        server = new Server(0);
        final WebAppContext context = loadWebContext();
        context.addEventListener(new ServletContextListener() {
            @Override
            public void contextInitialized(ServletContextEvent sce) {
                servletContext = sce.getServletContext();

                context.addEventListener(new ServletContextListener() {
                    @Override
                    public void contextInitialized(ServletContextEvent sce) {
                        applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
                        // тут бины тепленькие, и все мои!!!
                    }

                    @Override
                    public void contextDestroyed(ServletContextEvent sce) {
                    }
                });
            }

            @Override
            public void contextDestroyed(ServletContextEvent sce) {
            }
        });
        server.setHandler(context);
        server.start();
        int port = server.getConnectors()[0].getLocalPort();

        String url = "http://localhost:" + port;
        return url;
    }
Дальше дело техники, вызываем метод
    private static Object mocking(WebApplicationContext webAppContext, String beanName, boolean isMockOfSpy) {
И сразу после того, как отработчик отработает Spring полезет деливерить бины по зависимым клиентам (@Autowired).

Я в дамках!

Вот исходный код простенького Spring Web проекта с демонстрацией этой возможности.

четверг, 15 ноября 2012 г.

Отчет о Tetris Coding Dojo #3 Киев DataArt XPDays

Привет! Мы в очередной раз собрались, чтобы погаматься в тетрис.
Спасибо Коле Алименкову, офису DataArt, а именно Антону Михайлюку и Саше Лылке которые помагали нам с организационной стороны, а так же и всем участникам этой встречи.

Ребят было достаточно много и теперь не только джависты, но и C# программисты. Кодили на лиспе, пхп, питоне...


Ребята не жалели бумагу


Уходили глубоко в себя :)


Но первым на Level 2 перешел AlexeyZ и тут же вся комната наполнилась смехом, потому как алгоритм не умел расскладывать квадратики, и относился к ним как к палочкам - вот и получилась занятная лесенка.


Вообще, долгое время ребята невыходили из минусов. Наверное я все же перемудлрил с алгоритмом подсчетоа очков накануне. Пришлось делать второй хот-фикс. Функцию я назвал makeMePositive и после этого на экране минуса пропали :)


А вот Игорь потирает руки. Мы-то с Серегой знаем, что он бывал на всех прошлых встречах и у него в загашнике есть хороший рабочий алгоритм - долго ждать не пришлось :) Игорь вскоре запустил его.


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



Призами на этот раз были игрушки из нашего детства



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

Саша и Леша находясь на втором уровне сложности умудрились заработать столько очков, что вышли на третье место среди ребят находящихся на 5 уровне.
За то, что они поделились секретом как они это сделали - мы вручили им один тетрис на двоих :)

Получили по тетрису так же тройка лидеров.

Призеры писали на php в паре. Тут они еще не знают, что попадут в тройку.

Двое ребят не побоявшиеся писать на Closure - им достались не такие тетрисы, как всем...
...в партии, что мы купили к сегодняшней встрече как раз было два тетриса, которые отличались от всех остальных. Так и на встрече - все писали на С# и Java, а ребята решили на Closure. И молоды! Клиенты мы потом у них заберем и к делу подошьем :)

А вот эти ребята писали тесты и потому немного отстали от всех :) Просто тесты имеют такое свойство - вначале ты их, а потомо ни тебе.



А вот вкратце вся игра, ускоренная в 10 раз


Остальные фотки тут

воскресенье, 11 ноября 2012 г.

Refactoring: Извлечение итератора из цикла for

Решаю задачку. Наколбасил кода. И в один прекрасный момент немного засутпорился. Есть метод
Он чудный :) но не об этом сейчас. Нужно очень быстро сделать так, чтобы в зависимости от boolean direction параметра метод итерировался по второму циклу (int x) в прямом, либо в обратном порядке (но в том же диапазоне).

Копипаст и if с двумя циклами не катит - дублирование. Выделить внутренности цикла int x тоже не катит - много зависимостей. Да и не интересно - я так уже делал в молодости :)

Некрасивый код стал еще более некрасивым.

Как быть? И тут приходит идея. А что если поспользоваться интерфейсом Iterable? По нему for ходит без пороблем. Выглядеть это будет приблизительно так.

А вот и сам класец. Только внимание, я его сделал таким, что о сам определяет где from где to даже если ты их перепутал местами - то немного неочевидно, фор не работает так, но мне удобно!

public class XIterator implements Iterable<Integer>{
    private int from;
    private int to;
    private int x;
    private boolean increase;

    public XIterator(int from, int to, boolean increase) {
        if (increase) {
            this.from = Math.min(from, to);
            this.to = Math.max(from, to);
        } else {
            this.from = Math.max(from, to);
            this.to = Math.min(from, to);
        }

        this.increase = increase;
        this.x = this.from;
    }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            @Override
            public boolean hasNext() {
                if (increase) {
                    return x <= to;
                } else {
                    return x >= to;
                }
            }

            @Override
            public Integer next() {
                if (increase) {
                    return x++;
                } else {
                    return x--;
                }
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }
}
Вот тесты для него
public class XIteratorTest {

    @Test
    public void increase() {
        assertIteratorValues(new XIterator(0, 10, true), "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]");
    }

    @Test
    public void decrease() {
        assertIteratorValues(new XIterator(0, 10, false), "[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]");
    }

    @Test
    public void increaseNegative() {
        assertIteratorValues(new XIterator(-5, 5, true), "[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]");
    }

    private void assertIteratorValues(Iterable<Integer> iterator, String expected) {
        LinkedList list = new LinkedList();
        for (Integer i : iterator) {
            list.add(i);
        }

        assertEquals(expected, list.toString());
    }

    @Test
    public void decreaseNegative() {
        assertIteratorValues(new XIterator(-5, 5, false), "[5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5]");
    }

    @Test
    public void increaseBadFromTo() {
        assertIteratorValues(new XIterator(5, -5, true), "[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]");
    }

    @Test
    public void decreaseBadFromTo() {
        assertIteratorValues(new XIterator(5, -5, false), "[5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5]");
    }

    @Test
    public void zeroIterator() {
        assertIteratorValues(new XIterator(0, 0, false), "[0]");
        assertIteratorValues(new XIterator(0, 0, true), "[0]");
    }
}

воскресенье, 4 ноября 2012 г.

Вокруг везде я

Не так давно мы праздновали день рождения Сергея. Собрались ребята из команды Сереги. Под пивко мы зацепили несколько интересных концептуальных тем :) Я немного опоздал, потому был без их контекста. И участвовал в разговоре немного через призму, свою какую-то призму. Я вдруг как-то ярко-ярко осознал, что вокруг везде я. Попробую пояснить.
"Ты не можешь утвержать, что я вижу крассный цвет так же как и ты, мы всего лишь научились различать его, но видеть мы его можем по-разному. Да, аппаратная часть говорит о некоем сходстве между нами, но видит человек не глазами, а мозгом..."
Из разговоров на перекуре
Никто никогда не сможет узнать каким мир вижу я. Даже, если взять во внимание все что я создал, что говорил, как при этом вел себя - все-равно никто и никогда не сможет сказать кто или что такое Саня. Так же и я не могу этого сделать по отношению к окружающим людям. Но все же я имею возможность судить об окружающих. Как так получается?

Если подумать, а о чем я сужу на самом деле? Сужу я по себе. Даже не по себе, а себя. Мозг видит вокруг не Петю, Васю, Колю, Игоря, а Саню, Саню, Саню и Саню. Как сказал Антон "везде вокруг ты, только морды разные". И если я дружу с Колей, это значит, что в нем есть что-то, что мне нравится в себе (или нравилось бы, если бы оно там было). Если мне неприятная компания Пети - значит это, что я себе таким не нравлюсь.

Говорят фидбек - это дар. Я тоже так думал. Но теперь замечена еще одна грань. Фидбек это дар, но не для того, кому его дают, а для того, кто его дает. Задумайся, когда говоришь кому-то "ты такой-сякой!!!" это говоришь в первую очередь себе.

Американцы говорят - "I like you" - я такой же как и ты (если буквально) или ты мне нравишься. Стоит задуматься. Кто не ловил себя на мысли, что на самом деле никого вокруг нет - все это представление, для меня одного. Посмотреть хоть фильм Шоу Трумана.

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

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


В этом контексте Слушать приобретает новый смысл. Слушать что говоришь, и слушать что говорят. Ведь каждый говорит только о себе, даже если он думает, что говорит о тебе.
Нельзя рассмотреть в другом человеке то, чего нет в тебе самом.
Люблю повторять это.

Остается вопрос о том, как подружиться с самим собой. Если мне кто-то или что-то не нравится, значит это не нравится во мне. Пару раз мне удавалось преваращать неприятеля в друга, но четкого инструмента я так и не нашел. Везде говорят "полюби это в себе", только как?

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

Отчет automated testing dojo. Киев, 3 ноября 2012

На этот раз мы с Сергеем хотим поблагодарить портал об автоматизированном тестировании ПО http://automated-testing.info/, а именно Михаила Поляруша, Андрея Азимова и Александра Кабалюка, а так же <ЕPAM> и Викторию Фесенко за этот чудный ивент. Мы в очерередной раз провели Automated Testing Dojo. Иучастников на этот раз было много!


На этот раз мы собирали всех желающих научиться автоматизировать, а не только опытных автоматизаторов. Ребята собирались в группы, в которых трудились. А мы - Серега, Миша и я помагали с возникающими вопросами.

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