TDD x Baixo Acoplamento

Pessoal,

 Estou estudando TDD a comecei a aplicar no meu trabalho. Mas não tenho certeza se realmente estou fazendo da forma correta e se meu código realmente está com baixo acoplamento. Por isso gostaria de opinião de vocês. Tenho uma classe chamada processo que tem o método getJuros:
public Double getJuros(Calendar dataBase, CalculadorDeJuros calculadorDeJuros, CalculadorDeDiferencaDeMesesEntreDatas calculadorDeDiferencaDeMeses) throws IllegalArgumentException {
        Double juros = 0.0;
        
        try {
            int mesesEmAtraso = calculadorDeDiferencaDeMeses.calcular(this.getDataDaPropositura(), dataBase);
            juros = calculadorDeJuros.calcular(this.getPorcentagemDeJuros(), mesesEmAtraso, this.getValorDaCausa());
        }catch(NullPointerException ex){
            throw new IllegalArgumentException("Não foi possível calcular o juros, falta parâmetros para o método");
        }

        return juros;
  }

E pra esse método, desenvolvi os seguintes casos de teste usando JUnit:

@Test
    public void testGetJuros() throws Exception {    
       Calendar dataDaPropositura = Calendar.getInstance();
       dataDaPropositura.set(2012, 6, 10);
       Calendar dataBase = Calendar.getInstance();
       dataBase.set(2012, 8, 1);
       Processo instance = new Processo();
       instance.setValorDaCausa(200.0);
       instance.setDataDaPropositura(dataDaPropositura);
       instance.setPorcentagemDeJuros(1.0);
       CalculadorDeJuros calculadorDeJurosMocked = mock(CalculadorDeJuros.class);
       when(calculadorDeJurosMocked.calcular(1.0, 1, 200.0)).thenReturn(2.0);
       CalculadorDeDiferencaDeMesesEntreDatas calculadorDeDiferencaDeMesesMocked = mock(CalculadorDeDiferencaDeMesesEntreDatas.class);
       when(calculadorDeDiferencaDeMesesMocked.calcular(dataDaPropositura, dataBase)).thenReturn(1);
       Double expectedResult = 2.0;
       Double result = instance.getJuros(dataBase, calculadorDeJurosMocked, calculadorDeDiferencaDeMesesMocked);
       assertEquals(expectedResult, result);
    }
    
    @Test(expected=IllegalArgumentException.class)
    public void testGetJurosSemCalculadorDeJuros() throws Exception {       
       Calendar dataDaPropositura = Calendar.getInstance();
       dataDaPropositura.set(2012, 6, 10);
       Calendar dataBase = Calendar.getInstance();
       dataBase.set(2012, 8, 1);
       Processo instance = new Processo();
       instance.setValorDaCausa(200.0);
       instance.setDataDaPropositura(dataDaPropositura);
       instance.setPorcentagemDeJuros(1.0);
       CalculadorDeDiferencaDeMesesEntreDatas calculadorDeDiferencaDeMesesMocked = mock(CalculadorDeDiferencaDeMesesEntreDatas.class);
       when(calculadorDeDiferencaDeMesesMocked.calcular(dataDaPropositura, dataBase)).thenReturn(1);
       Double result = instance.getJuros(dataBase, null, calculadorDeDiferencaDeMesesMocked);
    }
    
    @Test(expected=IllegalArgumentException.class)
    public void testGetJurosSemCalculadorDeDiferencaDeMeses() throws Exception {       
       Calendar dataDaPropositura = Calendar.getInstance();
       dataDaPropositura.set(2012, 6, 10);
       Calendar dataBase = Calendar.getInstance();
       dataBase.set(2012, 8, 1);
       Processo instance = new Processo();
       instance.setValorDaCausa(200.0);
       instance.setDataDaPropositura(dataDaPropositura);
       instance.setPorcentagemDeJuros(1.0);
       CalculadorDeJuros calculadorDeJurosMocked = mock(CalculadorDeJuros.class);
       when(calculadorDeJurosMocked.calcular(1.0, 1, 200.0)).thenReturn(2.0);
       Double result = instance.getJuros(dataBase, calculadorDeJurosMocked, null);
    }
    
    @Test(expected=IllegalArgumentException.class)
    public void testGetJurosSemDataBase() throws Exception {    
       Calendar dataDaPropositura = Calendar.getInstance();
       dataDaPropositura.set(2012, 6, 10);
       Processo instance = new Processo();
       instance.setValorDaCausa(200.0);
       instance.setDataDaPropositura(dataDaPropositura);
       instance.setPorcentagemDeJuros(1.0);
       CalculadorDeJuros calculadorDeJurosMocked = mock(CalculadorDeJuros.class);
       when(calculadorDeJurosMocked.calcular(1.0, 1, 200.0)).thenReturn(2.0);
       CalculadorDeDiferencaDeMesesEntreDatas calculadorDeDiferencaDeMesesMocked = mock(CalculadorDeDiferencaDeMesesEntreDatas.class);
       when(calculadorDeDiferencaDeMesesMocked.calcular(dataDaPropositura, null)).thenThrow(IllegalArgumentException.class);
       Double result = instance.getJuros(null, calculadorDeJurosMocked, calculadorDeDiferencaDeMesesMocked);
    }
    
    @Test(expected=IllegalArgumentException.class)
    public void testGetJurosSemDataDaPropositura() throws Exception {    
       Calendar dataBase = Calendar.getInstance();
       dataBase.set(2012, 8, 1);
       Processo instance = new Processo();
       instance.setValorDaCausa(200.0);
       instance.setPorcentagemDeJuros(1.0);
       CalculadorDeJuros calculadorDeJurosMocked = mock(CalculadorDeJuros.class);
       when(calculadorDeJurosMocked.calcular(1.0, 1, 200.0)).thenReturn(2.0);
       CalculadorDeDiferencaDeMesesEntreDatas calculadorDeDiferencaDeMesesMocked = mock(CalculadorDeDiferencaDeMesesEntreDatas.class);
       when(calculadorDeDiferencaDeMesesMocked.calcular(null, dataBase)).thenThrow(IllegalArgumentException.class);
       Double result = instance.getJuros(dataBase, calculadorDeJurosMocked, calculadorDeDiferencaDeMesesMocked);
    }

Achei os testes um pouco complexos, sei que quando isso acontece é sinal de que meu código está com alto acoplamento e deveria pensar em refatorar. Mas realmente está complexo ou é só uma paranoia minha? O que acham?

Eu diria que está muito complexo.

Antes de mais nada uma dica: separe o seu código horizontalmente em blocos, isso facilita a leitura:


@Test
    public void testGetJuros() throws Exception {    
       Calendar dataDaPropositura = Calendar.getInstance();
       dataDaPropositura.set(2012, 6, 10);

       Calendar dataBase = Calendar.getInstance();
       dataBase.set(2012, 8, 1);

       Processo instance = new Processo();
       instance.setValorDaCausa(200.0);
       instance.setDataDaPropositura(dataDaPropositura);
       instance.setPorcentagemDeJuros(1.0);

       CalculadorDeJuros calculadorDeJurosMocked = mock(CalculadorDeJuros.class);
       when(calculadorDeJurosMocked.calcular(1.0, 1, 200.0)).thenReturn(2.0);

       CalculadorDeDiferencaDeMesesEntreDatas calculadorDeDiferencaDeMesesMocked = mock(CalculadorDeDiferencaDeMesesEntreDatas.class);
       when(calculadorDeDiferencaDeMesesMocked.calcular(dataDaPropositura, dataBase)).thenReturn(1);

       Double result = instance.getJuros(dataBase, calculadorDeJurosMocked, calculadorDeDiferencaDeMesesMocked);
       assertEquals(2.0, result);
    }

Mendes, obrigado pela atenção. Tem alguma dica pra diminuir a complexidade? Você manteria essas classes CalculadorDeJuros, CalculadorDeDiferencaDeMeses… ? Tenho que ficar mockando essas classes, acho que por isso ficou complexo.

A meu ver, o grande problema é que falta coesão no cálculo de juros. Você usa 3 classes para fazer o cálculo dos juros de um processo: Processo, CalculadorDeDiferencaDeMeses e CalculadorDeJuros. Acredito que o Processo tem os dados sobre juros (taxa, data base, etc.), o cálculo deveria ser feito na própria classe Processo, ou então, você pode refatorar os dados de Juros em uma classe separada, por exemplo:

class Juro{
   private BigDecimal valorBase;
   private Date dataBase;
   private BigDecimal taxaMensal;
   private TipoJuro tipo; //simples, composto
}

ou até evoluir para algo como:

class JuroSimples{
   private BigDecimal valorBase;
   private Date dataBase;
   private BigDecimal taxaMensal;

   public BigDecimal getValorNaData(Date dataAtual){
      //...
   }
}

enfim, depende muito da regra de negócio. Outra dica: utilize Calendar apenas para efetuar cálculos com datas. Para armazenar datas, utilize Date ou Timestamp.

Entendi. Havia criado essas duas classes (CalculadorDeJuros e CalculadorDeDiferencaDeMesesEntreDatas) por questões de reaproveitamento de código e encapsulamento, pois se precisasse da regra de cálculo de juros e saber a diferença de dias entre duas datas, as regras estariam encapsuladas nessas classes. Se a regra de cálculo de juros também mudasse a refatoração ficaria fácil. Se deixar o cálculo somente na classe processo, não teria essa flexibilidade. Será que não compensa mantê-las por isso?

A data base também é dinâmica, o usuário informa. O sistema calcula o juros com base na data da propositura do processo e na data base fornecida pelo usuário.

public class CalculadorDeJuros {
    
    /**
     * Efetua o calulo de juros
     * @param taxa
     * @param valor
     * @return Double
     */
    public Double calcular(Double taxa, Integer diferencaDeMeses, Double valor) throws IllegalArgumentException{
        Double juros = null;
        try{
            juros = (valor * (taxa / 100 )) * diferencaDeMeses;
        }catch(NullPointerException ex){
            throw new IllegalArgumentException("Não foi possível calcular a multa, você deixou de passar um parâmetro.");
        }
        return juros;
    }
    
}

Um toque de mais um frustado usuário da API de Datas do Java: use Joda Time para trabalhar com datas :wink:

[quote=anderson_lr]A data base também é dinâmica, o usuário informa. O sistema calcula o juros com base na data da propositura do processo e na data base fornecida pelo usuário.

[code]
public class CalculadorDeJuros {

/**
 * Efetua o calulo de juros
 * @param taxa
 * @param valor
 * @return Double
 */
public Double calcular(Double taxa, Integer diferencaDeMeses, Double valor) throws IllegalArgumentException{
    Double juros = null;
    try{
        juros = (valor * (taxa / 100 )) * diferencaDeMeses;
    }catch(NullPointerException ex){
        throw new IllegalArgumentException("Não foi possível calcular a multa, você deixou de passar um parâmetro.");
    }
    return juros;
}

}
[/code][/quote]

Mais uma dica: evite ficar tratando a nulidade de parâmetros com blocos try catch, para isso imponha uma pré-condição. Para mais detalhes veja leia http://fragmental.com.br/wiki/index.php/Contratos_Nulos.html

Veja bem, você até pode criar classes separadas para efetuar o cálculo de juros, calcular diferença entre datas,etc. Porém, o cliente da classe Processo não deveria estar ciente dessas dependências. É dessa maneira que você consegue um design flexível. Se a sua classe Processo depende de outras classes para efetuar o cálculo dos juros, isso deve ser encapsulado apenas na classe Processo.

Mas como eu disse, é meio difícil dizer o que está certo ou o que está errado sem conhecer a regra de negócio que você está implementando. Uma princípio que eu busco seguir é fazer o desenvolvimento top-down. Antes de escrever testes eu gosto de pelo menos prototipar a interface para saber ao certo o tipo de regra que eu devo implementar. Daí sim eu parto para os testes.

Você poderia exemplificar como ficaria esse método utilizando as classes CalculadorDeJuros e CalculadorDeDiferencaDeDiasEntreDatas seguindo esse conceito?

Cara, esse teu exemplo é muito legal pra gente “viajar” em cima dele. Eu to meio sem tempo agora, mas fim de semana vou tentar fazer alguma com ele pra gente discutir.

[quote=anderson_lr]Pessoal,

 Estou estudando TDD a comecei a aplicar no meu trabalho. Mas não tenho certeza se realmente estou fazendo da forma correta e se meu código realmente está com baixo acoplamento. Por isso gostaria de opinião de vocês. Tenho uma classe chamada processo que tem o método getJuros:
public Double getJuros(Calendar dataBase, CalculadorDeJuros calculadorDeJuros, CalculadorDeDiferencaDeMesesEntreDatas calculadorDeDiferencaDeMeses) throws IllegalArgumentException {
        Double juros = 0.0;
        
        try {
            int mesesEmAtraso = calculadorDeDiferencaDeMeses.calcular(this.getDataDaPropositura(), dataBase);
            juros = calculadorDeJuros.calcular(this.getPorcentagemDeJuros(), mesesEmAtraso, this.getValorDaCausa());
        }catch(NullPointerException ex){
            throw new IllegalArgumentException("Não foi possível calcular o juros, falta parâmetros para o método");
        }

        return juros;
  }

E pra esse método, desenvolvi os seguintes casos de teste usando JUnit:

@Test
    public void testGetJuros() throws Exception {    
       Calendar dataDaPropositura = Calendar.getInstance();
       dataDaPropositura.set(2012, 6, 10);
       Calendar dataBase = Calendar.getInstance();
       dataBase.set(2012, 8, 1);
       Processo instance = new Processo();
       instance.setValorDaCausa(200.0);
       instance.setDataDaPropositura(dataDaPropositura);
       instance.setPorcentagemDeJuros(1.0);
       CalculadorDeJuros calculadorDeJurosMocked = mock(CalculadorDeJuros.class);
       when(calculadorDeJurosMocked.calcular(1.0, 1, 200.0)).thenReturn(2.0);
       CalculadorDeDiferencaDeMesesEntreDatas calculadorDeDiferencaDeMesesMocked = mock(CalculadorDeDiferencaDeMesesEntreDatas.class);
       when(calculadorDeDiferencaDeMesesMocked.calcular(dataDaPropositura, dataBase)).thenReturn(1);
       Double expectedResult = 2.0;
       Double result = instance.getJuros(dataBase, calculadorDeJurosMocked, calculadorDeDiferencaDeMesesMocked);
       assertEquals(expectedResult, result);
    }
    
    @Test(expected=IllegalArgumentException.class)
    public void testGetJurosSemCalculadorDeJuros() throws Exception {       
       Calendar dataDaPropositura = Calendar.getInstance();
       dataDaPropositura.set(2012, 6, 10);
       Calendar dataBase = Calendar.getInstance();
       dataBase.set(2012, 8, 1);
       Processo instance = new Processo();
       instance.setValorDaCausa(200.0);
       instance.setDataDaPropositura(dataDaPropositura);
       instance.setPorcentagemDeJuros(1.0);
       CalculadorDeDiferencaDeMesesEntreDatas calculadorDeDiferencaDeMesesMocked = mock(CalculadorDeDiferencaDeMesesEntreDatas.class);
       when(calculadorDeDiferencaDeMesesMocked.calcular(dataDaPropositura, dataBase)).thenReturn(1);
       Double result = instance.getJuros(dataBase, null, calculadorDeDiferencaDeMesesMocked);
    }
    
    @Test(expected=IllegalArgumentException.class)
    public void testGetJurosSemCalculadorDeDiferencaDeMeses() throws Exception {       
       Calendar dataDaPropositura = Calendar.getInstance();
       dataDaPropositura.set(2012, 6, 10);
       Calendar dataBase = Calendar.getInstance();
       dataBase.set(2012, 8, 1);
       Processo instance = new Processo();
       instance.setValorDaCausa(200.0);
       instance.setDataDaPropositura(dataDaPropositura);
       instance.setPorcentagemDeJuros(1.0);
       CalculadorDeJuros calculadorDeJurosMocked = mock(CalculadorDeJuros.class);
       when(calculadorDeJurosMocked.calcular(1.0, 1, 200.0)).thenReturn(2.0);
       Double result = instance.getJuros(dataBase, calculadorDeJurosMocked, null);
    }
    
    @Test(expected=IllegalArgumentException.class)
    public void testGetJurosSemDataBase() throws Exception {    
       Calendar dataDaPropositura = Calendar.getInstance();
       dataDaPropositura.set(2012, 6, 10);
       Processo instance = new Processo();
       instance.setValorDaCausa(200.0);
       instance.setDataDaPropositura(dataDaPropositura);
       instance.setPorcentagemDeJuros(1.0);
       CalculadorDeJuros calculadorDeJurosMocked = mock(CalculadorDeJuros.class);
       when(calculadorDeJurosMocked.calcular(1.0, 1, 200.0)).thenReturn(2.0);
       CalculadorDeDiferencaDeMesesEntreDatas calculadorDeDiferencaDeMesesMocked = mock(CalculadorDeDiferencaDeMesesEntreDatas.class);
       when(calculadorDeDiferencaDeMesesMocked.calcular(dataDaPropositura, null)).thenThrow(IllegalArgumentException.class);
       Double result = instance.getJuros(null, calculadorDeJurosMocked, calculadorDeDiferencaDeMesesMocked);
    }
    
    @Test(expected=IllegalArgumentException.class)
    public void testGetJurosSemDataDaPropositura() throws Exception {    
       Calendar dataBase = Calendar.getInstance();
       dataBase.set(2012, 8, 1);
       Processo instance = new Processo();
       instance.setValorDaCausa(200.0);
       instance.setPorcentagemDeJuros(1.0);
       CalculadorDeJuros calculadorDeJurosMocked = mock(CalculadorDeJuros.class);
       when(calculadorDeJurosMocked.calcular(1.0, 1, 200.0)).thenReturn(2.0);
       CalculadorDeDiferencaDeMesesEntreDatas calculadorDeDiferencaDeMesesMocked = mock(CalculadorDeDiferencaDeMesesEntreDatas.class);
       when(calculadorDeDiferencaDeMesesMocked.calcular(null, dataBase)).thenThrow(IllegalArgumentException.class);
       Double result = instance.getJuros(dataBase, calculadorDeJurosMocked, calculadorDeDiferencaDeMesesMocked);
    }

Achei os testes um pouco complexos, sei que quando isso acontece é sinal de que meu código está com alto acoplamento e deveria pensar em refatorar. Mas realmente está complexo ou é só uma paranoia minha? O que acham? [/quote]

Putz, que balde de agua fria. Quando li voce falando de juros e tal pensei que tivessemos um exemplo legal de testes pra trabalhar e esclarecer muitas dúvidas comuns de quem está começando com testes.

Quem sabe não viria até alguns dos gurus da arte pra me criticar e ensinar mais alguma coisa também?

Mas eis que me deparo com você testando o mockito.

Repare que embora você queira dizer:

O que você está dizendo na verdade é:

A única forma de seus testes falharem é o pessoal do mockito lançar uma versão bugada. Eles vão adorar o seu teste, mas provavelmente já têm os deles.

O que você precisa testar é a implementação de fato dos seus calculadores, o seu código suscetível a falhas está lá dentro e não no método getJuros().

Apenas reforçando o que o pessoal já sugeriu: joda-time neles!

Por ultimo, mas não menos importante: se livre das checked excpetions. Você vai ouvir muito mimimi, chororê e blablablá sobre isso, mas não tem conversa, a única serventia delas é encher o saco te obrigando a tratar exceções só pra lançar outra de outro tipo ou sujando as suas interfaces.

Provavelmente nesse mesmo tópico vai vir alguém me xingar por isso, mas não de bola, chuta essa porcaria. Nenhuma outra linguagem usa esse negócio e nem por isso são menos robustas que Java.

Checked exception my ass! RuntimeException é a mãe das exceções no Java.

Então, estou mocando o CalculadorDeJuros por que em testes unitários temos que testar somente o método unitariamente. Ou seja, não devo me preocupar em testar a classe CalculadorDeJuros, senão não seria teste unitário e sim teste de integração. Se o método utiliza recursos externos, devo simular esse recurso, pois não é nossa intensão testá-lo mas sim o método getJuros da classe Processo. O teste desse método não pode depender do resultado dos outros não é isso? Mas posso também simular todos os problemas que acontecer na classe CalculadorDeJuros, e definir como o método getJuros irá se comportar caso esses problemas aconteça. Pra isso utilizo também o mockito. Pra testar a calsse CalculadorDeJuros desenvolvi os seguintes casos de teste:

@Test
    public void testCalcular() throws Exception {
        CalculadorDeJuros instance = new CalculadorDeJuros();
        Double expResult = 20d;
        Double result = instance.calcular(1.0, 10, 200d);
        assertEquals(expResult, result);
    }
    
    @Test(expected=IllegalArgumentException.class)
    public void testCalcularComTaxaNula() throws Exception {
        CalculadorDeJuros instance = new CalculadorDeJuros();
        Double result = instance.calcular(1.0, null, 200d);
    }
    
    
    @Test(expected=IllegalArgumentException.class)
    public void testCalcularComValorNulo() throws Exception {
        CalculadorDeJuros instance = new CalculadorDeJuros();
        Double result = instance.calcular(1.0, 10, null);
    }
    
    @Test(expected=IllegalArgumentException.class)
    public void testCalcularComDiferencaDeDiasNula() throws Exception {
        CalculadorDeJuros instance = new CalculadorDeJuros();
        Double result = instance.calcular(1.0, null, 200d);
    }
    
    @Test
    public void testCalcularComTaxaZero() throws Exception {
        CalculadorDeJuros instance = new CalculadorDeJuros();
        Double expResult = 0d;
        Double result = instance.calcular(0.0, 10, 200d);
        assertEquals(expResult, result);
    }
    
    @Test
    public void testCalcularComValorZero() throws Exception {
        CalculadorDeJuros instance = new CalculadorDeJuros();
        Double expResult = 0d;
        Double result = instance.calcular(1.0, 10, 0d);
        assertEquals(expResult, result);
    }
   
    @Test
    public void testCalcularComDiferencaDeDiasZero() throws Exception {
        CalculadorDeJuros instance = new CalculadorDeJuros();
        Double expResult = 0d;
        Double result = instance.calcular(1.0, 0, 0d);
        assertEquals(expResult, result);
    }

Lembrando que isso é só uma discussão. Também estou aprendendo e adoro essas discussões saudáveis. Também fico muito contente com a interação de vocês. Valeu aí galera, vamos continuar a discussão e me corrijam se falei alguma besteira.

[quote=anderson_lr]Então, estou mocando o CalculadorDeJuros por que em testes unitários temos que testar somente o método unitariamente. Ou seja, não devo me preocupar em testar a classe CalculadorDeJuros, senão não seria teste unitário e sim teste de integração. Se o método utiliza recursos externos, devo simular esse recurso, pois não é nossa intensão testá-lo mas sim o método getJuros da classe Processo. O teste desse método não pode depender do resultado dos outros não é isso? Mas posso também simular todos os problemas que acontecer na classe CalculadorDeJuros, e definir como o método getJuros irá se comportar caso esses problemas aconteça. Pra isso utilizo também o mockito.
[/quote]

É por ai mesmo. Nesse caso você pode mockar os seus calculadores, já que você tem testes para eles. Mas nesse caso específico eu acharia desnecessário. Certo, há quem vá argumentar contra, mas o seu Processo só está delegando o cálculo de juros para outros objetos. É como eu disse, a única chance dos seus testes falharem é se houver algum problema na implementação do mockito.

Sim, você deveria simular os problemas que poderiam acontecer em CalculadorDeJuros para ver como o seu Processo irá se comportar nesses casos. Mas não é o que está acontecendo aqui, exceto pelo tratamento de exceção. O que na minha opinião por si só não vale um teste. Faria sentido testar se, por exemplo, você precisasse tomar uma decisão caso o valor do juros superasse em 100% o valor original. Ou, por acaso fosse menor que zero e isso gerasse outras ações.

Você deve testar o que pode falhar, e o seu processo não falha, então voce está desperdiçando tempo com ele.

Outra coisa: Tente dar mais significado ao nome dos seus testes e as instancias que você está usando. Lembre-se de que ele vai te servir como documentação tambem. O habito de começar um metodo de teste com a palavra test vem do legado do JUnit 3, hoje voce não precisa mais, você pode dar nomes mais especificos a eles. Como:

@Teste
public void naoEhPossivelCalcularJurosSeATaxaNaoFoiInformada() {

}
//ao inves de 
@Test
public void testCalcularComTaxaNula(){}

Tente também dar mais significado ao seus métodos, o nome da classe está bom, é bastante significativo, mas os três parâmetros confundirão alguém que um dia vai dar manutenção no seu sistema. Por exemplo, voce está passando somente dados completamente fora de contexto. Se eu um dia pegar o código pra alterar eu não sei exatamente quais entidades poderão ter meus juros calculados por esse seu calculador. Qualquer uma? somente a propositura? Qualquer entidade que possua valor e data de vencimento? O que exatamente é a taxa e para que serve?

Se o seu calculador calcula uma propositura não deveria receber a entidade que a representa como parâmetro? Se é para várias entidades não deveria então receber uma interface “Calculavel” ou qualquer coisa do gênero que as entidades implementariam. A taxa, ou todas as possíveis taxas não deveriam estar no construtor do calculador e internamente ele saber qual delas aplicar?

Tente dar mais coesão aos parâmetros do seu calculador, assim diminuirá o numero deles e a sua interface publica terá mais significado.

E por ultimo. Por que um classe de nome Processo tem um metodo chamado getJuros()? Isso faz sentido na regra? Por exemplo, quando você conversa com o usuário vocês falam sobre os juros do processo? Eles são do processo mesmo?

Estes objetos que você utiliza sempre, poderia instanciá-los num método setUp, já que muitos deles sempre se repetem. Até mesmo os mocks…

Tem razão.

Só não entendi muito bem como aplicar o DBD nesse contexto. Alguém teria um exemplo?