Как вы тестируете / изменяете непроверенный и непроверяемый код?

StackOverflow https://stackoverflow.com/questions/7287

Вопрос

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

Это было полезно?

Решение

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

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

public class MyClass {
    public MyClass() {
        // undesirable DB logic
    }
}

становится

public class MyClass {
    public MyClass() {
        loadFromDB();
    }

    protected void loadFromDB() {
        // undesirable DB logic
    }
}

и тогда ваш тест будет выглядеть примерно так:

public class MyClassTest {
    public void testSomething() {
        MyClass myClass = new MyClassWrapper();
        // test it
    }

    private static class MyClassWrapper extends MyClass {
        @Override
        protected void loadFromDB() {
            // some mock logic
        }
    }
}

Это несколько неудачный пример, потому что в этом случае вы могли бы использовать DBUnit, но на самом деле я недавно сделал это в аналогичном случае, потому что хотел протестировать некоторые функциональные возможности, совершенно не связанные с загружаемыми данными, так что это было очень эффективно.Я также обнаружил, что такое раскрытие элементов полезно в других подобных случаях, когда мне нужно избавиться от некоторой зависимости, которая была в классе в течение длительного времени.

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

Это немного халтурно, но я нашел это весьма полезным.

Другие советы

@валтерс

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

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

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

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

Я твердо верю, что неудачный тест должен быть исправлен как можно скорее, точно так же, как вы исправили бы код, который не удалось скомпилировать как можно скорее.

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

Если вы пишете Java (скорее всего, так оно и есть), ознакомьтесь http://www.easymock.org/ - может быть полезно для уменьшения сцепления в целях тестирования.

Я прочитал "Эффективная работа с устаревшим кодом" и согласен, что это очень полезно для работы с "непроверяемым" кодом.

Некоторые методы применимы только к скомпилированным языкам (я работаю над "старыми" приложениями PHP), но я бы сказал, что большая часть книги применима к любому языку.

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top