Java - Como trocar um caminho de arquivo de recurso por um arquivo de teste durante o teste de unidade?

8

Eu tenho um arquivo de recurso que tem algumas configurações. Eu tenho uma classe ResourceLoader que carrega as configurações deste arquivo. Atualmente, esta classe é uma classe singleton instanciada ansiosamente. Assim que essa classe é carregada, ela lê as configurações do arquivo (caminho do arquivo armazenado como um campo constante em outra classe). Algumas dessas configurações não são adequadas para testes de unidade. Por exemplo, eu tenho um tempo de suspensão do encadeamento neste arquivo, que pode levar horas para o código de produção, mas eu gostaria que fosse alguns milissegundos para testes de unidade. Então, eu tenho outro arquivo de recurso de teste que tem um conjunto diferente de valores. Minha pergunta é como faço para trocar o arquivo principal de recursos por esse arquivo de teste durante o teste de unidade? O projeto é um projeto inteligente e estou usando o testng como estrutura de teste. Estas são algumas das abordagens que eu

  1. Use @BeforeSuite e modifique a variável constante FilePath para apontar para o arquivo de teste e use @AfterSuite para apontar de volta para o arquivo original. Isso parece estar funcionando, mas acho que porque a classe ResourceLoader é instantaneamente instanciada, não há garantia de que o método @BeforeSuite sempre seja executado antes do carregamento da classe ResourceLoader e, portanto, propriedades antigas podem ser carregadas antes que o caminho do arquivo seja alterado. Embora a maioria dos compiladores carregue uma classe somente quando necessário, não tenho certeza se esse é um requisito de especificação java. Portanto, em teoria, isso pode não funcionar para todos os compiladores java.

  2. Passe o caminho do arquivo de recursos como um argumento de linha de comando. Posso adicionar o caminho do arquivo de recurso de teste como argumento da linha de comando na configuração infalível no pom. Isso parece um pouco excessivo.

  3. Use a abordagem em 1. e torne o ResourceLoader preguiçoso instanciado. Isso garante que se @BeforeMethod for chamado antes da primeira chamada para ResourceLoader.getInstance (). GetProperty (..), o ResourceLoader carregará o arquivo correto. Isso parece ser melhor do que as duas primeiras abordagens, mas acho que tornar uma classe singleton preguiçosa instancia torna-a feia, pois não posso usar um padrão simples como torná-la uma enumeração e tal (como é o caso da instanciação ansiosa).

Parece um cenário comum, qual é a maneira mais comum de fazer isso?

Marko Cain
fonte
Retrabalhar seu aplicativo para usar uma biblioteca de configuração melhor é uma opção?
Thorbjørn Ravn Andersen
Sim, eu só quero saber a maneira padrão de fazer isso. Estou aberto a reformular o aplicativo.
Marko Cain
A única maneira padrão é usar as propriedades do ambiente e / ou do sistema. Isso geralmente é muito limitado, porque é difícil fornecer e substituir a partir da linha de comando em, por exemplo, testes. Sugiro que você adicione informações à sua pergunta sobre o que você precisa e como espera poder configurar seu aplicativo com alguns casos de uso. Você pode pensar em passar os valores de configuração no construtor das classes que precisam deles.
Thorbjørn Ravn Andersen

Respostas:

7

Todos os singletons quer ansiosamente ou preguiçosamente instanciado são anti-padrão . O uso de singletons torna o teste de unidade mais difícil, porque não há uma maneira fácil de zombar de singleton.

Método estático simulado

Uma solução alternativa é usar o PowerMock para simular o método estático que retorna a instância singleton.

Use injeção de dependência

Uma solução melhor é usar a injeção de dependência. Se você já usa uma estrutura de injeção de dependência (por exemplo, Spring, CDI), refatorar o código para criarResourceLoader um bean gerenciado com escopo singleton .

Se você não usar uma estrutura de injeção de dependência, uma refatoração fácil será fazer alterações em todas as classes usando o singleton ResourceLoader:

public class MyService {

  public MyService() {
    this(ResourceLoader.getInstance());
  }

  public MyService(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }
}

E então em testes unitários simulados ResourceLoader usando Mockito

ResourceLoader resourceLoader = mock(ResourceLoader.class);
when(ResourceLoader.getProperty("my-property")).thenReturn("10");
MyService myService = new MyService(resourceLoader);

Externalizar configuração

Outra abordagem é colocar um arquivo com configurações de teste em src/test/resources . Se você armazenar as configurações no src/main/resources/application.properties, um arquivo src/test/resources/application.propertieso substituirá.

Além disso, externalizar a configuração para um arquivo não compactado em um JAR é uma boa idéia. Dessa forma, o arquivo src/main/resources/application.propertiesconterá propriedades padrão e um arquivo passado usando o parâmetro de linha de comando substituirá essas propriedades. Portanto, um arquivo com propriedades de teste também será passado como parâmetro de linha de comando. Veja como o Spring lida com a configuração externalizada .

Usar propriedades do sistema Java

Uma abordagem ainda mais fácil é permitir a substituição de propriedades padrão pelas Propriedades do sistema no método ResourceLoader.getInstance().getProperty()e passar as propriedades de teste dessa maneira

public String getProperty(String name) {
  // defaultProperties are loaded from a file on a file system:
  // defaultProperties.load(new FileInputStream(new File(filePath)));
  // or from a file in the classpath:
  // defaultProperties.load(ResourceLoader.class.getResourceAsStream(filePath));
  return System.getProperty(name, defaultProperties.get(name));
}
Evgeniy Khyst
fonte
0

Verifique se você está no jUnit

Você também pode verificar em tempo de execução se o jUnit está em execução e depois trocar o caminho. Isso funcionaria assim ( não testado ):

public static funktionToTest() {
    StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
    List<StackTraceElement> list = Arrays.asList(stackTraceElements);
    String path = "/origin/path/to/your/file"; // here can you set the default path to your rescource
    for (StackTraceElement stackTraceElement : list) {
        if (stackTraceElement.getClassName().startsWith("org.junit.")) {
            path = "/path/only/in/jUnit/test"; // and here the normal path
        }           
    }
    //do what you want with the path
}
scolastico
fonte