2012-10-15 1 views
2

В настоящее время мы с коллегами вводим модульные тесты в нашу устаревшую кодовую базу Java EE5. Мы используем в основном JUnit и Mockito. В процессе написания тестов мы заметили, что несколько методов в наших EJB трудно тестировать, потому что они делали много всего сразу.Как я могу использовать методы рефакторинга и модульных тестов для устаревших методов Java EE5 EJB?

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

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

  • consumeMessages

    • acknowledgePreviouslyDownloadedMessages

    • getNewUnreadMessages

    • addExtraMessages (в зависимости от степени сложных условий)

    • markMessagesAsDownloaded

    • serializeMessageObjects

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

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

Итак, я здесь.

Как вы можете сблизить легко проверяемые низкоуровневые методы и избегать загромождения интерфейсов? В нашем случае интерфейсы EJB.

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

Вы бы порекомендовали другие общие шаблоны OO или шаблоны Java EE?

ответ

1

На первый взгляд, я бы сказал, что нам, вероятно, необходимо ввести новый класс, который бы 1) разоблачил общедоступные методы, которые могут быть проверены модулем, но 2) не подвергаться воздействию открытый интерфейс вашего API.

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

В таком случае, что я хотел бы сделать что-то вроде этого:

public class Engine { 
    public void doActionOnEngine() {} 
    public void doOtherActionOnEngine() {} 
} 


public class Car { 
    private Engine engine; 

    // the setter is used for dependency injection 
    public void setEngine(Engine engine) { 
    this.engine = engine; 
    } 

    // notice that there is no getter for engine 

    public void doActionOnCar() { 
    engine.doActionOnEngine(); 
    } 

    public void doOtherActionOnCar() { 
    engine.doActionOnEngine(); 
    engine.doOtherActionOnEngine(), 
    } 
} 

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

0

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

Обычно программа настроена на использование определенных ресурсов, таких как конкретный файл, IP-адрес, имя хоста и т. Д. Чтобы противостоять этому, вам необходимо реорганизовать программу для использования инъекции зависимостей , Обычно это делается путем добавления параметров к конструктору, которые заменяют значения ahrdcoded.

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

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

В какой-то момент вы, вероятно, обнаружите, что можете значительно упростить свой код, используя эти шаблоны проектирования: Command, Composite, Adapter, Factory, Builder и Facade. Это самые распространенные модели, которые сокращают беспорядок.

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

1

Принцип зависимости (DI) и принцип одиночной ответственности (SRP) очень взаимосвязаны.

SRP в основном заявляет, что каждый класс должен делать только одно дело и делегировать все другие вопросы отдельным классам. Например, ваш метод serializeMessageObjects должен быть извлечен в его собственный класс - назовем его MessageObjectSerializer.

DI означает ввод (пропускание) объекта MessageObjectSerializer в качестве аргумента для вашего объекта MessageQueue - либо в конструкторе, либо в вызове метода consumeMessages. Вы можете использовать рамки DI для этого, но я рекомендую сделать это вручную, чтобы получить концепцию.

Теперь, если вы создаете интерфейс для MessageObjectSerializer, вы можете передать его MessageQueue, а затем получите полное значение шаблона, так как вы можете создать mocks/stubs для легкого тестирования. Внезапно consumeMessages не должен обращать внимание на то, как ведет себя serializeMessageObjects.

Ниже я попытался проиллюстрировать рисунок. Обратите внимание, что если вы хотите протестировать consumeMessages, вам не нужно использовать объект MessageObjectSerializer. Вы можете сделать макет или заглушку, которая делает именно то, что вы хотите, и передать ее вместо конкретного класса. Это действительно упрощает тестирование. Прошу простить синтаксические ошибки. У меня не было доступа к Visual Studio, поэтому он написан в текстовом редакторе.

// THE MAIN CLASS 
public class MyMessageQueue() 
{ 

    IMessageObjectSerializer _serializer; 



    //Constructor that takes the gets the serialization logic injected 
    public MyMessageQueue(IMessageObjectSerializer serializer) 
    { 
     _serializer = serializer; 

     //Also a lot of other injection 
    } 


    //Your main method. Now it calls an external object to serialize 
    public void consumeMessages() 
    { 
     //Do all the other stuff 

     _serializer.serializeMessageObjects() 
    } 
} 

//THE SERIALIZER CLASS 
Public class MessageObjectSerializer : IMessageObjectSerializer 
{ 
    public List<MessageObject> serializeMessageObjects() 
    { 
     //DO THE SERILIZATION LOGIC HERE 
    } 
} 


//THE INTERFACE FOR THE SERIALIZER 
Public interface MessageObjectSerializer 
{ 
    List<MessageObject> serializeMessageObjects(); 
} 

EDIT: К сожалению, мой пример в C#. Надеюсь, вы можете использовать его в любом случае :-)

+0

Спасибо за пример. В любом случае, это достаточно близко, чтобы Java была почти неразличимой! Я посмотрю, как я могу извлечь свои задачи в этот вид объекта. –

Смежные вопросы