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

Если тебе полезно что-то из того, чем я делюсь в своем блоге - можешь поделиться своими деньгами со мной.
с пожеланием
столько времени читатели провели на блоге - 
сейчас онлайн - 
Показаны сообщения с ярлыком code. Показать все сообщения
Показаны сообщения с ярлыком code. Показать все сообщения

понедельник, 16 марта 2020 г.

SmartAssert или как проверять статус всех assertEquals в одном тесте

Решение этого вопроса есть с коробки, я же хотел покодить в свое удовольствие и делюсь тем, что получилось.

Код лежит тут. Лицензия GNU GPL v3. На здоровье.

Если тебе лень писать @Test public void shouldBlahBlahBlah_whenBlahBlahBlah () {} для каждого отдельного ассерта, то скорее всего у тебя несколько ассертов в одном тесте. Так не рекомендуют делать, но это решение встречается достаточно часто. Почему? Просто потому что блок // given в этом конкретном тесте с 3-5-7ю ассертами может быть большим. А копипастить его в каждый отдельный тест чтобы уютно разместить там 1 assertEquals еще большее зло.

Да, конечно можно выделить // given часть теста в отдельный метод и повторно использовать. Только фигня получится. Вот был лаконичный тест: в // given подготовились, в // when вызвали одну строчку тестируемого метода, и в 3-5-7ю assertEquals проверили объект, который вернулся. И что? 1 тест. И следуя рекомендации 1 assert на 1 тест мы получаем кучу операторных скобок, 8 методов и 10 шагов в сторону от рекомендации - "тест как документация". Вот все было перед глазами, а теперь иди собирай все все мысли по классу.

И если ты не следуешь бездумно всем рекомендациям, описанным в инженерных книгах, то ты, так же как и я получишь один тест с 3-5-7ю ассертами. Но тут есть другой бок - скорее всего ты, так же как и я, утомился перезапускать этот один стройный тест. Да-да, интеграционный, блин, со спрингом под капотом, от чего он ранится не 10 милисекунд, а 100 секунд! Потому что мир джава жесток и беспощаден...
@SpringBootTest(classes = CodenjoyContestApplication.class,
        properties = "spring.main.allow-bean-definition-overriding=true")
@RunWith(SpringRunner.class)
@ActiveProfiles(SQLiteProfile.NAME)
@Import(RestGameControllerTest.ContextConfiguration.class)
@WebAppConfiguration
public class RestGameControllerTest {
...в этом тесте будет несколько assertEquals. И раз за разом, натыкаясь на очередной свалившийся ассерт, скорее всего ты, так же как и я, задавался вопросом: какого фига в junit тест валится после первого же ассерта?

Решение созрело.

Да, я знаю, что писать несколько assertEquals в одном тесте (было) не ок. Но писать по новой конструкции для каждого ассерта тоже перебор.
@Test
public void shouldBlahBlah_whenBlahBlah() {
   ...
}
Вот и написал свой assertEquals который агрегирует ошибки и валидирует их только, когда ты явно этого попросишь в @After методе
@After
public void checkErrors() {
    SmartAssert.checkResult();
}
В простом вариаенте запуска потребуется еще всего лишь импортнуть класс, в котором есть статический assertEquals метод.
import static com.codenjoy.dojo.stuff.SmartAssert.*;
А старый импорт удалить
import static org.junit.Assert.*;
А что умного? Так это вывод результатов. Каждый тест отработает от начала и до конца, не важно сколько assertEquals по дороге не пройдут. При этом ты увидишь в консоли все expected: <qwe> but was: <asd> блоки и кусочек стектрейса из тестового класса, в котором были вызовы измененного assertEquals длинной до 10 строк (на это можно повлиять).

Например вот тест, я в нем заведомо поломал первые 4 assert, заменив expected с true на false
@Test
public void shouldExists() {
    assertEquals(false, service.exists("first"));
    assertEquals("false", get("/rest/game/first/exists"));
   
    assertEquals(false, service.exists("second"));
    assertEquals("false", get("/rest/game/second/exists"));

    assertEquals(false, service.exists("non-exists"));
    assertEquals("false", get("/rest/game/non-exists/exists"));
}
В оригинальной версии тест прекратил бы выполнение после первой же строчки. Слетел ассерт - давай до свидания. Но не co SmartAssert
org.junit.ComparisonFailure: expected:<[fals]e>; but was:<[tru]e>
 com.codenjoy.dojo.web.rest.RestGameControllerTest.shouldExists(RestGameControllerTest.java:135)

org.junit.ComparisonFailure: expected:<[fals]e> but was:<[tru]e>
 com.codenjoy.dojo.web.rest.RestGameControllerTest.shouldExists(RestGameControllerTest.java:136)

org.junit.ComparisonFailure: expected:<[fals]e> but was:<[tru]e>
 com.codenjoy.dojo.web.rest.RestGameControllerTest.shouldExists(RestGameControllerTest.java:138)

org.junit.ComparisonFailure: expected:<[fals]e> but was:<[tru]e>
 com.codenjoy.dojo.web.rest.RestGameControllerTest.shouldExists(RestGameControllerTest.java:139)

java.lang.AssertionError: There are errors
 at org.junit.Assert.fail(Assert.java:88)
 at com.codenjoy.dojo.stuff.SmartAssert.checkResult(SmartAssert.java:118)
 at com.codenjoy.dojo.stuff.SmartAssert.checkResult(SmartAssert.java:132)
 at com.codenjoy.dojo.web.rest.RestGameControllerTest.checkErrors(RestGameControllerTest.java:98)
Важно, что ссылки на сами ассерты в коде в idea кликабельны.
(RestGameControllerTest.java:139)
Так же я добился замены базовым проверятором Idea констукции
expected:<[fals]e> but was:<[tru]e>
На такую, которая будет подсвечивать diff каждого слетевшего ассерта отдельно. В этом мне помог deprecated MultipleFailureException.

Код лежит тут. Лицензия GNU GPL v3. На здоровье.


понедельник, 21 января 2013 г.

Рисуем фракталы на Java

Для одной задумки понадобилась библиотека, для работы с bmp изображениями. Найти ее было не сложно. Сейчас чего-только не написали уже для Java. Спасибо Nayuki Minase за то, что он потрудился и написал библиотеку для работы с bmp.

И вот я конечно же ищу сэмплы, чтобы разобраться как сие диво работает. И что вижу? Одна из демок рисует Множество Мандельброта. Ну вот я и завис, уж простите. В прошлом, когда еще кодил на Delphi я тоже завис - на по-дольше. В результате родилась такой вот фрактальный браузер. Сегодня я решил помянуть это и написать свою версию рисовалки фракталов на основе библиотеки работы с bmp рисунками.

Вот один из рисунков в большом разрешении (клик по рисунку увеличит его, но осторожно - там 20 мег)...
А теперь о реализации (исходник можно скачать тут (зеркало), осторожно внутри исходники библиотеки по работе с BMP, она распостраняется по MIT лицензии).

Итак интерфейсик Фрактала
public interface Fractal {
    Position getZoom();

    int getFunction(double x, double y, int iterations);
}
Вот Мандельброт
public class Mandelbrot implements Fractal {
    private Position mandelbrot = new Position(-1.9, 0.5, -1.2, 1.2);

    @Override
    public Position getZoom() {
        return mandelbrot.zoom(40).move(-1.25, 2).zoom(5);
    }

    @Override
    public int getFunction(double a, double b, int iterations) {
        double r = 0;
        double x = 0;
        double y = 0;
        int color = iterations;
        while (color > 0 && r < 4) {
            double x2 = x * x;
            double y2 = y * y;
            double xy = x * y;
            x = x2 - y2 + a;
            y = 2 * xy + b;
            r = x2 + y2;
            color--;
        }
        return color;
    }
}
Вот Джулия
public class Julia implements Fractal {

    @Override
    public Position getZoom() {
        return new Position(-1.59, 1.527, -1.558, 1.558);
    }

    @Override
    public int getFunction(double x0, double y0, int iterations) {
        double r = 0;
        double a = -0.55;
        double b = -0.55;
        double x = x0;
        double y = y0;
        int color = iterations;
        while (color > 0 && r < 4) {
            double x2 = x * x;
            double y2 = y * y;
            double xy = x * y;
            x = x2 - y2 + a;
            y = 2 * xy + b;
            r = x2 + y2;
            color--;
        }
        return color;
    }
}
Класс Position я сделал для удобной работы с координатами (зум, мув). Простите за французский в названии полей/переменных.
import p79068.bmpio.Rgb888Image;

public class Position {
    private double bxMin;
    private double bxMax;
    private double byMin;
    private double byMax;

    Position(double bxMin, double bxMax, double byMin, double byMax) {
        this.bxMin = bxMin;
        this.bxMax = bxMax;
        this.byMin = byMin;
        this.byMax = byMax;
    }

    public Position zoom(double zoom) {
        double lx = (bxMax - bxMin)*(zoom-1)/(2*zoom);
        double ly = (byMax - byMin)*(zoom-1)/(2*zoom);
        double xMin = bxMin + lx;
        double xMax = bxMax - lx;
        double yMin = byMin + ly;
        double yMax = byMax - ly;

        return new Position(xMin, xMax, yMin, yMax);
    }

    public Position move(double dx, double dy) {
        double xMin = bxMin + (bxMax - bxMin)*dx;
        double xMax = bxMax + (bxMax - bxMin)*dx;
        double yMin = byMin + (byMax - byMin)*dy;
        double yMax = byMax + (byMax - byMin)*dy;

        return new Position(xMin, xMax, yMin, yMax);
    }

    public Position resize(double width, double height) {
        if (width/height > 1) {
            double dx = (bxMax - bxMin)*(width/height - 1)/2;
            double xMin = bxMin - dx;
            double xMax = bxMax + dx;
            double yMin = byMin;
            double yMax = byMax;
            return new Position(xMin, xMax, yMin, yMax);
        } if (width/height < 1) {
            double dy = (byMax - byMin)*(height/width - 1)/2;
            double xMin = bxMin;
            double xMax = bxMax;
            double yMin = byMin - dy;
            double yMax = byMax + dy;
            return new Position(xMin, xMax, yMin, yMax);
        } else {
            return this;
        }
    }

    public double getXMin() {
        return bxMin;
    }

    public double getXMax() {
        return bxMax;
    }

    public double getYMin() {
        return byMin;
    }

    public double getYMax() {
        return byMax;
    }

    public String toString() {
        return String.format("x=[%s...%s]\ny=[%s...%s]",
                bxMin, bxMax, byMin, byMax);
    }
}
Фрактальное изображение, основано на интерфейсе из библиотеки. Суть в том, что метод getRgb888Pixel() будет дергаться либой по каждому пикселю изобраения шириной и высотой getWidth() на getHeight()..
import p79068.bmpio.Rgb888Image;

public class FractalImage implements Rgb888Image {

    private int width;
    private int height;
    private Fractal fractal;
    private Palette palette;
    private Position zoom;

    public FractalImage(int width, int height, Fractal fractal, Palette palette) {
        this.width = width;
        this.height = height;
        this.fractal = fractal;        
        this.palette = palette;
        zoom = fractal.getZoom().resize(width, height);
    }

    @Override
    public int getWidth() {
        return width;
    }

    @Override
    public int getHeight() {
        return height;
    }

    @Override
    public int getRgb888Pixel(int x, int y) {
        double x1 = zoom.getXMin() + (x + 0.5) / width * (zoom.getXMax() - zoom.getXMin());
        double y1 = zoom.getYMax() - (y + 0.5) / height * (zoom.getYMax() - zoom.getYMin());
        int r = fractal.getFunction(x1, y1, palette.getSize() - 1);
        return palette.getColor(r);
    }

    public String getFractalName() {
        return fractal.getClass().getSimpleName();
    }
}
Он зависим не только от фрактала, но и от палитры
public interface Palette {
    int getColor(int index);

    int getSize();
}
Реализация которой в чернобелом варианте будет такой. Всего в палитре этой 256 цветов - 8 бит на канал (red=green=blue).
public class BlackAndWhite256Palette implements Palette {
    @Override
    public int getColor(int r) {
        return (r) | (r << 8) | (r << 16);
    }

    @Override
    public int getSize() {
        return 256;
    }
}
А вот палитра цветная рендомная, она рисует более интересные фракталы.
Код причесал как мог - он был портирован из Delphi. Я его писал лет 15 назад - можешь представить, что там было...
public class RandomPalette implements Palette {
    class Marker {
        int x;
        int color;

        public Marker(int x, int color) {
            this.x = x;
            this.color = color;
        }
    }

    private static final int MW = 8; // ширина маркера
    private static final int[] colorArray = new int[]{
            0xFFFFFF, 0x00FFFF, 0xFF00FF, 0xFFFF00, 0x0000FF, 0xFF0000, 0x00FF00,
            0xC0FFFF, 0xFFC0FF, 0xFFFFC0, 0xC0C0FF, 0xFFC0C0, 0xC0FFC0, 0xC000FF,
            0x00C0FF, 0x00FFC0, 0xC0FF00, 0xFFC000, 0xFF00C0};

    private List<Marker> markers = new LinkedList<Marker>();
    private int[] palette;

    public RandomPalette(int size) {
        palette = new int[size];

        int color = getRandomColor();
        markers.add(new Marker(0, color));
        markers.add(new Marker(size, color));

        int count = random(size / (MW * 3)) + 3;
        double r = size / (count + 1);

        addBlackMarker(MW + 1);
        for (int i = 1; i <= count; i++) {
            int j = (int) (i * r);
            if (yesOrNo()) {
                addBlackMarker(j - MW - 1);
            }
            addMarker(j, getRandomColor());
            if (yesOrNo()) {
                addBlackMarker(j + MW + 1);
            }
        }
        addBlackMarker(size - MW - 1);

        calculatePalette();
    }

    private int getRandomColor() {
        return colorArray[random(colorArray.length)];
    }

    private boolean yesOrNo() {
        return random(2) == 1;
    }

    private void addBlackMarker(int x) {
        addMarker(x, 0);
    }

    private void calculatePalette() {
        int x = markers.get(0).x;
        for (int i = 0; i < markers.size() - 1; i++) {
            int length = markers.get(i + 1).x - markers.get(i).x;
            for (int dx = 0; dx < length; dx++) {
                palette[x + dx] = colorChange(markers.get(i).color, markers.get(i + 1).color, length, dx);
            }
            x = x + length;
        }
        palette[0] = 0;
    }

    private int colorChange(int from, int to, double len, double x) {
        double red = change(getR(from), getR(to), len, x);
        double green = change(getG(from), getG(to), len, x);
        double blue = change(getB(from), getB(to), len, x);

        return rgb((int) red, (int) green, (int) blue);
    }

    private double change(double from, double to, double len, double x) {
        if (from == to) {
            return from;
        }

        double delta = Math.abs(from - to) * x / len;

        if (from < to) {
            return from + delta;
        } else {
            return from - delta;
        }
    }

    private int getR(int col) {
        return (col & 0x0000FF);
    }

    private int getG(int col) {
        return (col & 0x00FF00) >>> 8;
    }

    private int getB(int col) {
        return (col & 0xFF0000) >>> 16;
    }

    private int rgb(int r, int g, int b) {
        return (r) | (g << 8) | (b << 16);
    }

    private void addMarker(int x, int color) {
        // определяем после которого маркера будет создаваемый
        int index;
        for (index = 0; index < markers.size(); index++) {
            if ((markers.get(index).x < x) && (x < markers.get(index + 1).x)) {
                break;
            }
        }

        // если после последнего то выходим
        if (index == markers.size()) {
            return;
        }

        // если маркер некуда втиснуть между двумя ближайшими то выходим
        if (markers.get(index + 1).x - markers.get(index).x < MW + 2) {
            return;
        }

        // очень близко ставить маркер возле соседнего нельзя
        if ((markers.get(index).x + MW + 1 > x) || (markers.get(index + 1).x - MW - 1 < x)) {
            return;
        }

        markers.add(index + 1, new Marker(x, color));
    }

    private int random(int n) {
        return new Random().nextInt(n);
    }

    @Override
    public int getColor(int r) {
        return palette[r];
    }

    @Override
    public int getSize() {
        return palette.length;
    }
}
Небольшой консольный прогресбарчик-декоратор для того, чтобы я знал как долго будет рисоваться фраклат размером 20000x20000 пикселей. Я его прооптимизировал немного, чтобы он не занимался делением каждый пиксель.
public class Progress implements Rgb888Image {
    private long square;
    private long count;
    private long iteration;
    private long next;
    private FractalImage image;

    public Progress(FractalImage image) {
        this.image = image;
        this.square = getWidth()*getHeight();
        this.next = square/100;
    }

    @Override
    public int getWidth() {
        return image.getWidth();
    }

    @Override
    public int getHeight() {
        return image.getHeight();
    }

    @Override
    public int getRgb888Pixel(int x, int y) {
        calculateProgress();

        return image.getRgb888Pixel(x, y);
    }

    private void calculateProgress() {
        iteration++;
        if (iteration == next) {
            count = count + iteration;
            iteration = 0;
            int progress = (int) ((double)count*100 / square);
            System.out.println(progress + "%");
        }
    }

    public String getFractalName() {
        return image.getFractalName();
    }
}
Ну и Main метод
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import p79068.bmpio.BmpImage;
import p79068.bmpio.BmpWriter;

public final class FractalDemo {
 
    public static void main(String[] args) throws IOException {
        draw(new Mandelbrot());
        draw(new Julia());
    }

    private static void draw(Fractal fractal) throws IOException {
        BmpImage bmp = new BmpImage();
        Palette palette = new BlackAndWhite256Palette();
        Progress image = new Progress(new FractalImage(1920, 1080, fractal, palette));
        bmp.image = image;
        File file = new File(image.getFractalName() + ".bmp");
        FileOutputStream out = new FileOutputStream(file);
        BmpWriter.write(out, bmp);
        out.close();
    }
}
Уверен кто-то это полюбит так же как и я...

воскресенье, 27 ноября 2011 г.

Java for fun: Как работать с Mockito?

Не так давно, на двухдневном тренинге по TDD я решил поизучать новый mock-framework. Путем гугления выбрал самую популярную. 4 часа я вкуривал доку по Mockito v1.8.5. Читал и писал тесты, чтобы опробовать либу на деле.

В доке все супер понятно написано, но знание, которое не опробовано на практике скоро теряется. Так родился этот проектик с несколькими десятками тестов, проверяющих почти все возможности библиотеки.

Еще пол дня и я могу представить презенташку основанную на этих простых тестах



Пользуйся на здоровье. Надеюсь мой день не прошел даром и ты получишь ответы на вопросы:
- Как работать с Mockito?
- В чем фишка Mockito?
- Что лучше: Mockito или другой mock-framework?

пятница, 24 декабря 2010 г.

Подборка #38

Есть и другие подборки: #1, #2, #3, #4, #5, #6, #7, #8, #9, #10, #11, #12, #13, #14, #15, #16, #17, #18, #19, #20, #21, #22, #23, #24, #25, #26, #27, #28, #29, #30, #31, #32, #33, #34, #35, #36, #37, #38, #39, #40

Как бы ножки не украли...


Читать дальше...

четверг, 18 февраля 2010 г.

Как скопировать из JavaScript-кода текст в буфер обмена?

Как-то раз я заметил одну приятную фишку: под textarea находился линк, копирующий ее содержимое в буфер обмена Windows. Выглядит фишка где-то так

Очень удобно" - подумал я. Следующая мысль была "нифига себе JavaScript и такое умеет?". (читаем дальше)