Jogue fora os testes quebradiços
É isso mesmo! Jogue fora. Todos eles! Você não precisa de deles. Você precisa de testes que garantam o comportamento do seu sistema. Software não é sobre o código escrito, software é sobre gerar o comportamento esperado a partir de um conjunto de requerimentos.
Bem, não sou um especialista em testes nem um guru da programação, mas tenho algumas opiniões.
O que é um teste quebradiço (ou “brittle test”)?
Um teste quebradiço é aquele que passa a falhar após uma alteração em código não relacionado ao que ele deveria testar ou quando o código que ele testa é alterado, mas o comportamento do sistema não muda.
Além do custo de manutenção inerente à correção de testes que quebram “sem motivo”, ocorre também um impacto na moral e na motivação dos desenvolvedores. Se um desenvolvedor faz mudanças em um código e, ao integrá-lo, testes começam a falhar por motivos como inconsistência de dados em banco ou alto acoplamento, é praticamente certo que isso irá irritá-lo e, a médio prazo, fazer com que ele abandone os testes, criando verdadeiros “ferros-velhos” de testes sucateados. As quebras se tornarão cada vez mais frequentes e, logo, processos como integração contínua serão completamente abandonados.
Casos comuns
Fixtures
Manter os grafos de objetos através de fixtures torna-se exponencialmente mais complexo à medida que sua aplicação aumenta de tamanho e cria mais relacionamentos entre os objetos. Adicione um atributo ou relacionamento entre modelos e um grande número de testes automaticamente começará a quebrar, exigindo esforço considerável para correção.
Utilize algo como object_daddy e factory_girl que, sim, também precisarão de alguma manutenção após alterações na estrutura de dados, mas exigindo esforço muito menor.
Testes de views em código (usando coisas como assert_tag ou o matcher have_tag)
Esses testes são uma completa e desnecessária perda de tempo. Uma mudança em alguma classe ou nome de campo e, bang!, testes quebrando sem que o comportamento da aplicação tenha sido afetado.
Você pode ser moderado nesses testes e não torná-los muito específicos para evitar isso mas, nesse caso, use algo como o Selenium, também com cuidado, pois é possível cair no mesmo erro com essa ferramenta.
Teste o que influi no comportamento, como o fluxo de redirecionamentos ou elementos com ids utilizados para chamadas ajax.
O Selenium também tem a vantagem de poder ser utilizado por testadores com algum conhecimento de estrutura Html.
Abuso de mocks e stubs
Isto é, para mim, um dos grandes vilões dos testes quebradiços. Esse abuso leva os testes a ficarem altamente acoplados a detalhes de implementação. Por exemplo (o exemplo é bobo, apenas para ilustrar):
class Delivery def has_packages? self.packages.count > 0 end end class DeliveryTest < Test::Unit::TestCase context "A scheduled delivery" do setup do Delivery.stubs(:count).returns(10) @delivery = Delivery.new end should "have packages" do assert @delivery.has_packages? end end end
Se, por um motivo qualquer, a implementação do método has_packages? mudar e passar a utilizar, por exemplo, o método size ao invés de count, o teste passará a quebrar sem que o comportamento tenha sido alterado. O exemplo é simples mas, em casos reais, isso é um grande problema. Note que stubs tendem a deixar os testes mais acoplados do que mocks.
Algumas soluções pra isso são: não utilizar mocks e stubs (o que é um tanto extremista), aumentar o encapsulamento das classes ou aceitar isso e continuar.
Esse tipo de coisa costuma acontecer quando queremos utilizar mocks e stubs para tornarmos os testes independentes de recursos externos, como banco de dados. Nesses casos, o trade-off entre acoplamento do teste ao código e o ganho em velocidade de execução ou tempo de manutenção através do isolamento deve ser avaliado.
Guias gerais
Para evitar testes quebradiços, algumas guias podem ser utilizadas para a maioria dos casos:
- Especifique apenas o que você quer que aconteça e nada mais
- Em termos de mocks e stubs: use stubs para queries (tudo o que retorna dados e não modifica estado) e mocks para comandos (que não necessariamente retornam algo, mas modificam estado)
- Lembre-se de que o que importa é o comportamento do método/classe/sistema. Como isto é implementado, em nível de código, não deve fazer diferença nos seus testes
Como já disse, não sou um especialista. Então, se você percebeu alguma grande besteira nesse post, não exite em discutir, eu tenho a mente aberta.
Update
Note que mocks e stubs, quando usados corretamente, são uma importante ferramenta de design. Esse tipo de ferramenta, além de isolar os testes de recursos externos, tem o intuito (e talvez até o principal objetivo) de servir como apoio à prática do TDD: enquanto escreve os testes antes do código, mocks e stubs (e dublês em geral) permitem que você especifique o comportamento de partes ainda não existentes da aplicação. Nesse processo, torna-se mais fácil detectar falhas como alto acoplamento e corrigí-las o quanto antes, gerando menos stress.
Leia mais:
- Does stubbing make your tests brittle?
- Sorry, you’re just not my type
- Brittle tests
- Mocks aren’t stubs
Matchers vs Assertions: DSLs não são linguagens naturais
Ok, todo o tempo eu escuto o mesmo argumento quando alguém fala do RSpec (o mesmo foi usado por Jay Fields em sua palestra no Rails Summit, semana passada):
Matchers são superiores à asserções porque, quando estou conversando com alguém, eu não afirmo “Ei, assira que são iguais: 2, 1+1″, eu afirmo “Ei, 1+1 deve ser igual a 2″.
Bullshit!

Trata-se de uma falácia (Dicto Simpliciter) e bem frágil. O que é óbvio e passa despercebido nesse caso é que Linguagens Específicas de Domínio (Domain Specific Languages, DSLs) não são linguagens naturais. Uh-oh! Tão óbvio.
Você com certeza fica completamente perdido nas discussões médicas do seriado House. Tente conversar com um engenheiro elétrico sobre o processo de geração de energia elétrica numa usina nuclear. Tente entender 20% do que ele fala.
Linguagens específicas de domínio são utilizadas por especialistas no domínio em questão. Então me diga se, dentro de um teste, não está o pragmático desenvolvedor em um domínio muito específico onde, sim, asserções fazem todo o sentido. Nem sequer tente argumentar que especificações (que substituem os malfadados testes do TDD) são feitos para que clientes entendam. Don’t fool yourself.
Matchers não são ruins, não me entenda mal. São, sim, muito legais e legíveis. Mas dizer que asserções “não são naturais” e tentar fazer disso um ponto de superioridade é falta de conhecimento.
Observações: sim, eu prefiro o Shoulda ao RSpec e não vejo problema nenhum na utilização de nenhum dos dois. O ponto aqui é lembrar que DSLs não são inglês ou português, são linguagens muito específicas em um dado contexto. O exemplo citado foi o que me veio à cabeça mais rapidamente, talvez devido ao Rails Summit e à palestra de Jay Fields.
Leia mais:
The ‘Language’ in Domain-Specific Language Doesn’t Mean English (or French, or Japanese, or …)
Edit in place restful com Rails 2.1
A funcionalidade “edit in place” é aquela na qual, ao clicar em um texto na página web, disponibiliza-se um campo para edição do texto clicado.
Nas versões “não-Rest” do Rails, era comum o uso do plugin in_place_editing para conseguir essa funcionalidade. Para as versões mais novas, ainda é possível utilizar esse plugin, mas a solução acaba ficando bem grosseira. Pesquisando sobre isso, encontrei o plugin better-edit-in-place, que permite utilizar a interface Rest padrão do Rails para conseguir a funcionalidade.
Depois de instalar o plugin, você precisa de apenas três passos para fazer tudo funcionar:
1. Escrever/incrementar os testes funcionais do controller (test first, aqui com Shoulda e… ahn… fixtures…):
context "on Ajax PUT to :update" do setup do xhr :put, :update, :id => emails(:one).id, :email => {:address => 'new_email@email.com'} end should_assign_to :email should_respond_with :success should_not_set_the_flash should "update the email address" do assert_equal assigns(:email).address, 'new_email@email.com' end end
2. Criar uma ação update no controller do recurso ou adicionar o formato correto ao bloco respond_to:
def update @email = Email.find(params[:id]) @email.update_attribute(:address, params[:email][:address]) respond_to do |format| format.js { render :json => @email } format.html #voce que sabe... end end
3. Ajustar seu markup para que a parte JavaScript do plugin funcione. Para isso, basta utilizar um helper disponibilizado pelo plugin:
<%= edit_in_place(email, :address) -%>
Pronto. Ao abrir a página e clicar sobre o elemento span que exibe o texto do atributo passado ao helper, o mesmo será substituído por uma caixa de texto com um botão para salvar e um link para cancelar a ação. Tudo simples e de forma muito mais limpa. O plugin também funciona com recursos aninhados.
Obs: note que ignorei “detalhes” como validação do modelo, entre outros importantes. Fiz isso apenas pra encurtar o post, não faça o mesmo em aplicações reais.
Um ano
No último sábado, dia nove de agosto, este blog completou um ano de vida, contados a partir do primeiro post. Tem sido uma caminhada muito boa. Aprendi muita coisa (sou meio viciado nisso
), mudei muita coisa e, hoje, Ruby e Rails “me sustentam”.
Obrigado a todos que acompanham, prestigiam e comentam. Já são mais de 200 assinantes do feed, o que me deixa muito feliz!
Reprisando aquele primeiro post: Ao trabalho!
Um pouco mais sobre named_scopes
Um pouco mais? Mas cadê o primeiro artigo sobre isso? Bom, não o escrevi, mas vou partir do ponto em que parou o Nando Vieira em seu artigo sobre named_scopes. Logo, assumo que você já sabe o que é um named_scope e conhece algumas possibilidades, como condições dinâmicas e encadeamento de named_scopes.
Apenas lendo uma introdução aos named_scopes já dá pra ficar bem empolgado. É um recurso simples e, ao mesmo tempo, poderoso e, quando bem utilizado, pode resulta no código bonito e sucinto que buscamos todo dia.
Vamos, então, explorar mais algumas possibilidades desse recurso.
Clique para ver o artigo completo…
Ruby quick tip: Blocos para fallback em hash lookups
Normalmente, ao tentar fazer um lookup em um hash com uma chave não existente, você tem o seguinte comportamento:
>> h = {:foo => "bar"} => {:foo=>"bar"} >> h[:other_foo] => nil
Você pode adicionar um bloco para tratar esses casos:
>> h = Hash.new { |hash, key| "#{key} is not here"} => {} >> h[:foo] => "foo is not here"
É possível, inclusive, alterar o hash em questão:
>> h = Hash.new { |hash, key| hash[key] = "value for #{key}" } => {} >> h[:foo] => "value for foo"
Learncast #1: BDD leve com Shoulda - testando modelos ActiveRecord
Behavior Driven Development parece ganhar tração constantemente nas comunidades de desenvolvedores de teste. Isso não é à toa: essa “nova” mentalidade dá o toque semântico que faltava às técnicas de Test Driven Development.
Na comunidade Rails, os três projetos mais conhecidos na área de BDD são RSpec, Shoulda e test/spec. Minha escolha é o Shoulda, que utilizo desde o lançamento com muito sucesso. Há um bom tempo venho “rascunhando” um screencast introdutório e, finalmente, ele está pronto. Clique aqui para baixá-lo em formato QuickTime.
O objetivo do screencast é apenas mostrar superficialmente o que é Shoulda e como testar funcionalidades de modelos ActiveRecord (como validações e associações). Em breve virão mais alguns cobrindo testes de controllers e mais detalhes sobre o uso do plugin.
Links interessantes:
Shoulda: tutorial | repositório | RDocs | bundle para TextMate
Plugin QuietBacktrace
Comentários, críticas e complementos são muito bem-vindos!
Obs: estou resfriado, mas fiz o possível para deixar o som o mais claro possível. Por favor, avise caso eu não tenha conseguido.
Update: como apontado nos comentários, subi o vídeo sem o som (duh!), mas isso já foi corrigido. Obrigado pelo aviso, pessoal.
Update 2: disponibilizei o script e as imagens que utilizo para o Autotest com o Shoulda em minha máquina, rodando o Leopard. Crie, na pasta home de seu usuário (/Users/<nome_do_seu_usuário>/), uma pasta chamada .autotest_images e copie as duas imagens para lá. Crie, também na home, um arquivo chamado .autotest e preencha-o com o script contido no pacote. Esse script é baseado em um script publicado pelo Carlos Brando.
Rails-footnotes, um plugin fundamental
Se você desenvolve em Rails no MacOS X com TextMate, você tem que utilizar o plugin rails-footnotes. Sério. É obrigatório. Mesmo.
Esse plugin coloca um rodapé em todas as páginas de sua aplicação, quando no ambiente de desenvolvimento, mostrando várias informações, como parâmetros da requisição, sessão, cookies, filtros, rotas, queries e log. Além disso, contém também links para abrir arquivos (como o controller ou view atual) no TextMate. Isso permite que você, enquanto navega pela aplicação, possa abrir no TextMate arquivos relacionados a página aberta no navegador, como o controller, a view, o layout ou a folha de estilos.
Parabéns ao José Valim, principal desenvolvedor do plugin atualmente.
Extremamente recomendado.
Dica: Migrations com comandos SQL e problemas com testes no Rails
Se você utiliza o método execute em suas migrations para rodar comandos SQL na criação de sua base de dados, cuidado ao rodar os testes de sua aplicação. Na criação da base de testes, o Rails não roda as migrations, ele utiliza o script contido no arquivo schema.rb. O problema é que, ao fazer o dump da base para esse arquivo, o Rails não utiliza os comandos SQL definidos nas migrations e sim os métodos da DSL de manipulação de estrutura e dados (como add_index, create_table, add_column etc).
Devido a isso, se você utilizou alguma particularidade do sistema gerenciador de banco de dados que utiliza ao definir sua base, muito provavelmente ocorrerá um erro no banco de dados ao tentar rodar os testes de sua aplicação.
Exemplo:
Em uma migration:
(...) create_table :tests do |t| t.column :test_column, :text end execute("ALTER TABLE test ADD INDEX test_index(test_column(200));") (...)
No MySQL é necessário definir um comprimento para índices em colunas dos tipos TEXT e BLOB e, como não há essa opção no método add_index, utilizamos um comando SQL. No entanto, no arquivo schema.rb, a criação do índice é feita da seguinte maneira:
add_index "tests", ["test_column"], :name => "test_index"
E isso causa um erro no MySQL. Para corrigí-lo, procure em seu arquivo environment.rb pela seguinte linha:
config.active_record.schema_format = :sql
Por padrão ela vem comentada. Retire o comentário para fazer com que o banco de dados de testes seja criado diretamente com comandos SQL. Caso não a encontre comentada, adicione-a dentro do bloco Rails::Initializer.run.
A TIM, assim como as outras operadoras, é uma porcaria
Sempre recomendei a TIM como operadora móvel GSM no estado de São Paulo… até recentemente, quando precisei da empresa para algo a mais do que triviais ligações e mensagens.
Eu possuía um plano pós-pago e um número com DDD 14 (interior de São Paulo). Me mudei para a cidade de São Paulo e entrei em contato com a TIM para mudar meu DDD (perderia meu número anterior) e mudar para um plano pré-pago. Fui informado de que isso era possível mas, mesmo assim, tive que ficar uma hora e quarenta minutos negando um plano de conta fix que o atendente ficava continuamente empurrando. A regra deve ser: “Não basta dizer NÃO uma vez, são necessárias cinqüenta vezes para ter certeza”.
Após isso, o atendente inicia a migração. Note que ele deveria, em primeiro lugar, mudar o número e, após isso, o plano. Se você tem um plano pré-pago, não consegue alterar seu número, deve comprar outro chip.
Fico meia hora na linha enquanto a migração era realizada, a ligação cai e fico sem qualquer sinal por uns dez minutos. O sinal retorna, consulto o plano via SMS e vejo que estou com cinco reais de crédito num plano pré-pago. Mas o número permanecia o mesmo. Entro em contato novamente e sou informado de que clientes de planos pré-pagos não podem alterar seu número. Passo os números de protocolo e nome do atendente e me afirmam que, mesmo sendo um erro da empresa, teria que comprar um outro chip ou entrar com um processo na justiça (claro, com uma risadinha irônica, pois o trabalho pra levar adiante um processo é muito comparado a quinze reais de um novo chip).
Resultado: estou com o número antigo e plano pré-pago, graças a mais um show de incompetência e desrespeito.
Ingenuamente eu ainda acreditava e recomendava a TIM para meus amigos. Não mais. Estamos mergulhados em um mar de incompetência e amadorismo quando se trata de telefonia no Brasil.
Fui mais educado no título, mas a melhor frase para descrevê-las é: “A TIM, assim como as outras operadoras, é uma merda“.

