Показаны сообщения с ярлыком тестирование. Показать все сообщения
Показаны сообщения с ярлыком тестирование. Показать все сообщения

2013-07-25

The Art of Unit Testing

Великолепная книга от Roy Osherove. Однозначно попадает в категорию 'И почему она не попалась мне пять лет назад?', однозначный мастрид. Не смотря на то, что все примеры идут для .NET, польза будет и для явистов, поскольку принципы работы отличаются только деталями.

Книга состоит из 4 разделов: базовые принципы, моки и стабы, тестируемый код, внедрение тестирования в легаси среде. И если первые два раздела я уже освоил, достаточно натанцевавшись по граблям (читал чтобы убедиться, что ничего не пропустил), то последние дали богатую пищу для размышлений.

A unit test should have the following properties: 
❂ It should be automated and repeatable.
❂ It should be easy to implement.
❂ Once it’s written, it should remain for future use. 
❂ Anyone should be able to run it.
❂ It should run at the push of a button.
❂ It should run quickly.

These are the FICC properties: fast, isolated, configuration-free, and consistent. If it’s hard to write such a test, or it takes a long time to write it, the system isn’t testable.

Противоречие между инкапсуляцией и тестируемостью решается очень легко:
Some people feel that opening up the design to make it more testable is a bad thing because it hurts the object-oriented principles the design is based on. I can wholeheartedly say to those people, “Don’t be silly.” Object-oriented techniques are there to enforce some constraints on the end user of the API (the end user being the programmer who will use your object model) so that the object model is used properly and is protected from unforeseen ways of usage. Object orientation also has a lot to do with reuse of code and the single-responsibility principle (which requires that each class has only a single responsibility). When we write unit tests for our code, we are adding another end user (the test) to the object model. That end user is just as important as the original one, but it has different goals when using the model. 

Ну и, в заключение, совет от Мастера: Finally, as a friend once said, a good bottle of vodka never hurts when dealing with legacy code.

2013-05-31

Тестирование файловой системы

Симуляция реального мира в песочнице
Май выдался богатым на новую и полезную информацию по тестированию: цикл статей от Сергея Теплякова, интересное обсуждение в Радио-Т.

В это же время появилась интересная задача связанная с доступом к файловой системе. Алгоритм:
1. получаем список файлов
2. каким-то образом фильтруем
3. обрабатываем отфильтрованные файлы.

Работа с ФС (по крайней мере в 6-й java) сосредоточена в классе File: list, delete, работа с путями. По сути эти методы относятся к сервису. Хочется иметь интерфейс чтобы убрать зависимость и получить возможность для подмены реализации, ну или хотя бы подмены mock-ами для тестирования.

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

Постановка задачи

По заданному пути
удалить пять файлов
с учетом сортировки по возрастанию
имя которых начинается с цифр
содержащие первой строкой ШИББОЛЕТ.

Решение

Реализация находится здесь, тесты здесь.

Сервис

FileService представляет собой фактически обертку с некоторыми удобными методами

public class FileService {
    /**
     * Get files in path
     */
    public Collection dir(File path) {
        return Arrays.asList(path.listFiles());
    }

    /**
     * Delete path
     */
    public void delete(File file) {
        if (!file.delete()) {
            log.debug("Cannot delete " + file.getPath());
        }
    }

    /**
     * Open file
     */
    public InputStream openStream(File path) throws FileNotFoundException {
        return new FileInputStream(path);
    }

    public Reader openReader(File path) throws FileNotFoundException {
        return new FileReader(path);
    }

    public boolean isDir(File path) {
        return path.isDirectory();
    }
}

Фильтрация

Фильтрация осуществляется при помощи удобнейшего FluentIterable:

public static Iterable filter(FileService fileService, File dir, int count, String mask, String prefix) {
        return FluentIterable
                .from(Ordering.natural().immutableSortedCopy(fileService.dir(dir)))
                .filter(new NamePredicate(mask))
                .filter(new ContentPredicate(fileService, prefix))
                .limit(count)
                .toImmutableList();
    } 

И пары предикатов для фильтрации по имени и содержимому. В предикат фильтрации по содержимому передается сервис файловой системы, плюс в Java 7 можно использовать очень удобный try для работы с Closeable ресурсами:
public class ContentPredicate implements Predicate {
    private FileService fileService;
    private String prefix;

    public ContentPredicate(FileService fileService, String prefix) {
        this.fileService = fileService;
        this.prefix = prefix;
    }

    @Override
    public boolean apply(@Nullable File file) {
        try (BufferedReader br = new BufferedReader(fileService.openReader(file))) {
            return prefix.equals(br.readLine());
        } catch (IOException ioe) {
            ContentPredicate.log.debug("Cannot read file " + file);
        }
        return false;
    }
}

Тестирование

С помощью mockito подменяем возвращаемые значения при тестировании предикатов и фильтра. Сами тесты получаются очень простыми и быстрыми:

@Test
    public void FilterTest() throws FileNotFoundException {
        FileService fileService = Mockito.mock(FileService.class);
        File dir = new File("/test/dir");
        List files = Arrays.asList(
                new File(dir, "100file.name"), // 0
                new File(dir, "bl"), // 1
                new File(dir, "099file.name"), // 2
                new File(dir, "bla"), // 3
                new File(dir, "098file.name"), // 4
                new File(dir, "blah"), // 5
                new File(dir, "097file.name")); // 6
        Mockito.when(fileService.dir(dir)).thenReturn(files);
        Mockito.when(fileService.openReader(files.get(6))).thenReturn(new StringReader("TEST"));
        Mockito.when(fileService.openReader(files.get(4))).thenReturn(new StringReader("TEST"));
        Mockito.when(fileService.openReader(files.get(2))).thenReturn(new StringReader("!!!!"));
        Mockito.when(fileService.openReader(files.get(0))).thenReturn(new StringReader("TEST"));

        Iterable result = Application.filter(fileService, dir, 3, "^\\d+.*$", "TEST");
        Iterator i = result.iterator();
        Assert.assertEquals(files.get(6), i.next());
        Assert.assertEquals(files.get(4), i.next());
        Assert.assertEquals(files.get(0), i.next());
        Assert.assertFalse(i.hasNext());
    }

Итог

Из минусов: имеется обертка, которая нужна лишь для тестирования "DIP головного мозга". Плюсы: классы становятся простыми и легко модифицируемым, самая важную логику становится легко тестировать.

2013-04-24

Бизнес-логика в базе


Типичный опердень
Выборка 41.5%
Фильтрация 5.8%
Обработка 13.4%
Аналитика 17.7%
Пост-обработка 21.6%
Комментарии 0%

Или почему это зло

Если бизнес-логика в базе

Первая по важности - это богатые возможности СУБД: умный оптимизатор, который может работать с индексами и собирать статистику, что драматически сокращает объем считываемых данных и памяти; под рукой дешевые джойны по внешним ключам; агрегатные и аналитические функции; группировка и сортировка; фильтрация.

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

Если бизнес-логика в приложении

Нет размазывание бизнес-логики между приложением, и базой. Что же такого на стороне приложения?
1. Не нужно вырезать и вставлять куски кода, чтобы их прогнать в изолированном окружении
2. Легкая отладка - можно остановиться в любом месте обрабоки и посмотреть на данные
3. Версионирование - кто вообще версионирует код, в базе?
4. Легковесность базы - нет логики, достаточно простого SQL, нет требований к базе, можно на коленке разворачивать схему любой версии на любой пользовательской машине. Когда база всего-лишь тупое хранилище данных, нам для работы достаточно командной строки, в идеале поля для выполнения кода с подсветкой синтаксиса. Сам билд становится переносимым, позволяя развернуть рабочее приложение на чистой машине.
5. Декларативный подход против императивно-функционального - СУБД сама выбирает оптимальный порадок выборки данных, зато во втором варианте процесс обработки можно "читать"
6. Комментарии - их нет. Ноль целых ноль десятых процента. Какой смысл их писать, если один кусочек логики размазывается по всему запросу? В случае пошаговой обработки можно пояснять что мы делаем на каждом этапе и почему.
7. Пошаговость процесса и функциональный стиль написания - позволяет писать тесты. Тестирование кода, работающего с базой - это уже джедайские практики. Я ни разу не видел тестов внутри базы, покрывающих пакеты, хотя инструменты имеются.
8. Возможность написать высокоуровневый DSL, который будет адаптирован к бизнес-домену, что позволит транслировать код в SQL с жутким мультипликатором. На эту тему у ребе metaclass можно прочитать много интересного.

Минусы хранения логики в приложении

1. ORM, которые строят сложные и, порой, неоптимальные запросы. А еще, многие из них не умею работать с курсорами, что усложнает написание кода в функциональном стиле с минимумом побочных эффектов. Претензий много.
2. Резко вырастает объем данных, которые нужно загрузить для выборки, одна из причин, по которой аппликейшен сервер держат поближе к базе данных (в сравнении с расстоянием до клиента).

Итог

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

P.S. Пояснения к легенде

Выборка - это Select и Join,
Фильтрация - Where и Having,
Обработка - различные функции, рассчеты, кейсы,
Аналитика - агрегатные и аналитические функции,
Пост-обработка - захардкоженные константы, сортировка и группировка.

2013-04-09

Код с привидениями

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

2013-03-28

Не ванильное тестирование

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

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

2013-03-26

Кровища и картины анальной боли

Думать - больно: нужно прилагать усилия и тратить энергию, чтобы разобраться. Поэтому, развитие идет, если сохранять status quo еще больнее.

2012-11-25

О бизнесе, программистах и тестировании

Отличный доклад про взаимоотношение разработчиков и бизнеса. С самого начала автор цитирует Билла Гейтса: "Автоматизация эффективного процесса умножает эффективность системы, автоматизация неэффективного процесса умножает неэффективность". Разбирается много вариантов отсутствия здравого смысла и 4 мифа.

Весь доклад, про попытки автоматизировать тестирование при помощи различных инструментов и мифы с этим связанные, примеры успеха и недоумения от результатов. Для именования мифов выбраны игры слов:
Instoolation - вера, что инструмент может решить процессуальные проблемы
Businessting - вера, что бизнес должен писать приемочные тесты
Acceptegration - вера, что тесты бывают либо модульными, либо приемочно-интеграционными
Rolation - вера, что "определенная роль" должны писать тесты в изоляции от коллег, например тестировщики

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

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

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

2012-08-11

Больше тестирования

Суть статей про Юнит-тестирование:

В правильных тестах не должно быть [1]
  • ввода-вывода
  • обращения к ресурсам
  • доступа к базе 
  • доступа к другим приложениям
Печально, что все этим же и заканчивается: если вы не можете протестировать ваш класс, то, скорее всего, он неправильно спроектирован. Для сложных случаев придется использовать магию моков, стабов и фикстур.

2012-08-04

Зрелость тестирования

Подумалось, что зрелость тестирования похожа на CMM и тоже разбивается на несколько стадий.

Начальная стадия


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

Повторяемая стадия


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

Установленная стадия


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

Уровень 1.

Пишутся тесты для простейших классов и методов. С одним входом и выходом.

Уровень 2.

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

Уровень 3.

Тестовые данные разбиваются на фикстуры и каждый тест запускается в изолированной среде.

Уровень 4.

Взаимодействие с внешним миром тестируется при помощи стабов и моков.

Уровень 5.

Тестируется пользовательский интерфейс?
Пишутся интеграционные тесты?

Управляемая стадия


Видел одним глазом. Запрет на внесение нового функционала без покрытия тестами и документирования внесенного кода.

Оптимизированная стадия


1. Magic
2. ???
3. Profit