TDD (Desenvolvimento Guiado por Testes)

A primeira pessoa a apresentar este método de desenvolvimento foi Kent Beck, autor do livro Extreme Programming, e relata em um capitulo de seu livro a necessidade de testar prematuramente, frequentemente e automaticamente, destacando que tal abordagem é necessária, pois quanto mais cedo encontrarmos erros mais barato será consertá-lo. Três anos mais tarde ele lança o livro Test Driven Development by example definindo uma estratégia de desenvolvimento que além de permitir detectar erros prematuramente, cria um ambiente de trabalho que favorece o desenho de uma boa arquitetura.

A principal característica do TDD é sua simplicidade, onde a principal regra a seguir é: criar um teste que falhe, escrever o código para o teste passar e em seguida refatorar seu código.

Perceba que não foi dito que uma linguagem é preferível que outras, nem que uma IDE é necessária para trabalhar desta forma. É claro que será necessário um framework de testes e o Kent Beck criou um que foi base para todos os outros que surgiram, o JUnit.

Certo, neste ponto começam a surgir as dúvidas, sobre como definir os métodos de testes, qual a granularidade do meu teste e por onde começar.

Um sistema sempre vai começar a partir dos requisitos ou estórias, uma boa documentação que defina quais serão os comportamentos do software.

Nós, na Sigma, descrevemos o comportamento de nosso sistema utilizando estórias. Então vamos escrever uma estória hipotética que define o comportamento de um sistema.

Eu como comprador

quero adicionar produtos comprados ao estoque

para manter o saldo em estoque atualizado;

Analisando a estória acima podemos extrair algumas entidades que o sistema de almoxarifado deve ter: Produto e Estoque. Além disso podemos visualizar os comportamentos esperados: dar entrada no estoque de um produto, consultar saldo de um produto em estoque;

Então podemos criar um teste que falhe para um dos comportamentos:

import unittest

class TestEstoque(unittest.TestCase):

    def test_estoque_deve_dar_entrada_em_produto(self):
        produto = Produto()

Este teste vai falhar, pois não existe uma classe Produto definida em nosso sistema.

import unittest


class Produto(object):
    def __init__(self):
        pass


class TestEstoque(unittest.TestCase):

    def test_estoque_deve_dar_entrada_em_produto(self):
        produto = Produto()

O código vai passar então podemos refatorar e criar corretamente um módulo e retirar o código da classe produto do module de teste;

Damos o segundo passo na implementação:

import unittest

class TestEstoque(unittest.TestCase):

    def test_estoque_deve_dar_entrada_em_produto(self):
        produto = Produto()
        estoque = Estoque()
        quantidade = 10
        estoque.entrada(produto, quantidade)

Perceba que avançamos alguns passos extras na implementação, pois temos experiência na criação de classes e podemos ser flexíveis e escrever mais trechos de código que temos fluência. Esta flexibilidade não pode ser usada em excesso, pois o principal objetivo ao escrever código que falhe é que esta falha seja fácil de ser encontrada, portanto se eu escrever muitas linhas de código eu posso gastar muito tempo consertando ele. Portanto aprecie com moderação.
O código acima está falhando, pois o método entrada não está implementado.

Portando vamos implementá-lo:

class Estoque(object):

    def __init__(self):
        self._estoque = []

    def entrada(self, produto, quantidade):
        self.estoque.append({
            'produto': produto,
            'quantidade': quantidade
        })

Novamente o teste vai passar, mas como definir que este teste é válido?

Asserções!

Asserção é um método para validar se uma condição de nosso teste é válida e caso ela não seja, minha asserção não será válida e vai lançar uma exceção.

import unittest

class TestEstoque(unittest.TestCase):

    def test_estoque_deve_dar_entrada_em_produto(self):

        # Definições
        produto = Produto()
        estoque = Estoque()
        quantidade = 10

        # Ações
        estoque.baixar(produto, quantidade)

        # Asserção
        saldo = estoque.saldo(produto)
        self.assertTrue(saldo == 10)

Perceba a estrutura de um testes, eu defino o estado inicial para meu teste, tomo uma ação e verifico se meu teste chegou ao estado esperado.

Este artigo serviu de base para o screencast sobre TDD, que foi gravado e está disponível neste link, com a apresentação também disponível neste link.

Geoprocessamento Online na Gestão Pública

Para a grande maioria dos municípios brasileiros o gerenciamento espacial de propriedades e lotes é um problema crônico que demanda solução urgente para governanças. A exigência por modelos de gerenciamento práticos e transparentes, oferencendo bases reais e confiáveis para tomadas de decisão, nunca foi tão pertinente..

Para entender a gravidade do tema é preciso observar, por exemplo, a execução e desenvolvimento de um plano diretor municipal, condizente com expectativas sociais e urbanas. A ação depende de soluções eficientes para administração espacial dos territórios. Tratam-se de ferramentas que já estão disponíveis no mercado mas que foram apropriadas por poucos governos municipais.

A atualização dos modelos tradicionais de geoprocessamento deve ser tratada como prioridade. A falta de investimento em tecnologias eficientes, melhor adaptadas à administração pública, pode custar caro às contas municipais e, consequentemente, para a população que vive nelas. Para entendermos a dimensão dos prejuízos basta considerar que praticamente todos os aspectos urbanos se processam em uma unidade geográfica organizada definindo as propriedades e prioridades de um município. O que isso quer dizer? Basicamente que, no âmbito da administração pública, quando o município não investe em soluções de geoprocessamento urbano, ele deixa de investir no planejamento da cidade e nas estruturas institucionais dela. Do transporte público à saúde, do zoneamento à educação, o uso de geoinformação e geotecnologias otimiza toda estrutura e serviços do município antes mesmo deles serem inaugurados.

Tecnologias de geoprocessamento eficientes, quando aplicadas à administração pública, garantem o acesso à informações integradas promovendo a gestão do território. A sociedade, dessa forma, usufrui, a longo termo, de serviços públicos mais eficientes e de melhor qualidade. Trata-se, portanto, de um investimento de baixo custo relativo que promove a sustentabilidade econômica e social do município a médio e longo prazos.

Sistemas web centralizados, com a representação cartográfica dos espaços municipais a partir de informações técnico-descritivas potencializam a gestão municipal. O uso destas ferramentas permite a disseminação de conteúdo entre todos os órgãos municipais e para o público, tornando as ações da prefeitura mais eficientes, transparentes e econômicas. A tecnologia para esse upgrade está disponível no mercado e registra grandes benefícios entre os municípios que implantaram sistemas de geoprocessamento digitais.

Celery e Tarefas assícronas com Python

O Celery é um aplicação, escrita em Python, que permite que você delegue tarefas a diversos workers, localizados em outros servidores.

O Celery é uma forma bastante de realizar estes jobs ou tarefas de forma assíncrona, sem ter de quebrar muito a cabeça com conceitos complicados e difíceis de serem acertados.

Primeiramente, vou explicar como o Celery funciona, em termos de arquitetura.

Temos alguns componentes principais envolvidos:

  1. Aplicação – este carinha aqui é a sua aplicação normal, a qual o usuário ou outra máquina interage;
  2. Broker – este componente é uma fila de mensagens, utilizada para transportar mensagens entre os processos que estamos descrevendo aqui. O Celery permite o uso de diversos brokers, como redis e RabbitMQ. Usamos o redis por aqui, mas é uma preferência;
  3. Worker – este é o componente que trabalha de verdade. Qualquer tarefa agendada pela aplicação no broker, será executada pelo worker;
  4. Beat – é um componente opcional, mas funciona como um agendador de tarefas, que dispara de tempos em tempos as tarefas para o broker, como uma aplicação;

Com esta arquitetura, é bastante fácil escalar a coisa toda para funcionar em várias máquinas, bastando adicionar mais workers conforme a frequência das tarefas aumentam. O Celery é muito customizável e tem opções importantes, como controle de filas (algumas tarefas só podem ser processadas uma a uma, em uma única fila, sem concorrência alguma, enquanto outras podem ser paralelizadas), aplicaçoes de monitoramento (como o Flower) e extensões de biblioteca para os desenvolvedores (como o Jobtastic).

Vamos hoje dar um exemplo bastante simples, integrado a um projeto Django. Não vamos entrar em muitos detalhes de como subir os workers ou o broker, pois vai depender muito do seu ambiente.

Imagine que sua aplicação manda emails. Quase todas as aplicações (web) mandam emails, mas algumas mandam mais emails que outras e com maior frequência. Entre o ponto em que uma requisição feita pelo usuário é processada e uma resposta é retorna, vários segundos podem se passar e não queremos deixar o usuário esperando. Então decidimos delegar o envio do email, que é mais demorado, para uma tarefa assícrona no Celery.

Como fazer isto?

Bem, primeiramente é necessário configurar sua aplicação Celery. Fazemos (aqui na SIGMA) por meio de um arquivo celery.py, localizado junto wsgi.py do projeto. Ele é mais ou menos assim:

# coding: utf-8
"""Configuração inicial do Celery"""
from __future__ import absolute_import
import os
from django.conf import settings

if not "DJANGO_SETTINGS_MODULE" in os.environ:
    os.environ["DJANGO_SETTINGS_MODULE"] = "settings.local"  # aqui na SIGMA usamos settings separados para cada ambiente

from celery import Celery
app = Celery('nome-da-app')

app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

Com esta configuração batuta, podemos iniciar um worker, por exemplo. Além disso, usamos o autodiscover do Celery, que percorrerá todas as INSTALLED_APPS e cadastrará todas as tarefas dentro dos módulos tasks.py.

Ok, com o Celery configurado, podemos startar um worker.

celery -A projeto worker -l info

Agora, vamos criar nossa task e chamar ela de dentro de uma view qualquer. Crie um arquivo chamado tasks.pyem uma de suas apps, por exemplo, sistema.

Para definir a task, utilizaremos um decorador:

from projeto.celery import app  # lembre do nosso arquivo celery.py?


@app.task()
 def email_async(email, assunto, template, contexto):
    template_html = get_template(template)
    template_txt = get_template(template.replace('.html', '.txt'))
    if isinstance(contexto, dict):
        contexto = Context(contexto)

    contexto['email_header_image'] = get_random_image()
    html = template_html.render(contexto)
    txt = template_txt.render(contexto)
    if isinstance(email, basestring):
        email =[email]

    send_mail(assunto,
              txt,
              'remetente',
              recipient_list=email,
              html_message=html)

Com a tarefa definida, podemos invocá-la de forma assícrona. O Celery tem muitas formas de se invocar uma tarefa. Existe um negócio chamado canvas que permite você combinar as tarefas de forma assícrona, paralelas, etc. Confira a documentação do canvas.

Vá em uma de suas views que enviam emails:

from .tasks import email_async

def view_qualquer(request):

    # faça alguma coisa útil

    email_async.delay('novo email', 'sistema/email.html', {'variavel_contexo_a': 'foo', 'b': 'novo email maroto'})

O segredo da chamada assícrona está no método delay, que irá usar o broker para agendar a tarefa e futuramente será executada por um worker.

É um resumo muito curto para mostrar todas as capacidades do Celery, mas é um projeto muito bem mantido, utilizado por muita gente.

O exemplo que demos, apesar de parecer bobo, é bastante significativo quando estamos falando de uma aplicação que envia centenas ou milhares de emails por dia, diminuindo muito o impacto nos servidores de aplicação e permitindo que você lide com uma carga bem mais alta.

Iremos fazer uma pequena série sobre o Celery. Tem jeito de consultar o status da tarefa, agendar tarefas periódicas, controlar exceções e um monte de coisa legal.

Fique ligado e obrigado!

Automação de Infraestrutura com Puppet

Let the computer do the repetitious, the mundane – it will do a better job of it than we would. We’ve got more important and more dificult things to do.
Trecho retirado do livro Programador Pragmatico.

O profissional de desenvolvimento de software conta com um grande trunfo em sua manga que é frequentemente negligenciado, um trunfo que vem sendo utilizado vastamente por outras áreas em que sua complexidade é maior do que a encontrada na área de software. Com a automação pode ser alcançado o que economia chama de lucro, pois um profissional aumenta sua produtividade e a qualidade do que é produzido. Estes dois parametros trarão uma enorme mudança em sua carreira, então a partir deste momento se torne um profissional que se vale deste trunfo e se destaca dos demais, simplesmente aplicando em seu trabalho o que de melhor fazemos: automatizar tarefas.

Durante os ultimos anos vemos as práticas DevOps se tornando cada vez mais a cultura dos desenvolvedores e administradores. Neste artigo faremos uma comparação da utilização de uma das tecnologias de escrita de infraestrutura como código criada pela empresa Puppet, o produto escolhido se chama puppet-agent e vamos escrever em uma dsl de mesmo nome, que tenta tornar este processo tão simples como listar quais software serão utilizados.

#####Aplicamos esta prática em dois cenários:

#####Windows:

Neste cenário o cliente utiliza Windows, embora esta arquitetura seja compatível com ambientes linux o cliente tinha mais experiência com Windows e o utilizava em todos ambientes do desenvolvimento à produção. Para prover serviços de mapa e hospedar a aplicação web map é utilizado IIS 7.5, Geoserver, Postgresql e Postgis. Nós escolhemos o puppet-agent e o script será aplicado em uma maquina existente preparada para receber as instalações oriundas do script puppet.

O primeiro passo é instalar o puppet para poder executar o script, que como dito é uma dsl simples

A script que criamos é composta por resources, cada resource tem funções como: instalação de pacote, execução de comandos shell, agendamento de tarefas, administração de serviços, criação de usuários, operações no sistema de arquivos e etc.

Os resources podem ser combinados de forma a determinar qual é o resource requerido para que um outro seja executado, ou assim que determinado resource for executado ele pode notificar outro para que este seja executado.

Uma dificuldade ao utilizar o puppet para instalação de sistemas é que o windows não possui um gestor de pacotes, os instaladores são individuais e distribuídos individualmente por cada fornecedor. Desta forma o processo de instalação exige que em alguns casos seja utilizado o processo de instalação headless que é disponibilizado pela ferramenta de empacotamento utilizada pela empresa que provê o instalador do software. Infelizmente por não haver uma padrão isto é o que mais dá trabalho durante a redação do script.

Após as instalações é necessário configurar as aplicações instaladas, iniciar serviços ou agendar tarefas.

Basicamente utilizamos o resources exec, que permite executar comandos shell, o file, que permite copiar arquivos e o scheduled_task para agendar tarefas.

Com o exec nós configuramos o postgres, executamos os scripts sql de criação das feições espaciais, com o file copiamos arquivos, como por exemplo o diretório data_dir do geoserver e com o scheduled_task configuramos a tarefa de execução do geoserver.

Utilizamos também o Hiera para criar um arquivo de configuração externo e permitir que o script fosse executado independente dos diretórios em que os arquivos existiriam ou do local em que desejamos instalar.

A grande desvantagem foi o tempo que levamos para montar este script. É bastante trabalhoso e em por utilizarmos o windows neste cenário, tivemos pouca documentação disponível principalmente para as instalações headless de cada instalador. Porém quando este desafio foi ultrapassado a grande vantagem foi conseguir instalar com pouquissima dificuldade quatro maquinas diferentes, uma maquina na cloud (Windows Server 2012 R2 ) e três estações de trabalho com Windows 7. O primeiro grande beneficio é que temos a certeza que todas as maquinas possuem a mesma versão e configuração e uma vantagem é que nossa equipe é distribuída e podemos contar com o apoio de outros desenvolvedores que não estejam geográficamente perto e eles conseguirão replicar o ambiente com rapidez e poderão efetivamente apoiar o desenvolvimento do software.

#####Linux:

Montar a maquina servidora de um web map escrito em python e javascript, que consulta um banco de dados geográfico.

De cara podemos dizer que só há vantagens ao utilizar vagrant e puppet para montar ambientes em Linux (dist Ubuntu) não tivemos um décimo das dificuldades apresentadas no Windows e o tempo transcorrido para redigir os scripts foi muito menor.

O maior desafio que encontramos é que algumas versões disponíveis por padrão nos repositórios do apt-get podem estar com uma grande defasagem, o que pode induzir ao erro ao usar uma versão antiga que contém bugs que já foram resolvidos, porém uma vez que você configura o repositório correto do pacote e instala as versões mais recentes o restante é muito prático.

Nós utilizamos postgres e postgis e é excelente ter um repositório de pacotes, fizemos todas as configurações, criação do database e configuração da extensão postgis.

Uma novidade foi utilizar um provider diferente ao utilizar o resource package, pois utilizamos o pip3 para instalação dos pacotes python, porém não tivemos problema algum.

Não identifiquei desvantagens ao escrever o script puppet para uma maquina linux, basta ter atenção às versões que são instaladas pelos repositórios default do apt-get e na dúvida utilize o repositório da fornecedora do software.

Para não dizer que a experiência foi livre de problemas, perdemos um tempo para instalar corretamente o puppet, pois às maquinas ubuntu disponíveis não trazem mais esta instalação por padrão.

A grande vantagem neste caso é que a replicação para outros provider do vagrant se torna fácil, permitindo que eu utilize o virtualbox, vmware, amazon aws ou digital ocean.

Spatialite Ubuntu 16

A biblioteca e o projeto spatialite são bastante especiais. Ela permite que você use um banco de dados SQLite como repositório de dados espaciais, com várias tabelas e relações.

Ela é bastante poderosa neste sentido, pois facilita que usuários GIS armazenem e compartilhem seus dados de forma mais simples, ao invés de enviar um monte de shapefiles para cima e para baixo.

No nosso caso, o problemas são com o Django, não com a biblioteca em si. Devido a algumas mudanças, o Django não consegue rodar, usando ela.

Bem, de acordo com a nova versão do Django e da SpatiaLite, algumas coisas mudaram de lugar e nem sempre o Django consegue encontrar a biblioteca.

Se vocẽ ver um erro do tipo:

Unable to load the SpatiaLite library extension “libspatialite.so.7” because: /usr/lib/x86_64-linux-gnu/libspatialite.so.7: undefined symbol:
sqlite3_spatialite_init

você encontrou o mesmo problema que nós.

Isto foi experimentado na versão do Ubuntu 16.04 e provavelmente afeta a todos os derivados (no nosso caso, afetou o Mint).

A resolução é simples. Instale algumas coisas e defina uma variável de ambiente:

Instale:

sudo apt-get install libspatialite3-mod-spatialite

Configure sua variável de ambiente:

echo SPATIALITE_LIBRARY_PATH="mod_spatialite" >> ~/.bashrc

Reinicie seu shell para que ele possa ler a configuração (ou dê um source no mesmo, tanto faz :D).

No Django, configure:

SPATIALITE_LIBRARY_PATH = 'mod_spatialite'

E aí, tiveram problemas com essa atualização?

Abraços