Хороший пример объектно-ориентированного дизайна в Java

У меня есть класс Java под названием TestExecutor, который отвечает за starting тест. Запуск теста включает в себя ряд этапов:

- Update test repository
- Locate the test script
- Create result empty directory
- Execute command
- Parse output
- Update database

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

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

public void start() throws TestExecuteException {
    try {
        updateRepository();
        locateScript();
        createResultDirectory();
        executeCommand();
        parseOutput();
        updateDatabase();
    catch(a,b,c) {
    }
}

private updateRepository() {
    // Code here
}
// And repeat for other functions

3 ответа

Я бы так и сделал. Во-первых, выполните контракт, который должен иметь каждый шаг теста.

interface TestCommand{
  void run();
}

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

//in your test class do this.
List<TestStep> testCommands = new ArrayList<>();
testCommands.add(new UpdateRepoCommand());
testCommands.add(new LocateScriptCommand());
// and so on....

Теперь выполните все ваши шаги в хронологическом порядке.

public void start(testSteps) throws TestExecuteException {
    try {
        for(TestCommand command : testCommands){
           command.run()
    }        
    catch(Exception e) {
      //deal with e
    }
}

Более того, как описано выше в CK, следуйте принципу SOLID на этих этапах тестирования. Введите зависимости и напишите для них модульный тест отдельно.

Ну, твой класс выглядит нормально для меня.

Я знаю, что это не очень хороший дизайн, так как мой класс делает слишком много

Поскольку класс несет единоличную ответственность, количество методов не имеет значения.

Проверьте этот шаблон метода проектирования шаблона. Ваш класс делает что-то похожее на то, что абстрактно Game класс делает.

public abstract class Game {
   abstract void initialize();
   abstract void startPlay();
   abstract void endPlay();

   //template method
   public final void play(){

      //initialize the game
      initialize();

      //start game
      startPlay();

      //end game
      endPlay();
   }
}

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

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

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

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

Тем не менее, я бы начал с группировки связанных функций в разные классы. Например, updateRepository() а также updateDatabase() может быть перемещен в отдельный класс под названием DatabaseHelper, так же locateScript() а также createResultDirectory() кажется, что операции с диском и могут быть перемещены в отдельный класс под названием DirectoryHelper, Я верю, что вы понимаете суть этого. То, что вы только что достигли, было разделение проблем.

Теперь, когда у вас есть отдельные классы, вам нужно собрать их вместе и заставить их работать. Ваш TestExecutor может продолжать иметь methods что вы перечислили. Единственным отличием будет то, что эти методы теперь будут делегировать свою работу отдельным классам, которые мы создали выше. За это, TestExecutor понадобится ссылка на DatabaseHelper а также DirectoryHelper классы. Вы можете просто создать эти классы прямо внутри TestExecutor, Но это будет означать, что TestExecutor тесно связан с реализацией. Вместо этого вы можете спросить код снаружи TestExecutor поставлять DatabaseHelpe а также DirectoryHelper использовать. Это известно как Инверсия Зависимости через Инъекцию Зависимости. Преимущество этого подхода в том, что теперь вы можете передавать любой подкласс DatabaseHelper а также DirectoryHelper в TaskExecutor и он не должен знать детали реализации. Это облегчает в модульном тестировании TaskExecutor высмеивая эти зависимости вместо передачи фактических экземпляров.

Я оставлю остальную часть SOLID принципы для вас, чтобы изучить, реализовать и оценить.

Другие вопросы по тегам