Hanami vs o mundo

Destaque

Coloque-se na situação em que você precisa desenvolver um software corp enterprise em Ruby. Qual a primeira coisa que pipoca em sua mente neste momento?

Pois é:

Domain-Driven Design

A hype (olá pessoal de FP!) ideia do Domain-Driven Design (DDD pros chegados), é trazer o contexto de negócio juntinho do camarada que faz o software funcionar. Por que isso é bom? O principal argumento é: entendimento.

Poderia resumir todo o blue book (Domain-Driven Design: Tackling Complexity in the Heart of Software – Eric Evans) com a frase acima. Obviamente que seus problemas começam juntamente com seu objetivo de deixar o conhecimento alinhado entre todos envolvidos na solução. É exatamente por isso que o livro é extenso e detalhado, mas isso é assunto pra outro papo.

Qual a outra coisa que você pensa quando se fala enterprise ? Aha!

Patterns of Enterprise Application Architecture

O livro vermelhão do (Martin) Fowler é sem dúvidas um ponto de partida para falarmos daquele papo maroto de Arquitetura. Foi lindo! Todo mundo queria ser Arquiteto de Software, lembra disso? Velhos tempos hahaha!

Passada essa fase, começamos todos a pensar: o que é arquitetura de software? Com isso, veio aquela conversa de Arquitetura Evolucionária, ideia que prega que a arquitetura do seu software deveria emergir de acordo com o passar do tempo e das necessidades do software em questão.

De certa forma isso deveria ter sido falado lá atrás, no começo do epicentro do Domain-Driven Design e da importância por uma Arquitetura de Software definida, por mais tosca que parecesse ser.

Motivo simples: muito bolovo teria sido evitado e o nível de frustação e demissão regulados.

Arquitetura de Software

Enfim, a tal da Arquitetura tem sua utilidade, apesar de eu ver poucas pessoas definindo legal o que significa isso.

Podemos definir arquitetura como uma camada do seu software que vista pela outra camada é percebida como um blackbox, tendo pontos de comunicação externos definidos e padronizados.

Outra definição válida é de que arquitetura é todo design de software que agrupado representa ambos requisitos funcionais e não funcionais de uma parte do software. É assim que Clements P, et al define arquitetura de software:

As Clements P, et al. point out, all architectures are designs but not all designs are architectures. Designs that need to be bound in order for the system to meet its functional and nonfunctional requirements and goals are architectural in nature.

@IBM – Developer Works: Documenting Software Architecture Part 1

Dito isso, poderíamos entrar na Deep Web dos tipos de arquitetura de software, como Layered Architecture, Ports & Adapters, Clean Architecture, Hexagonal Architecture, CQRS.. opa, esse não!

Many people have been getting confused over what CQRS is. They look at CQRS as being an architecture; it is not.

O próprio senhor Greg Young disse que esse papo de que CQRS é arquitetura é infudado. E ele fala mais:

CQRS is a very simple pattern that enables many opportunities for architecture that may otherwise not exist. CQRS is not eventual consistency, it is not eventing, it is not messaging, it is not having separated models for reading and writing, nor is it using event sourcing.

Olé!

e o Hanami?

hanami-logo

O que um framework web Ruby tem a ver com tudo isso?

Tudo, obviamente!

A ideia geral do Hanami é ser desacoplado. Independente. Rebeldia? Jamais! O papo é que o framework por definição é feito para colaborar e não definir sua arquitetura no pior estilo One Size fits All Architecture.

Só por não definir uma arquitetura ele já possibilita ao desenvolvedor maluco definir sua própria arquitetura. E mais: evolui-la de acordo com suas próprias necessidades. Epa! Isso traz a tona o que eu falei lá em cima sobre Arquitetura Evolucionária.

weMor1k.gif

Legal, né?

Ah, e se eu não quiser definir nada? Quero seguir como uma boa ovelha que sou!? Neste caso o Hanami, mesmo não definindo uma arquitetura, sugere um Hexagonal Architecture out of the box. Sem pensar, você terá camadas bem definidas, isolando seu modelo dos seus adapters de apresentação e integração. Boom!

Destaque

Existe vida após o primeiro teste passar?

listen-to-tests

Conseguiu fazer seu primeiro teste passar. Está orgulhoso de seu novo achievement porém sabe que esse primeiro passo é apenas o início para conseguir mensurar uma evolução no seu processo de desenvolvimento usando Test-first como referência de design de código. Agora, tudo que você precisa é continuar neste ciclo.

O ciclo é importante para evitar se atrapalhar e perder o ritmo neste começo. Fato! Mas, ainda mais importante é ter uma ideia clara do que pretende fazer a seguir.

Saindo um pouco de Test-first, uma coisa legal que você pode fazer é ter uma ToDo list. São aquelas coisas simples onde você organiza suas tarefas. Vai de Remember The Milk até um Trello ou Pivotal Tracker . Obviamente cada um deles tem seus pontos de ataque, mas tangenciei o tema justamente para te alertar que uma mente organizada, ajuda e muito você manter um ritmo. Minha técnica é bem simples: ao final do dia, eu vejo meu progresso no software e estimo um novo ponto de parada para o próximo dia. Quando o dia chega, eu tenho em mente que meu objetivo do dia é “Conseguir enviar um Pedido com sucesso”, por exemplo.

Com a organização em dia, você sabe o que atacar agora. Assim sendo, podemos voltar ao Test-first exclusivamente.

Vida após o primeiro teste com Test-first

Qual a próxima feature importante do seu negócio? Veja, entenda como feature requisitos funcionais e não funcionais, ou seja, pode ser algo não necessariamente explicito pelo chefe/cliente, mas algo que você percebeu fazendo seu teste de unidade com Test-Driven Development. Acredite: isso acontece e muito!

Criar cenários sem final feliz é igualmente importante ao cenário feliz. Se você espera que seu código some dois números, o que acontece se você mandar dois null para ele somar? Ele deveria fazer algo? Se é importante que ele seja resiliente e lide com este tipo de coisa, você deve criar um teste de unidade para este cenário também!

É perfeitamente aceitável criar um teste de unidade que dado dois null ele deverá retornar uma exceção (Exception). O anormal, seria você criar um teste de unidade que não espera que uma exceção seja lançada. Neste caso, você deve esperar um valor de retorno ou que um mock seja chamado.

Muito interessante criar testes que exercitam diferentes cenários e configurações de seus inputs. Por exemplo, numa inscrição, você pode simular o comportamento do método que inscreve pessoas, utilizando pessoas cadastradas, pessoas elegíveis à inscrição e pessoas não elegíveis a inscrição. Desta forma, você teria testes assim:

it “fará a inscrição dado um usuário elegível”

it “não fará a inscrição ao receber um usuário expirado”

it “não fará a inscrição ao receber um usuário inelegível”

Na maioria dos casos, você pode perfeitamente possuir mais de um caso de teste para um mesmo requisito funcional que você pegou para fazer.

O código mudará

Acostume-se. Ele mudará! No seu primeiro teste você não tem nada no seu domínio. Após o primeiro teste, novas coisas surgirão. No segundo teste, você acabará mudando código de produção que afeta o primeiro teste. Isto é ruim? Muito pelo contrário: isto é excelente. Significa que você está literalmente Guided by Tests, pois está criando o código necessário para seu teste passar. Está com isso deixando o Test-first guiar seu design de código.

Problemas irão ocorrer. Em problema, o primeiro passo é não seguir adiante. Mantenha-se aonde está, exatamente no teste que você está com dificuldades. Tente entender o que o teste está lhe dizendo. No mês de agosto inteiro, dediquei os posts a tratar os problemas mais comuns ao tentar seguir adiante com TDD. Leia o Prelúdio da série “Rua sem saída” e veja se eu abordei sua dúvida. Isto te ajudará a voltar aos trilhos (Rails ;P) com Test-first.

Concluindo

Organização mental; Seguir passos palpáveis que dão retorno rápido em modificações; Lembrar-se do Red, Green, Refactor; Efetivamente fazer o teste antes do código de produção, pois o teste te auxilia a guiar o design do código; saber “ouvir” o teste; e, o mais importante: não ter medo de mudar seu código de produção devido a um novo teste de unidade são a chaves necessárias para abrir qualquer caminho travado.

O que Test-Driven Development não é

Destaque

Uma dúvida muito pertinente em engenharia de software é sobre Test-Driven Development, TDD para os mais chegados. Há resistência por parte daqueles que não conhecem – ora, também não é para menos: o nome remete a Teste e teste remete a um processo da área de Verificação e Validação.

Após ouvir o developer dizer nunca fez e nunca viu “fazerem TDD” e suas argumentações sobre o porque isso não funciona, costumo perguntar uma simples pergunta: para você, o que é TDD ?

Até o momento as respostas foram uma derivação de: “Serve ahn… pra você verificar se seu software funciona (..) serve para evitar bugs”. Ora, não é para menos que você desacredita – e por isso nunca tentou – na ideia.

Redução de bugs é um efeito do TDD e não seu objetivo. Alguns já não definem mais a sigla como Test-Driven Development, mas sim como: Test-Driven Design o que na minha opinião é muito mais descritivo para os “novatos no assunto”. Este novo nome deixa tudo mais claro: TDD é sobre a construção do seu design. Pronto. Só isso.

Partindo do princípio que você saiba o que “design” significa em engenharia de software, aposto que está repensando em tudo que você argumentava (se é que) contra a adoção do estilo de desenvolvimento. Explicado o que é o Test-Driven Development Design há ainda outras barreiras a ser vencidas. Vamos a elas.

1. TDD consome mais tempo

Depende. No início é óbvio que você irá mais devagar, pois estará aprendendo algumas coisas enquanto tenta implementar uma feature:

  • A técnica de codar seguindo TDD;
  • A técnica de codar Testes Unitários de Unidade;
  • O framework de teste;

Codificar seguindo o Test-Driven Design (vou repetir isso inúmeras vezes para fixar), após estudar e pegar prática, você irá produzir mais do que antes. Fato. Além disso, provavelmente o deixará pensando bastante sobre pontos como:
como devo começar meu teste? O que devo testar? Como testar? e, até onde testar?

Note que “testar” neste contexto significa: verificar que o método está fazendo o que se espera que seja feito; Apesar de parecer óbvio, é melhor ter isso sempre em mente.

Teste de Unidade é um ponto importantíssimo para conseguir seguir em frente com o Test-Driven Design. Isso dá um post dedicado, por isso podemos discutir sobre o que não é Teste de Unidade.

Ele não “testa” necessariamente todos os métodos da sua classe. Um erro comum é achar que Teste de Unidade significa testar todo getter e setter de uma classe. Errado.
Quando estiver testando um método de uma classe e este método chamar outro método de outra classe, você não pode deixar essa chamada acontecer “de verdade”. Aqui entra o assunto Test Double – double, mock e stub. Também digno de outro post. Quando eu digo outro método de outra classe, estou literalmente dizendo que:

class Foo
  def do_something(another_class: MyClassDependency.new)
    # …
    another_class.find_bar(…)
  end
end

o MyClassDependency#find_bar precisa ser “mockado” ou “stubado”, pois devemos sempre partir do princípio de que ele já foi testado isoladamente e o find_bar irá retornar o objeto/valor corretamente. Por isso o Mock/Stub. (vide imagem do post ;])

Não devemos testar métodos privados diretamente. Dizem que há exceções, realmente podem haver, pois design é questão de experimentar e evoluir, porém eu em particular nunca testei método privado – mesmo em algumas situações eu ter insistido e quase feito.

O motivo é muito simples: devemos encarar nossos métodos privados como versões instáveis da nossa API. Considere toda classe do seu projeto, como uma API: métodos públicos são o canal de comunicação com a classe que tem suas interfaces (nome do método, parâmetros e retorno) estáveis. Já os métodos privados da classe, são nossos métodos instáveis, ou seja, estamos informando ao cliente (outra classe do projeto) que aquele método poderá mudar sua interface de uma versão para a outra sem aviso prévio. Isto não quer dizer que seus métodos privados não serão testados. Muito pelo contrário. Você precisa testar o método público da sua classe que utiliza o método privado em questão. Com isso, você garante não só que seu método instável (privado/protected) está funcionando, mas também que ele está bem relacionado com o(s) método(s) público(s) que o utiliza. E é perfeitamente normal sua classe possuir alguns métodos privados. Não tenha medo de utilizá-los, sempre pensando que um método deve ter apenas uma responsabilidade – um motivo para mudar.

Novamente: Teste de Unidade não deve chamar métodos de classes colaboradoras (igual exemplo acima). Ou seja: não devemos chamar nossa API/abstração de banco de dados, nem aquele Webservice de Pagamento/Notificações, nem o seu Mailer, nem seu Filesystem, etc. Você sempre deverá Mockar ou Stubar essas “dependências” da sua classe.

2. Eu gosto de “Testar” depois.

Há quem diga que já tem em mente o que precisa fazer para aquela feature funcionar e que por isto, prefere fazer o código de produção [1] primeiro e o teste depois.

Essa é fácil: se for para fazer o teste depois, você não estará construindo o design da sua aplicação. Estará perdendo tempo.

Testar depois tem dois problemas: a) perderá a chance de desacoplar suas classes, pois acredite: TDD faz você pensar mais sobre como fazer e isso incrivelmente nos faz mais produtivos, pois você resolverá os problemas de forma mais simples. b) você fará o teste assumindo que sua classe/método está correto e funciona, com isso criará o que chamam de Teste Viciado que em outras palavras quer dizer: você faz seu teste satisfazer seu código de produção, porém seu código de produção não garante que o teste está correto.

Dica simples: não deixe para testar depois. Crie o costume de fazer o teste antes!

[1]: Código de produção é o código que você deploya.

3. Minha equipe/co-workers não tem interesse pelo assunto.

Você não precisa esperar seu chefe instaurar a obrigatoriedade do Test-Driven Design. Não espere que ele saiba das vantagens disso. Aproveite o momento para você começar a espalhar a ideia na equipe.

Recentemente entrei numa equipe que não faz testes de unidade, tão pouco TDD. No começo, fiquei observando a reação das pessoas sobre Test-Driven Design e suas queixas sobre a possibilidade de fazer. Minha meta era em um mês conseguir mostrar o que a técnica de teste poderia resolver nos projetos e o quão empolgante é codificar com Test-First. Passado este um mês, eu já tinha mostrado TDD in Action para três desenvolvedores que além de ficarem convencidos e animados com a ideia, passaram a defendê-la. Eu acredito que mudanças de cultura deste tipo, devem sempre ser bottom-up, dos developers para os gestores. Muito improvável que seu gestor não aprovará a ideia se você se propuser a explicar para que o Test-Driven Design é executado em todo o mundo.

Concluindo

Test-Driven Development Design é sobre como construir seu design de forma simples e desacoplada. Sem bullshits, sem bala de prata ou santo graal. Adotar Test-Driven Development é evoluir sua forma de fazer software de uma forma muito rápida.

Hanami em prod: 2 anos depois – Parte 1

Começamos os protótipos quando o projeto se chamava Lotus-rb. Lá em Outubro de 2015, a ideia era validar se quem-sabe-talvez, Lotus-rb pudesse ser uma alternativa real ao então nosso principal projeto em Ruby on Rails.

Le Prototype

Como todo mundo, fizemos um ToDo app para validar. Experimentamos o model e o validation, pois já tinhamos em mente separar o Input e sua validação do domain-model. Queríamos isolar ao máximo nosso modelo, muito nos moldes do Clean Architecture do Uncle Bob / Ports & Adapters do Alistair Cockburn. A ideia era muito experimental e confiávamos na robustez por baixo do Lotus-rb: Sequel. O Sequel, diferente do Lotus-rb, era estável e bem conhecido no ecosistema Ruby. Pensavamos: se o Lotus cair, ficamos com uma camada de abstração do Sequel e migramos isso depois facilmente para um Sequel-model da vida.

Meses depois, nosso projeto Core foi ao ar, substituindo partes do nosso agora legado Rails app. Já com o nome de Hanami e algumas atualizações feitas, tudo ia bem, para nossas necessidades da época. O projeto é uma API com interface JSON API, com foco em separação em camadas, Domain-Driven Design e arquitetura Hexagonal.

Le Presentation Layer

Ao longo deste processo, testamos outras gems como Roar (do Apotonick), Representable, JSONApi-Serializers e Grape. Como dá para ver, nossa Presentation Layer demorou a ficar do jeito que imaginávamos. É a vida. Ela é assim e boas. Fizemos uma versão básica da Presentation Layer e depois de tudo pronto e algumas utilizações, repensamos como poderíamos deixa-la de facto isolada.

Nossa principal questão foi como lidar com a transformação do Domain-Model para entregar algo “burro” ao nosso Presentation Layer. Outro ponto era o ponto inverso: como transformar o payload do Request em um objeto de transferência para ser encaminhado a Application Layer – a.k.a Use Cases. Idas e vindas, começamos com um modesto fluxo endpoint -> form object -> application service -> domain model até chegarmos em algo com responsabilidades melhor definidas:

# Presentation Layer
endpoint
  form object
    optional triggers: Validation & Relationships (JSON API specifics)

# Application Layer
  use case
    orchestrates to:
      domain model or # domain model
      infrastructure service # application boundaries

Antes de ir para production, tivemos que refazer nossa ideia de Form Object, pois o modelo inicial, não continha uma forma plugável para adicionar Input Validation (Hanami Validation implementation). Era muito manual e repetitivo. Natural neste ponto do processo, pois tivemos que distribuir nosso foco de atenção em toda a arquitetura inicial.

Com o novo modelo, o FormObject delega para a validação usando Template Method, caso o tal FormObject dê include na Validation que deseja utilizar naquele endpoint. Isso deixou muito transparte como validamos as coisas, pois no final, tinhamos uma delegação muito boa entre as classes FormObject e Validation.

Como spoiler, se liga um exemplo de FormObject:

# apps/web/form_object/auto/financing/applications/patch.rb

require_relative 'validations'
require_relative 'relationships'

module FormObject
  module Auto
    module Financing
      module Applications
        class Patch
          include Validations
          include Relationships

          def initialize(params, current_user, overrides = {})
            @raw_params = params
            @current_user = current_user
            @update_application = overrides.fetch(:application_use_case) do
              ::UseCase::Auto::Financing::Application::Update.new
            end
            @repository = overrides.fetch(:applications_repository) do
              ::Auto::Financing::ApplicationsRepository
            end
          end

          def process
            validate_using(@raw_params)
          end

          private

          def validation_succeed(validated_params)
            @update_application.update_application_form_for(
              @current_user,
              entity_with_relationships_updated,
              validated_params
            )
          end

          def entity_with_relationships_updated
            update_relationships(find_application(@raw_params['id']))
          end

          def find_application(id)
            @repository.find(id)
          end
        end
      end
    end
  end
end

# apps/web/form_object/auto/financing/applications/validations.rb
# Classe de Validation citada acima

require 'form_object/definitions/validatable'
require 'validations/auto/financing/application_validator'

module FormObject
  module Auto
    module Financing
      module Applications
        module Validations
          include Definitions::Validatable

          def validator_class(params)
            ::Validations::Auto::Financing::ApplicationValidator.new(params)
          end
        end
      end
    end
  end
end

# apps/web/validations/auto/financing/application_validator.rb
# Hanami Validation class

module Validations
  module Auto
    module Financing
      class ApplicationValidator
        include Hanami::Validations

        attribute :requester_full_name, type: String, size: 0..200
        attribute :requester_cpf, type: CoerceTypes::Cpf, size: 11
        attribute :status_flow, type: String
        attribute :purchase_value, type: CoerceTypes::Nullable::Money
        attribute :down_payment, type: CoerceTypes::Nullable::Money
        attribute :loan_amount, type: CoerceTypes::Nullable::Money
      end
    end
  end
end

Le Application Layer

Nosso próximo desafio foi mudar nossos Application Services. Trocamos o nome de Application Service para Use Case. Isso por si, já deixou objetivo o que era esperado destas classes bem como da Application Layer inteira.

Use Case bem descrito passou a ser prioridade em meados de maio/2016, quando percebemos que a Application Layer precisava sair de um mero detalhe, para assumir a responsabilidade de orquestrar como nossos casos de uso eram controlados. Nosso objetivo era simples: conseguir com um tree boundaries/use_cases entender o que o software é capaz de fazer. Todo aquele papo de sempre sobre nomenclatura de classes, etc., mas ir além e pensar nos contextos (modules/namespaces) que os Use Cases fazem parte.

Como todo o projeto, nossos Use Cases são stateless e recebem suas dependências através de injeção de dependência no initialize. Podem controlar db transactions e em alguns casos, disparar Domain Events, como sugerido pelo Vaugh Verner em seu livro Implementing Domain-Driven Design.

No próximo post, vou descrever como foi lidar com a evolução de nosso domain-model e como conseguimos lidar com isso ao longo destes dois anos.

Talk: Lidando com defeitos em Test-Driven Development

Em Setembro/2014, participei do evento #S7TDD (7 dias com Test-Driven Development). O evento foi muito proveitoso com excelentes reviews dos participantes.

Falei em algumas oportunidades, uma delas abordei como o Test-Driven Development poderia auxiliar no gerenciamento de “guerra”, ou seja, quando as coisas “dão ruim”. Abaixo, o vídeo completo da minha apresentação:

Lidando com defeitos em Test-Driven Development from Hélio Costa e Silva on Vimeo.

Foi uma experiência e tanto participar de um evento online como o #S7TDD. Quem sabe em breve eu não aparece em outro 😉

Planejando no Macro para entender o micro

Finalizei o último post deixando um suspense no ar. Se não viu, recomendo que leia! Pois bem, quando falei sobre planejar no macro e codificar no micro, deixava ali uma ideia útil de fato, muito bem abordada, que quase todo developer já ouviu pelo menos falar o nome e que porém, já foi muito mal utilizada.

A UML, pode ser um aliado forte no seu planejamento macro de uma funcionalidade do software. Calma! Nada de fechar a aba e ir me xingar no twitter. Estou afirmando para você que é desenvolvedor e cria seus próprios testes a esboçar numa visão macro o que planeja fazer e com a UML, você obtém esse rascunho rapidamente.

Porque a UML e não (coloque aqui seu outro método) ?

UML é uma linguagem de modelagem de diagramas e sua simplicidade (no uso simples dela) a torna uma vantagem no esboço de rascunhos macro de seu software. Outro ponto a favor da UML é o shareable que ela permite: mande-me um esboço UML que eu entenderei o que você quer me falar.

Esboço!

Não estou falando para chamar o arquiteto de software para ele criar todo o software em UML e mandar você fazer. Vou repetir: crie você seus esboços para ajudar no seu próprio entendimento do que pretende e como fará os relacionamentos. Geralmente, eu faço micro esboços que resolve uma e somente uma funcionalidade. Após criá-la, consigo ver se fará sentido aquela minha ideia do ponto de vista arquitetônico e daí então, crio meu teste de unidade e sigo adiante.

  • Eu não faço uma funcionalidade e depois ligo com outra que liga com outra;
  • Não guardo estes rascunhos depois que acreditei na solução macro e segui adiante com a micro (Teste de Unidade) ajustando meu design de código, e;
  • Não faço rascunho macro em UML para toda e qualquer nova feature. Apenas naquelas onde as fronteiras da classe alvo do teste estão meio obscuras para mim.

Isto quer dizer que se Order se relaciona com alguns atores (outras entidades/classes puras) e este relacionamento está estranho para mim, eu tenho subir o nível de abstração, saindo do micro e indo para o macro, buscando entender como dar-se-á o futuro relacionamento ou/e se ele faz mesmo sentido naquele contexto.

Tipos de macro-esboço

Geralmente eu apelo para o Diagrama de Objetos por ser algo simples: coloca dentro do retângulo o nome da classe e pronto. Onde ele por ser útil:
a) quando você está procurando quais as fronteiras seu objeto terá; (lembre-se de que todo e qualquer objeto deve ser planejado como se o mesmo fosse uma API provendo serviço através de métodos públicos a outros objetos do mesmo sistema – por isso uso muito o termo fronteira [boundary]).
b) quando você precisa entender se o objeto deverá ser Aggregate Root de algum outro via composição.
c) traçar fluxos possíveis dentro de um conjunto de objetos.

Quando a coisa aperta e eu fico desconfiado das responsabilidades de cada método em objeto(s) em uma feature, eu recorro ao Diagrama de Sequência. Ele torna-se muito útil nos cenários macro-micro (nome horrível), onde estou planejando o macro, mas entrando um pouco no detalhe da responsabilidade (SRP, alguém?) da feature. Ele te ajuda a lembrar sempre da Lei de Demeter com seu Tell Don’t Ask.

Já acabei utilizando raramente o Diagrama de Classe, onde rascunhava um pouco sobre os métodos de uma classe, mas no meu uso ele acabava não agregando na visão macro, por especular a visão micro sem conhecimento de causa.

Porque não usar a UML como documentação de software?

Quer tentar? Vá em frente (e quebre a cara). Eu e alguns amigos já tentamos manter um Diagrama Caso de Uso em um software anos atrás e sinceramente? Totalmente inútil. Precisava de atualização a todo instante; Ficando desatualizado tornava-se um peso a mais e não ajudava no design pois era um punhado de diagrama e não código executável que te prova que sua teoria no código de produção funciona ou não. Imagina você tendo que atualizar todo diagrama de sequência assim que resolve mudar o input ou output de um método. This is madness! – além de ser esforço inútil pelos motivos já citados (não exercita o código de produção).

Concluindo

Diagramas UML, rabiscos, círculos interligados, etc., ajudam você a planejar sua visão macro quando sente-se desconfortável com certa integração entre objetos. São valiosas ferramentas para esboçar sua ideia e reorganizar sua mente antes de seguir para seu próximo Teste de Unidade, que diferente do primeiro, exercita código de produção como ninguém.

Como manter sua suite de testes saudável

taporra

Depois de uma longa pausa por causa da demanda que o #S7TDD me gerou, retorno das sombras para continuar com as polêmicas do Test-first. Hoje, retomo com algo primordial, principal, essencial, o must have em Test-first: nada menos do que ele, a manutenção da suite de testes.

O que é a manutenção da suite?

Partindo do princípio de que sua suite está em constante crescimento, você uma hora ou outra irá se perguntar em como organizar a casa para evitar se perder e não se enrolar com seus testes de unidade.

Para tanto, a manutenção da suite é importante e, quanto mais você praticar Test-first, mais simples e eventual torna-se-á tal manutenção.

E para que preciso saber/fazer isso?

Como nem todo mundo está no nível de Robert Martin (@unclebob), se reorganizar torna-se importante para continuar vendo resultado no Test-First e principalmente, continuar entendendo como o software funciona só de olhar um punhado pequeno de testes de unidade.

Perder o controle da suite é relativamente fácil principalmente quando estamos começando com testes de unidade, pois como muitos questionam: “mas e as junções? você não une as coisas nunca?”. A vantagem do Teste de Unidade mora justamente no cerne apontado nesta frase, mas como tudo tem um viés, você precisa estar atento justamente com a manutenção deste monte de teste de unidade espalhados pelo seu /specs.

Sejamos pragmáticos aqui: sua suite está precisando de uma reorganizada quando você lê um teste e não entende o que aquilo está fazendo dentro de um contexto macro. Deixando a explicação fancy de lado, temos:

  1. Pegue um teste de unidade aleatoriamente;
  2. Leia e veja o que ele implementa (funcionabilidade);
  3. Tente reproduzir aquela situação em um Controller, Command, etc, ou seja, suponha que você viu o teste e deseja criar a funcionalidade numa Action de Controller qualquer;
  4. Ache uma outra funcionalidade que se relacione com essa primeira que você reproduziu;
  5. Leia o teste dela, tente reproduzi-la ligando a primeira funcionalidade na segunda.

Conseguiu entender ambos os testes e relacionar as funcionalidades? Parabéns, sua suite está saudável (por enquanto haha).

Restabelecendo a ordem na casa (dos testes)

A primeira coisa a se fazer caso os steps acima não tenham dado resultado é verificar a anatomia dos testes de unidade. Às vezes, você acumulou duas ou três funcionalidades em um mesmo teste de unidade sem perceber. Ao splitar estes testes em testes específicos para cada situação, a legibilidade irá saltar de um espaguete para algo compreensível. Costumo dizer que esta é a principal dica para quem quer arrumar a legibilidade dos testes.

Supondo que o problema da anatomia está resolvido, podemos melhorar a legibilidade seguindo o Ubiquitous Language descrita por Eric Evans em seu renomado livro Domain-Driven Design cujo já o mencionei aqui algumas vezes no passado. Dar nomes é algo complicado (veja pelas ruas das cidades). O nome é a coisa mais importante do seu código. Isto é o elo entre o código e a vida real, solicitada e falada o tempo todo por alguém de business. Quanto mais você incorporar os nomes que ouve no código, mais natural ficará as reuniões de planejamento/sprint.

Nosso item número três do check-list é o Princípio da Responsabilidade Única (SRP, sigla em inglês). Lê isso: seu método em teste precisa fazer apenas uma coisa. Ele pode entretanto, se relacionar com outros objeto#metódo para complementar sua atividade, pedindo uma espécie de suporte. Pode também delegar atividades para outros objeto#método. Pode inclusive, se passar como parâmetro para outros objeto#método.
Veja a quantidade de recursos em Design Orientado a Objetos que você tem a disposição para não violar a SRP e você aí fazendo 3, 4, 5 steps em um único método sem a menor necessidade. Vai entender, né?

Sério. SRP é TOP 3 não é por acaso. Ela é aqui na minha lista a primeira que trata de código e OOD. As outras duas são mais arquiteturais e convenções do que código na prática.

Acredito que estas três dicas farão o trabalho de reduzir o caos que sua suite possa estar enfrentando enquanto lê isso. Um chute nada estatístico seria 80~90% de problemas resolvidos.

Como evitar on the fly que isso aconteça?

Planeje no macro; Codifique no micro. Não entendeu? Segunda-feira que vem você entenderá o que isso quer dizer.