Como testes de unidade deve configurar fontes de dados quando não estiver sendo executado em um servidor de aplicativos?

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

Pergunta

Obrigado a todos por sua ajuda. Um número de você postou (como eu deveria ter esperado) respostas indicando toda a minha abordagem foi errada, ou que o código de baixo nível nunca deve ter de saber se é ou não está sendo executado em um recipiente. Eu tenderia a concordar. No entanto, estou lidando com um aplicativo de legado complexo e não tem a opção de fazer um grande refactoring para o problema atual.

Deixe-me voltar atrás e fazer a pergunta a motivou a minha pergunta original.

Eu tenho um aplicativo herdado em execução no JBoss, e fizeram algumas modificações para códigos de baixo nível. Eu criei um teste de unidade para o meu modificação. Para executar o teste, eu preciso conectar a um banco de dados.

O código legado fica a fonte de dados da seguinte maneira:

(jndiName é uma cadeia definida)

Context ctx = new InitialContext();
DataSource dataSource = (DataSource) ctx.lookup(jndiName);

O meu problema é que quando eu executar esse código sob teste de unidade, o contexto não tem fontes de dados definidas. Minha solução para isso era tentar ver se eu estou correndo sob o servidor de aplicativos e, se não, criar o teste DataSource e devolvê-lo. Se eu estou correndo sob o servidor de aplicações, então eu uso o código acima.

Então, o meu real pergunta é: Qual é a maneira correta de fazer isso? Existe alguma maneira aprovado o teste de unidade pode configurar o contexto para retornar a fonte de dados apropriado para que o código sob teste não precisa estar ciente de onde ele está correndo?


Para Contexto: minha pergunta inicial:

Eu tenho algum código Java que precisa saber se é ou não está funcionando sob JBoss. Existe uma maneira canônica para o código para dizer se ele está sendo executado em um recipiente?

A minha primeira abordagem foi desenvolvida através experimention e consiste em obter o contexto inicial e teste que pode procurar determinados valores.

private boolean isRunningUnderJBoss(Context ctx) {
        boolean runningUnderJBoss = false;
        try {
            // The following invokes a naming exception when not running under
            // JBoss.
            ctx.getNameInNamespace();

            // The URL packages must contain the string "jboss".
            String urlPackages = (String) ctx.lookup("java.naming.factory.url.pkgs");
            if ((urlPackages != null) && (urlPackages.toUpperCase().contains("JBOSS"))) {
                runningUnderJBoss = true;
            }
        } catch (Exception e) {
            // If we get there, we are not under JBoss
            runningUnderJBoss = false;
        }
        return runningUnderJBoss;
    }

Context ctx = new InitialContext();
if (isRunningUnderJboss(ctx)
{
.........

Agora, isso parece funcionar, mas se sente como um hack. Qual é a maneira correta de fazer isso? Idealmente, eu gostaria de uma forma que iria trabalhar com uma variedade de servidores de aplicativos, não apenas JBoss.

Foi útil?

Solução

Toda a abordagem se sente mal dirigido a mim. Se as suas necessidades de aplicativos para saber qual recipiente que está sendo executado em que você está fazendo algo errado.

Quando eu uso Primavera I pode passar de Tomcat para WebLogic e voltar sem mudar nada. Tenho certeza de que, com a configuração adequada que eu poderia fazer o mesmo truque com JBOSS também. Esse é o objetivo que eu atirar para.

Outras dicas

Todo o conceito é de trás para frente. código de nível inferior não deve fazer este tipo de teste. Se você precisar de uma implementação diferente passá-lo para baixo em um ponto relevante.

Uma combinação de Dependency Injection (quer seja através da Primavera, arquivos config, ou argumentos do programa) eo padrão de fábrica normalmente funcionam melhor.

Como exemplo, eu passar um argumento para meus scripts Ant que os arquivos de configuração de configuração dependendo se a orelha ou a guerra está indo para um desenvolvimento, teste ou ambiente de produção.

Talvez algo como isto (feio, mas ele pode trabalhar)

 private void isRunningOn( String thatServerName ) { 

     String uniqueClassName = getSpecialClassNameFor( thatServerName );
     try { 
         Class.forName( uniqueClassName );
     } catch ( ClassNotFoudException cnfe ) { 
         return false;
     }
     return true;
 } 

O getSpecialClassNameFor método retornaria uma classe que é único para cada servidor de aplicativos (e pode retornar novos nomes de classe quando os servidores mais aplicativos são adicionados)

Em seguida, você usá-lo como:

  if( isRunningOn("JBoss")) {
         createJBossStrategy....etcetc
  }
Context ctx = new InitialContext();
DataSource dataSource = (DataSource) ctx.lookup(jndiName);

Quem constrói o InitialContext? Sua construção deve estar fora do código que você está tentando teste, ou de outra forma você não será capaz de zombar do contexto.

Uma vez que você disse que está trabalhando em um aplicativo de legado, primeiro refatorar o código para que você possa facilmente dependência injetar o contexto ou fonte de dados para a classe. Então você pode testes mais facilmente escrever para essa classe.

Você pode fazer a transição do código legado por ter dois construtores, como no código abaixo, até que você tenha refatorado o código que constrói a classe. Desta forma, você pode mais facilmente testar Foo e você pode manter o código que utiliza Foo inalterado. Então você pode lentamente refatorar o código, de modo que o construtor antigo é completamente removido e todas as dependências são dependência injetado.

public class Foo {
  private final DataSource dataSource;
  public Foo() { // production code calls this - no changes needed to callers
    Context ctx = new InitialContext();
    this.dataSource = (DataSource) ctx.lookup(jndiName);
  }
  public Foo(DataSource dataSource) { // test code calls this
    this.dataSource = dataSource;
  }
  // methods that use dataSource
}

Mas antes de você começar a fazer isso refatoração, você deve ter alguns testes de integração para cobrir suas costas. Caso contrário, você não pode saber se mesmo as refatorações simples, como mover a pesquisa DataSource para o construtor, quebrar alguma coisa. Então, quando o código fica melhor, mais testável, você pode escrever testes de unidade. (Por definição, se um teste toca o sistema de arquivos, rede ou banco de dados, não é um teste de unidade -., É um teste de integração)

O benefício de testes de unidade é que eles correm rápido - centenas ou milhares por segundo - e são muito focados para testar apenas um comportamento de cada vez. Isso torna possível executar, em seguida, muitas vezes (se você hesitar em execução todos os testes de unidade depois de mudar uma linha, eles correm muito lentamente) para que você obtenha feedback rápido. E porque eles são muito focados, você vai saber só de olhar para o nome do teste na sua falta exatamente onde no código de produção é o erro.

O benefício de testes de integração é que eles se certificar de que todas as peças estão encaixadas corretamente. Isso também é importante, mas você não pode executá-los muito frequentemente porque coisas como tocar o banco de dados torná-los muito lento. Mas você ainda deve executá-los pelo menos uma vez por dia em seu servidor de integração contínua.

Há um par de maneiras de resolver este problema. Um é para passar um objecto de contexto para a classe quando está sob teste de unidade. Se você não pode alterar a assinatura do método, refatorar a criação do contexto inital a um método protegido e testar uma subclasse que retorna o objeto de contexto escarnecido, substituindo o método. Isso pode pelo menos colocar a classe em teste para que você possa refazer a melhores alternativas de lá.

A próxima opção é fazer conexões de banco de dados uma fábrica que pode dizer se ele está em um recipiente ou não, e fazer a coisa apropriada em cada caso.

Uma coisa a se pensar é - uma vez que você tem essa conexão com o banco para fora do recipiente, o que você vai fazer com ele? É mais fácil, mas não é bem um teste de unidade, se você tem que carregar a camada de acesso de dados inteiro.

Para obter mais ajuda neste sentido de mover código legado em teste unidade, eu sugiro que você olhe de Michael Pena trabalhar efetivamente com legacy Code .

Uma maneira limpa para fazer isso seria ter ouvintes do ciclo de vida configurados no web.xml. Estes podem definir sinalizadores globais se quiser. Por exemplo, você poderia definir um ServletContextListener em sua web.xml e no método contextInitialized, definir um sinalizador global que você está correndo dentro de um recipiente. Se o sinalizador global não está definido, então você não está rodando dentro de um recipiente.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top