Статьи

Разработка дополнений Roo: тестирование конфигураций XML

В последнем посте мы обсуждали модульное тестирование кода дополнения Roo. Я чувствую, что это так же важно, как и тестирование любого другого фрагмента кода Java, учитывая, что каждый раз, когда вы запускаете команду в контейнере, вам буквально приходится загружать ее, обновлять пакет OSG i, а затем тестировать. Цикл обратной связи слишком длинный, чтобы исправлять небольшие раздражающие ошибки, такие как неправильный анализ XML- документа.

Ру «Расширенные дополнения» и Конфигурация

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

С помощью надстройки CoffeeScript мы хотим манипулировать файлом pom.xml — то, для чего нам не нужен контейнер. Roo использует эту хорошую встроенную библиотеку JAXP (и, конечно, реализацию Apache). Как сказал бы Бен Алекс, «стандартная Java». Таким образом, мы должны быть в состоянии легко протестировать его.

Класс CoffeescriptOperationsImpl — тестируется!

В последнем блоге мы показали, как тестировать объект CoffeescriptCommands, который делегирует вызовы bean-компоненту CoffeescriptOperations OSGi, который реализуется классом CoffeescriptOperationsImpl. Это где работа надстройки делается. Итак, давайте проверим это.

Настройка тестового класса и Mockito

Как и в прошлый раз, нам нужно настроить Mockito. Предположим, вы прочитали этот пост и установили правильные зависимости.

Нам нужно проверить, работает ли наш код операций и вызывает ли он соответствующие службы Roo. Итак, давайте создадим наш тестируемый компонент, а затем посмеемся над соавтором:

public class CoffeescriptOperationsImplTest {
  private CoffeescriptOperationsImpl coffeescriptOperations; 

  @Before
  public void setUp() {
    coffeescriptOperations = new CoffeescriptOperationsImpl();
    coffeescriptOperations.projectOperations = 
      Mockito.mock(ProjectOperations.class);
  }
  ...
 

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

h2. Просмотр нашего тестируемого метода — setup ()

Давайте посмотрим на наш метод настройки:

public void setup(String coffeeDir, 
                String outputDirectory, boolean bare) {
  String moduleName = projectOperations.getFocusedModuleName();
  Element coffeePluginElement = getCoffeeScriptPluginElement();
  Document document = coffeePluginElement.getOwnerDocument();

  if (bare) {
    addTextElement(document, coffeePluginElement, "bare", "true");
  } else {
    addTextElement(document, coffeePluginElement, "bare", COFFEE_DEFAULT_BARE_SETTING);
  }

  if (coffeeDir != null && coffeeDir.trim().length() > 0) {
    addTextElement(document, coffeePluginElement, "coffeeDir", coffeeDir);
  } else {
    addTextElement(document, coffeePluginElement, "coffeeDir", COFFEE_DEFAULT_SRC_DIRECTORY);
  }

  if (outputDirectory != null && outputDirectory.trim().length() > 0) {
    addTextElement(document, coffeePluginElement, "coffeeOutputDirectory", outputDirectory);
  } else {
    addTextElement(document, coffeePluginElement, "coffeeOutputDirectory", COFFEE_DEFAULT_OUTPUT_DIRECTORY);
  }

  projectOperations.addBuildPlugin(moduleName, new Plugin(coffeePluginElement));
}

It’s clear that we have a LOT of branches in this code, but that’s because we’re taking input from our command itself. I’ll lie here, and tell you that I’ve written tests against all of these branches, but again, I said I’m lying — and in a further lie, I’ll tell you that «I’m gonna get to it!» However, here’s why lying doesn’t help — I’m sure I have bugs in this code, and I really need to verify it all.

Oh, and I was thinking — I have a few private methods to help me keep the code organized and modular… Perhaps I should test those too but that leads the way of code smell… Interesting read BTW.

Reviewing the tasks in the method

Ok, the method does a few things:

1. Asks a helper method for the Configuration XML file as a basis for the Maven plugin.
2. Does a couple of gyrations so that we can maniuplate the plugin nodes with the DOM API — since Roo’s Maven object model is essentially a thin wrapper around the XML API we have to think more in XML. This is something I’ll be exploring in the future.
3. Sets the options the user passed in.
4. Adds the build plugin to the Maven build.

Ultimately, though, we need to see if:

1. Given a call to setup(), and the appropriate parameters,
2. Does the Plugin contain the proper information

Our test method for the setup process

Ok,

@Test
public void testSetupCoffeescript() {

  when(coffeescriptOperations.projectOperations
     .getFocusedProjectName()).thenReturn("foo");

  // a way for Mockito to grab passed input parameters for testing
  ArgumentCaptor<Plugin> pluginCaptor = 
     ArgumentCaptor.forClass(Plugin.class);

  // invoke our method
  coffeescriptOperations.setup("baz", "bar", false);

  // did we call addBuildPlugin? Also, notice we capture what the
  // method passed to the mocked projectOperations.addBuildPlugin method
  // for the plugin XML Element code
  verify(coffeescriptOperations.projectOperations)
     .addBuildPlugin(any(String.class), pluginCaptor.capture());

  // Since the plugin has been called and we've captured the method's 
  // second argument, we'll pluck it out and take a gander...
  Plugin coffeescriptPlugin = pluginCaptor.getValue();

  // make sure they passed something!
  assertNotNull(coffeescriptPlugin);

  // checks against the model
  Assert.assertEquals("false", coffeescriptPlugin.getConfiguration()
      .getConfiguration().getElementsByTagName("bare")
      .item(0).getTextContent());

  Assert.assertEquals("bar", coffeescriptPlugin.getConfiguration()
      .getConfiguration().getElementsByTagName("coffeeOutputDirectory")
      .item(0).getTextContent());

  Assert.assertEquals("baz", coffeescriptPlugin.getConfiguration()
      .getConfiguration().getElementsByTagName("coffeeDir")
      .item(0).getTextContent());
}

Mockito’s ArgumentCaptor

I guess this is really a testing tools article, rather than a Roo article.

The ArgumentCaptor API is really useful to see what the values were for a mock that was called by your class under test. This is a way to verify that we were passing in the right plugin configuration to our Roo projectManager, which, after all, we aren’t testing. That’s the Roo team’s job!

Wrap-up

Looking at it from a distance, Roo is just a Java platform that generates, manipulates and configures applications. So it can really do anything. However, rather than testing by re-deploying 10 times, we can run a fast Junit test 10 times instead.

If you go to my Silly Weasel link at the top of the blog page, you’ll see the OBR URL for getting my Coffeescript, jQuery and (soon) Site add-ons. You can browse my maven repository (the same URL without the repository.xml ending) and grab the source for anything I’ve released.

Please send me comments if you’d like to add to this discussion.