ChatOps em Ruby com Testes

Publicado por Gabriel Mazetto no dia dev

No mês de setembro estivemos na RubyConf Brasil 2015, e um dos assuntos que atraiu a atenção de quem passou pela trilha de Workshops 3 foi o nosso processo de desenvolvimento e entrega de software: o deploy via chat.

Na Resultados Digitais usamos o (Slack) para diversas atividades: coordenar os diversos times; sincronizar e auxiliar o trabalho do time de Suporte, Customer Success e Vendas; discutir novas funcionalidades e auxiliar os membros que estão trabalhando remotamente a terem o mesmo fluxo de comunicação que o pessoal do escritório.

Através do chat também conseguimos controlar diversas funcionalidades de operação, como receber alertas de serviços, realizar deploys em staging e produção alinhados com integração contínua. Isso permite que todos os interessados saibam o que está acontecendo de forma transparente e efetiva.

Quem introduziu o assunto foi o nosso CTO, Bruno Ghisi (@brunogh) com a palestra “Escalando times e salvando casamentos com continuous delivery”, onde ele contou como, na metade de 2014, ele conseguiu repassar a responsabilidade de fazer deploys e homologar em produção para cada membro dos times de Produto (incluindo designers), com a garantia de que conseguiriam fazer um trabalho tão bom quanto.

Mostramos em seguida com a minha palestra, o que roda por trás do Capybot, o nosso robô responsável por executar tarefas automatizadas da operação:

Os slides conseguem dar uma noção do que foi comentado e oferecem explicações e uma boa oportunidade para pesquisar sobre o assunto.

O que eu não mostrei na apresentação foi como fazer testes unitários. Isto vou compartilhar com vocês agora.

Vamos usar novamente o exemplo do Handler das Keynotes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module Lita
  module Handlers
    class Rubyconf < Handler
      route(/keynote$/i, :next_keynote, command: true, help: {"keynote" => "Display the next keynote"})

      def next_keynote(message)
        keynote = Keynote.get_next
        message.reply "Next keynote will be: #{keynote.title}"
      end

    end

    Lita.register_handler(Rubyconf)
  end
end

O código acima programa o nosso bot para responder qual a próxima palestra, obtendo essa informação de uma classe hipotética “Keynote”.

Nó código da rota, passamos os seguintes atributos em sequência: expressão regular (/keynote$/i) com o texto que estamos esperando, método que vai ser chamado (:next_keynote), marcamos opcionalmente que o texto deve mencionar o bot com command: true e por fim, o texto de ajuda que será apresentado quando o usuário pedir um lita help.

O arquivo de testes vai ser mais ou menos assim:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
describe Lita::Handlers::Rubyconf, lita_handler: true do
  # Rotas
  it { is_expected.to route_command('keynote').to(:next_keynote) }

  # Comandos
  describe '#next_keynote' do
    before(:each) do
      allow(Keynote).to receive(:next_keynote) { 'Chatops in Ruby' }
    end

    it 'replies with next keynote' do
      # É aqui que simulamos textos enviados pelos usuários, mencionando o bot
      # Isto é equivalente ao usuário digitar: @lita: keynote
      send_command('keynote')

      # O que esperamos receber
      expect(replies.last).to include('Chatops in Ruby')
    end

    it 'it only replies when mentioned' do
      # Esta é uma simulação de mensagem normal dita em um canal, sem mencionar o bot
      send_message('keynote')

      expect(replies.last).not_to include('Chatops in Ruby')
    end
  end
end

O restante da complexidade da operação não diz respeito ao próprio bot. Este é um dos motivos pelos quais não estou me importando em testar e implementar nesse exemplo a classe “Keynote”.

No entanto é importante ter a separação clara entre “Handler” e “Keynote”. Devemos testar o keynote em um spec separado e isolado do Lita. Uma boa analogia é compreender que o Handler é uma mistura de roteamento + controller.

Os exemplos que passei são mais didáticos, para demonstrar como é simples começar a usar o Lita.

Existe um guia mais detalhado sobre como escrever plugins para ele (como este que mostrei): http://docs.lita.io/plugin-authoring/. Outra forma de aprender é ver como outros plugins foram feitos, e existem vários: https://www.lita.io/plugins#handlers.

Operação é uma das tarefas mais complexas que existem e, sem automação, estaremos fazendo um trabalho repetitivo, sujeito a falhas de processo e bastante custoso. Existem milhões de benefícios para se automatizar processos, e ChatOps é uma ótima abordagem para isso, além de poder ser divertido.

Se você produzir algum plugin interessante, comente neste artigo e nos avise! :)

Gabriel Mazetto

Gabriel Mazetto

Full Stack Developer

Comentários