• 0.70

  • +2.78

  • ,

В девелопмент через тестирование.

Я не писатель, я инженер.

Моя история началась 7 лет назад в городе-герое Минске.

Интро
Сам я родом из небольшого районного центра в 100 км от столицы РБ, куда и вернулся после получения диплома специалиста по телекоммуникациям.
На тот момент я немного знал и умел верстать статические HTML страницы с небольшими примесями JS. Уверенность в то время еще придавали умение обращаться с PC и навыки слепого набора на RU и EN расскладках. (да, я был наивен и не сведущ). Очень хотелось устроится Web-верстальщиком.
С английским вообще была беда: понимание текста — pre-intermediate в лучшем случае, а понимание на слух, разговорный и письмо — полное дно.

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

Составил слабенькое резюме (нашел где-то в сети какой-то вариант). Портфолио у меня не было. По весне резюме разослал по всем крупным компаниям Минска. В это же время общался со друзьями, одноклассниками, знакомыми, которые уже работали в IT сфере, на тему не нужен ли им юный, глупый падаван, но с очень бешеным желанием обучаться всему новому.

Ожидание
Первых 3 месяца (конец весны и почти все лето) ответов не было вообще. Моя уверенность в правильности выбора уже улетучилась. Из средств на существование — дохленькая зп жены, которой еле-еле хватало на еду и возмещение долга. И вот в конце августа моему счастью не было предела — 2!!! компании откликнулись на мое резюме.

Предложения
#1: Первое письмо было, можно так сказать, ни о чем — заброс удочки живой я еще или нет. На мой ответ, что я все еще ищу работу, мне было предложено попробовать пройти обучение в компании в течение 3 месяцев (5 дней в неделю по 6 часов), по итогам которого будет принято решение, достоинен ли я. Учитывая мое финансовое положение и удаленность моего места жительства от столицы — сразу отправили это предложение «ф топку».
#2: Второе письмо оказалось приглашением на собеседование.

Судьба?
В «офис» (пару комнат в каком-то складском здании, многие из которых на то время мне показались чем-то похожими на актовые залы) я приехал слишком заранее (спасибо «хорошо развитой» междугородной системе ОТ). До собеседования на должность верстальщика я так и не дошел. Дабы мне не мозолить глаза сотрудникам и не ждать в одиночестве, HR-специалист предложила мне попробовать пособеседоваться на должность QA-инженера. Кто такие тестировщики, я понятия не имел, но девушки, проводившие собеседование, так упоительно расписывали все прелести работы QA-специалиста, что я решился на беседу. Беседа была не долгой: минут 10-15 они пытались понять что я знаю и умею, еще минут 10 они рассписывали прелести направления и проект, на который ищут человека. Долгим было тестовое задание: мне дали «потыкать» разарабатываемое Web-приложение (это я сейчас знаю, что это была оттестированная версия, в которой имелись известные баги, которые, собственно, я и должен был найти). Явилась ли эта беседа причиной того, что собеседование на должность верстальщика со мной перенесли на другую дату, или это просто случайное совпадение, но домой я уезжал полный впечатлений и с обещанием девушек связаться со мной втечение двух ближайших недель с результатами нашей беседы. Еще более удивительным было письмо с предложением позиции junior test engineer, пришедшее уже на следующий день. Понятное дело, что ждать собеседование на верстальщика я уже не стал, а в тестировщики пошел с надеждой и верой, что работая в IT уже легче прокачиваться и перемещаться.
Как мне рассказали уже спустя год — такое быстрое предложение было вызвано тем, что я нашел серьезные баги, которые пропустила их QA команда. В довесок им понравились мои знания верстки.

Карьерный рост, или когда же?
#1. Еще не Junior.
Все началось с испытательного срока. По условиям контракта — испытательный срок 3 месяца, с выплатой 50% от договоренной зп (это был мой первый опыт получения зарплаты в конверте :) ). Во время испытательного срока — интенсивные курсы QA инженера. Каждый день 1-2 тестовых задания: приложения в которых нужно найти как можно больше багов. Были и взлеты и падения. Каждое рабочее утро у меня начиналось с подъема в 6.00, быстрого завтрака и 2 часовой езды на ОТ. 9 часов работа с перерывом на обед и 2 часа обратной дороги домой. Первой зп еле хватило на покрытие транспортных расходов. Был и не приятный момент: на одном из тестовых заданий я очень сильно «засыпался», набрал всего 10% — даже ставился вопрос о прекращении дальнейшего сотрудничества. Договорились еще пару недель потренировать меня, а уже потом принимать решение. Испытательный срок закрыли досрочно. За 2 месяца интенсивных тренировок меня превратили в…

#2. Junior Test Engineer.
Для меня, как для человека приехавшего из глубинки и до этого работавшего на гос. контору, все в IT было в диковинку. Особенно непривычным было отношение в компании к сотрудникам. Никто на тебя не орет без повода, у всех есть конкретные задачи, никто не контролирует тебя по 10 раз на дню. Я попал в рай… Но по мере роста моих навков, я начал опускатья на землю. Вся учеба сводилась к тренировкам в тестировании, написании тестовых сценариев, оформлению багов и изучению английского. В общем благодаря первым пунктам изучать английский получалось так себе. Изучать же что-то в девелопменте не получалось вообще. Вот таким мне запомнился мой первый год в IT.

#3. Test Engineer.
Прошел почти год. Приставка junior «отвалилась», немного выросла зп. За зиму очень сильно задолбало по 4 часа в день тратить на дорогу. На семейном совете было принято решение перебираться в столицу. К концу лета сняли однокомнатную квартиру на окраине в старой хрущевке. По аренде как раз смогли вписаться в мой бюджет на дорогу. Снова жизнь на одну зп, т.к. у жены поиски с работой не клеились еще похлеще моих. Прошла эйфория от устройства в IT. Завершился «мой» первый проект (к слову не удачно, прогорел), команду расформировали по другим проектам. Вот тут я в полной мере ощутил, что такое сидеть на «бэнче» для тестировщика. Если разработчик сидя на «бэнче» прокачивает свои скилы, посещая различные курсы и просматривая различные видео, то «скамейка» тестировщиков — это полный «трэш». Тебя пихают во все проекты где нужна помощь. За неделю ты можешь поучавствовать в тестировании 2-3 проектов. Начали появлять негативные эмоции… Незаметно подкралась зима… Новый 2011 год!
Через пару недель после НГ, ко мне в личку постучался коллега, недавно перешедший в другую контору. Им на проект нужен был срочно тестировщик. Он предлагал мне пройти собеседование. Собеседование было чисто формальным. Спросили умею ли я писать sql запросы, дали 2-3 задания на написание запросов с использованием JOIN. Спросили на какую зп я согласился бы перейти к ним.
Дали на 25% больше — я почти не думал над принятием решения о переходе. Новая компания, новый проект, большой, я бы даже сказал, огромный. Более 200 членов команды только в Минске. Приятным бонусом оказались командировки в Москву и ежеквартальные денежные бонусы. Жизнь стала налаживаться, жена нашла хоть и не высокооплачиваемую работу, но по душе. С долгами расчитываться стало веселей. В новой компании опять пришлось проходить обучение (которое заняло 3 месяца), тоже был испытательный срок, который закрыил уже через месяц. ЗП на испытательном сроке не урезали.

#4. Middle.
В разных компаниях эту позицию называют по разному (middel, senoir, вообще без приставки), но в большинстве это как раз серединная позиция. 3+ года опыта работы в IT. Мне удалось закрепиться в отрасли. К моему мнению стали прислушиваться. Появилось немного времени на обучение. Этой компании я благодарен за курсы английского языка. После 2-х лет занятий я стал немного понимать язык на слух, без проблем читать техническую документацию по проекту, начал местами понимать статьи в англязычных газетах. Спустя год проект закрылся, такую большую команду не так-то легко разместить по другим проектам. Разработчикам предложили пройти переподготовку на Java. Т.к. я все равно находился на «бэнче», мне удалось уговорить ПМ-а записать и меня на эти курсы. Так состоялось мое первое знакомство с Java. Длилось оно не долго — 4 занятия. Причины две: курс был расчитан на разработчиков с опытом работы 3+ года, и меня перевели на новый проект. На новом проекте помимо ручного тестироваиня, я соприкоснулся с автоматизированными тестами. Тесты писались на Delphi-script :). Через пол года на новом проекте начали урезать бюджет, я как последний пришедший — стал первым ушедшим. Опять «бэнч»… и ожидание пополнения в семье.

#5. A как живут мидлы в Европе?
В один из апрельских дней я как-то повстречал одно из коллег еще по первому проекту. Мы разговорились. Коллега уже год как работал в Риге, был очень доволен переездом. Потом, шутя предложил мне написать резюме и передать ему. Я написал какую-то ерунду (не писатель я, а инжинер) на одну страницу. Через неделю раздался звонок с не беларусского номера. Меня приглашали побеседовать через Skype. Была не была. Беседа велась на русском. Просто пообщались за жизнь. HR с той стороны предложил попробовать пройти собеседование на новый стартующий проект.
Все прошло очень быстро. Через 2 дня беседа с HR: вот тут по моим знаниям прошлись «танком», благо на русском. Так же проверили уровень моего разговорного английского. Еще через 2 дня техническое собеседование — 10 заданий и 30 минут времени, все это онлайн (я дома перед компом, они толпой на другом конце «провода»). Через неделю я получил офер. Запустился процесс оформления документов. Через месяц я стал папой. Еще через месяц я прогуливался по старым улочкам Риги и не мог поверить в произошедшее. Еще 4.5 года назад я был простым безработным парнем, с огромным долгом перед государством. Да, с долгом удалось без проблем рассчитаться перед началом оформления документов.
Старт нового проекта дал возможность прокачаться в автоматизации. Т.к. первых 3-4 месяца тестировать было особо нечего, руководство проекта приняло решение осваивать автоматизацию Selenium + Nodejs (не слишком популярный стек на то время). Так я стал автоматизатором. за 3 года на проекте я создал свой фреймворк с нуля, успел его уже дважды переписать.

#6. Lead.
На волне успешного проекта по автоматизации я взял еще одну высоту. За это время успел 2 раза отказаться от позиции team lead (не люблю я митинги, мне нравится возиться с кодом). Обучил своего первого падавана :). Провел несколько собеседований на позицию тестировщика. Но все это не то, к чему я когда-то стремился.

Ну как же без его величества, случая
Летом 2016 я случайно наткнулся на проект javarush.ru, первых 10 уровней пролетел за 2 вечера. Аппетит приходит во время еды, тем более отпуск на носу. О! да тут еще и скидка на год…
В общем вторую десятку уровней осилил за 2 недели отпуска в горах. Осень… Загрузка на проекте, рождение второго ребенка, оформление документов, а еще и грядущий апдейт 2.0 — перерыв на 3 месяца. НО! перерыв только для javarush, но не для JAVA. После отпуска я не давал покоя коллегам из серверной команды, задавая им вопросы по непонятным темам с javarush, показывал им куски своего кода, рассказывал свои логические решения, «подсматривал» их код в репозитории. Моя активность не осталась не замеченой. В конце ноября лид серверной команды предложил мне попробовать свои силы в Java EE на благо проекта. Для начала это были пробные шаги — 1-2 дня в неделю. К весне — 50/50 junior java developer/lead test engineer. По весне собрав всю волю в кулак — мужественно до 2-3 часов ночи продолжил свои сражения на полях javarush.

Эпилог
На сегодняшний день я работаю в проекте средней величины. 75% времени — я java developer, 25% — test automation engineer.
Как видите, не всегда нужны портфолио. В моем случае реальное собеседование было только одно — при переезде в Ригу.
Что касается изучения английского — в Риге в кинотеатрах фильмы идут на языке оригинала, это явилось толчком к прокачке восприятия на слух. Очень много технических курсов смотрю на английском. Мультфильмы с детьми тоже стараемся смотреть на языке оригинала. В Риге курсов по английском никаких не проходил, язык качаю просмотром видео контента и прослушиванием подкастов, а так же общением с коллегами.
Что касается собеседований (и как испытатель, и как испытуемый) — не старайтесь придать себе вес понятиями, в которых плаваете. Вывести на чистую воду очень легко, а вот впечатление о себе подпортите. Лучше честно признаться, что с данным материалом не сталкивался, но при необходимости все подтяну. Перед собеседованием не поленитесь продумать, что вы хотите/собираетесь рассказывать о себе, о своих предыдущих проектах, о своих целях на ближайшие 3-5 лет. На моем первом собеседовании вопрос «кем я себя вижу через 3 года» поставил меня в тупик.
Что касается текущего проекта. Проект построен на Spring технологии. Поддерживается «зоопарк» СУБД, для их контроля используется Liquibase (система контроля и версионинга БД). Очень много интеграций с различными системами. Во все это меня «погрузили» даже без малейшего представления что такое Spring, Hibernate и т.п. Во всем этом приходится разбираться на ходу. Параллельно просматриваю видео туториалы, читая отрывками книги.

P.S.
Эта статья родилась из ответа на один из комментариев к предыдущей success story. Попытавшись начать отвечать, понял, что для комментария уже слишком много.
Не бойтесь ошибаться.
  • ,

task01.task0134 - не проходит на корректном решении

public static long getVolume(final int a, final int b, final int c) {
        return 1000 * a * b * c;
    }

Решение выше принимается как верное, но если результат перемножения больше int — ответ получается не правильный.
public static long getVolume(final int a, final int b, final int c) {
        return 1000L * a * b * c;
    }

А это решение не принимается.

P.S.: проблема была актуальна на момент конца февраля.
  • ,

task33.task3310 - Задание 10.

Shortener (10)


Создай и реализуй класс FileStorageStrategy. Он должен:
10.1. Реализовывать интерфейс StorageStrategy.
10.2. Использовать FileBucket в качестве ведер (англ. bucket).
Подсказка: класс должен содержать поле FileBucket[] table.
10.3. Работать аналогично тому, как это делает OurHashMapStorageStrategy, но удваивать количество ведер не когда количество элементов size станет больше какого-то порога, а когда размер одного из ведер (файлов) стал больше bucketSizeLimit.
10.3.1. Добавь в класс поле long bucketSizeLimit.
10.3.2. Проинициализируй его значением по умолчанию, например, 10000 байт.
10.3.3. Добавь сеттер и геттер для этого поля.
10.4. При реализации метода resize(int newCapacity) проследи, чтобы уже не нужные файлы были удалены (вызови метод remove()).
Проверь новую стратегию в методе main(). Учти, что стратегия FileStorageStrategy гораздо более медленная, чем остальные. Не используй большое количество элементов для теста, это может занять оооочень много времени.
Запусти программу и сравни скорость работы всех 3х стратегий.

Требования:
1. Класс FileStorageStrategy должен поддерживать интерфейс StorageStrategy.
2. В классе FileStorageStrategy должны быть созданы все необходимые поля (согласно условию задачи).
3. Методы интерфейса StorageStrategy должны быть реализованы в FileStorageStrategy таким образом, чтобы обеспечивать корректную работу Shortener созданного на его основе.

Последнее требование ни в какую не хочет выполняться.
Да и второе зачлось только когда добавил некое мифическое поле (о котором нигде ни слова кроме как на help.javarush.ru) private long maxBucketSize
Перечитал все вопросы по этой подзадаче тут и на help.javarush.ru. Прошелся поиском по github-у (не нашел ни одного коммита решенной задачи для новой версии).

package com.javarush.task.task33.task3310.strategy;

import java.util.Objects;
import java.util.stream.IntStream;

public class FileStorageStrategy implements StorageStrategy {
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    private static final long DEFAULT_BUCKET_SIZE_LIMIT = 10000;
    private FileBucket[] table = new FileBucket[DEFAULT_INITIAL_CAPACITY];

    private long bucketSizeLimit = DEFAULT_BUCKET_SIZE_LIMIT;
    private int size;
    private long maxBucketSize;

    public FileStorageStrategy() {
        for (int i = 0; i < table.length; i++) {
            table[i] = new FileBucket();
        }
    }

    public long getBucketSizeLimit() {
        return bucketSizeLimit;
    }

    public void setBucketSizeLimit(final long bucketSizeLimit) {
        this.bucketSizeLimit = bucketSizeLimit;
    }

    public int getSize() {
        return size;
    }

    public long getMaxBucketSize() {
        return maxBucketSize;
    }

    private int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    private int indexFor(final int hash, final int length) {
        return hash & (length - 1);
    }

    private Entry getEntry(final Long key) {
        final int hash = (key == null) ? 0 : hash(key.hashCode());
        final int index = indexFor(hash, table.length);

        for (Entry e = table[index].getEntry(); e != null; e = e.next) {
            final Long eKey = e.key;
            if (e.hash == hash && Objects.equals(key, eKey))
                return e;
        }
        return null;
    }

    private void resize(final int newCapacity) {
        final FileBucket[] newTable = IntStream
                .range(0, newCapacity)
                .mapToObj(i -> new FileBucket())
                .toArray(FileBucket[]::new);
        transfer(newTable);
        table = newTable;
        maxBucketSize = 0;
        for (final FileBucket bucket : table) {
            final long currentBucketSize = bucket.getFileSize();
            maxBucketSize = currentBucketSize > maxBucketSize ? currentBucketSize : maxBucketSize;
        }
    }

    private void transfer(final FileBucket[] newTable) {
        for (final FileBucket bucket : table) {
            Entry e = bucket.getEntry();
            while (e != null) {
                final Entry next = e.next;
                final int index = indexFor(e.hash, newTable.length);
                e.next = newTable[index].getEntry();
                newTable[index].putEntry(e);
                e = next;
            }
            bucket.remove();
        }
    }

    private void addEntry(final int hash, final Long key, final String value, final int bucketIndex) {
        final Entry e = table[bucketIndex].getEntry();
        table[bucketIndex].putEntry(new Entry(hash, key, value, e));
        size++;

        final long currentBucketSize = table[bucketIndex].getFileSize();
        maxBucketSize = currentBucketSize > maxBucketSize ? currentBucketSize : maxBucketSize;
        if (maxBucketSize > bucketSizeLimit) resize(2 * table.length);
    }

    private void createEntry(final int hash, final Long key, final String value, final int bucketIndex) {
        table[bucketIndex].putEntry(new Entry(hash, key, value, null));
        size++;
        final long currentBucketSize = table[bucketIndex].getFileSize();
        maxBucketSize = currentBucketSize > maxBucketSize ? currentBucketSize : maxBucketSize;
    }

    @Override
    public boolean containsKey(final Long key) {
        return getEntry(key) != null;
    }

    @Override
    public boolean containsValue(final String value) {
        for (final FileBucket bucket : table) {
            for (Entry e = bucket.getEntry(); e != null; e = e.next) {
                if (Objects.equals(e.value, value))
                    return true;
            }
        }
        return false;
    }

    @Override
    public void put(final Long key, final String value) {
        final int hash = (key == null) ? 0 : hash(key.hashCode());
        final int index = indexFor(hash, table.length);

        if (table[index].getEntry() != null) {
            for (Entry e = table[index].getEntry(); e != null; e = e.next) {
                if (e.hash == hash && Objects.equals(e.key, key)) {
                    e.value = value;
                    return;
                }
            }
            addEntry(hash, key, value, index);
        } else {
            createEntry(hash, key, value, index);
        }
    }

    @Override
    public Long getKey(final String value) {
        for (final FileBucket bucket : table) {
            for (Entry e = bucket.getEntry(); e != null; e = e.next) {
                if (Objects.equals(e.value, value))
                    return e.key;
            }
        }
        return null;
    }

    @Override
    public String getValue(final Long key) {
        final Entry e = getEntry(key);
        return e == null ? null : e.value;
    }
}
  • ,

task36.task3607

Описание класса:
1. Реализует интерфейс Queue.
2. Используется при работе с трэдами.
3. Из этой очереди элементы могут быть взяты только тогда, когда они заэкспарятся, их время задержки истекло.
4. Головой очереди является элемент, который заэкспарился раньше всех.

Мой код — возвращает верное значение в ИДЕЕ, но валидатору что-то не нравится.
Попробовал запустить задачу в браузере — на выходе null.
Возник по ходу вопрос, а как java.util.* работает в браузере, если его нету в classpath?
Буду благодарен за любую подсказку:

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/*
Найти класс по описанию
*/
public class Solution {
    public static void main(final String[] args) {
        System.out.println(getExpectedClass());
    }
    public static Class getExpectedClass() {
        final Set<String> classNames = new HashSet<>();
        final URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
        final URL[] jarsPath = classLoader.getURLs();
        //find classes within rt.jar package
        for (final URL jarPath : jarsPath) {
            if (jarPath.toString().endsWith("rt.jar")) {
                try {
                    final JarFile jarFile = new JarFile(jarPath.getPath().toString());
                    final Enumeration<JarEntry> en = jarFile.entries();
                    while (en.hasMoreElements()) {
                        final String fileName = en.nextElement().getName();
                        if (fileName.startsWith("java/util") && fileName.endsWith(".class")) {
                            classNames.add(fileName);
                        }
                    }
                } catch (final IOException ignored) {
                }
            }
        }
        //find class with Queue interface implementation
        for (final String className : classNames) {
            try {
                final String fileName = className.substring(0, className.length() - 6).replace('/', '.');
                final Class cls = Class.forName(fileName);
                if (Queue.class.isAssignableFrom(cls) && cls.getEnclosingClass() == null) {
                    final Field[] fields = cls.getDeclaredFields();
                    final Method[] methods = cls.getDeclaredMethods();
                    if (Arrays.stream(methods)
                            .filter(m -> m.getName().contains("peekExpired"))
                            .flatMap(m -> Arrays.stream(fields))
                            .anyMatch(f -> f.getType().equals(Thread.class))) {
                        return cls;
                    }
                }
            } catch (final ClassNotFoundException ignored) {
            }
        }
        return null;
    }
}
  • ,

level22.lesson13.task02 - ПРАВИЛЬНОЕ условие задачи!!!

Правильное условие задачи!!!

В метод main первым параметром приходит имя файла:
  • кодировка файла — UTF-8;
  • содержимое файла — символы из кодировки Windows-1251 (как пример строка win1251TestString).
В метод main вторым параметром приходит имя файла:
  • кодировка файла — Windows-1251;
  • в него должно записаться содержимое первого файла в виде нормальночитаемой фразы.
  • ,

level20.lesson10.bonus04

Перепробовал все тесты имевшиеся в этой ветке.
Все тесты проходят на ура, даже с добавлением/удалением null.
Сервер не принимает решение :(.

Update: убрал код, дабы администрация не удалила топик. Читайте мой последний комментарий ;).