Páginas

SyntaxHighlighter

terça-feira, 9 de novembro de 2010

A arte de desenvolver sem branches

Ultimamente eu e meu time estamos evitando ao máximo a criação de branches no SVN. Nosso objetivo é comitar apenas no trunk e tentar manter a aplicação sempre releaseable. Acreditamos que isso ajuda a manter o fluxo de entregas constante e o caminho aberto para o sonhado continuous deployment e delivery.

Nossas últimas experiências com feature branch foram desagradáveis. Além dos (muito) chatos, às vezes longos, e arriscados merges envolvidos na criação de um novo branch, o que mais incomoda é a falta de integração contínua e o "tumulto" causado quando o branch é reintegrado ao trunk - dependendo do tamanho e o grau de intrusão é necessária uma longa fase de estabilização.

Para evitar esse transtorno, uma prática que começamos a adotar é, ao invés de criar branches no controlador de versão, fazer as ramificações no próprio código fonte - branch by abstraction. Basicamente o que fazemos é criar flags em arquivos de propriedades da aplicação e utilizá-las para esconder/desabilitar uma feature incompleta (GUI) ou para chavear entre uma implementação ou outra.

Características do design, por exemplo utilização de patterns como  Dependency InjectionStrategy e Factory, facilitam bastante o trabalho. Mais que isso, já vi casos de refactorings para suportar esse tipo de chaveamento, melhorar o design através da redução do acoplamento, e nascimento de novos componentes!

É uma estratégia bem simples de adotar e que traz alguns benefícios. Como continuamos a comitar no mainline, o código segue sendo continuamente integrado pelo Hudson juntamente com as demais features. Se bem planejado e estruturado, é possível entregar a feature aos poucos, em pequenas partes, reduzindo o risco das implantações. A cada release pouquíssimo código novo é implantado e consequentemente a identificação de eventuais bugs ou efeitos colaterais indesejados é antecipada, reduzindo o custo de investigação e correção.

Para ilustrar: suponha que temos uma nova demanda que alterará drasticamente o cerne da aplicação. Pior que isso, a estimativa para terminá-la é de alguns dias, podendo chegar até uma semana. Em paralelo várias outras melhorias e correções de bugs menores irão acontecer. O objetivo é entregar o quanto antes tudo o que for ficando pronto e evitar ao máximo o "represamento" de código.

Enxergo três opções: A primeira, e pior de todas, é sair implementando tudo no trunk deixando a aplicação unreleaseable por um longo período de tempo. A segunda é criar uma feature branch para a alteração maior e desenvolver as demandas menores no trunk, dessa forma conseguimos manter o ritmo de entregas, mas o código da alteração maior é "estocado". A última é criar um code flip, implementar todas as demandas no trunk só que deixando a grande alteração dormente e escondida. Apesar de incompleto e invisível para o usuário o código faz parte dos binários entregues, passou pela integração contínua e por QA - já está sendo aceito, está implantado!

Alguns pontos de atenção caso opte pela feature flag:
  • Aposentar os code flips e garantir a remoção do código obsoleto;
  • Manter ambos os caminhos (code paths) enquanto as duas versões coexistirem.
Volto a mencionar que esse tipo de decisão precisa ser bem pensada. Já tivemos períodos tensos com bloqueamento de implantações - tanto com branches normais como abstratas. Se seu cliente estiver "mal acostumado" com várias entregas por semana, por exemplo, e você começar a faze-lás quinzenalmente, pode ter certeza que ele vai reclamar. Além do que, a sensação de receber software concreto quase que diariamente, é mil vezes melhor que um "burndown na pinta".

É importante ressaltar que apesar do título, não estou dizendo que nunca criarei branches ou que é sempre melhor evitá-los. Sei que existem casos onde não há outra saída. O importante é saber tomar esse tipo de decisão - focar nas entregas mais frequentes.

Para se aprofundar melhor:

sexta-feira, 29 de outubro de 2010

Using Text instead of String to store large texts in GAE's datastore

I just started playing with Gaelyk and GAE. The goal is to learn the GAE APIs and services and improve my groovy foo!
So, in order to practice it I've chosen to develop a simple pastebin like application using Gaelyk.

I started it by modeling the main entity: Snippet, which is a very simple POGO with only 3 properties:

class Snippet implements Serializable {
 
 String name
 String text
 String language
 
}

The groovylet below ilustrates how we can easily instantiate a new entity from the posted request parameters and then persist it into the data store.

def snippet = new Snippet(params);

// coerce a POJO into an entity
def snippetEntity = snippet as Entity
snippetEntity.save()

forward "/snippet.gptl"

So far so good. After some tests I got the following error:

Error: GroovyServlet Error: script: '/WEB-INF/groovy/snippet/create.groovy': Script processing failed.name: String properties must be 500 characters or less. Instead, use com.google.appengine.api.datastore.Text, which can store strings of any length.com.google.appengine.api.datastore.DataTypeUtils.checkSupportedSingleValue(DataTypeUtils.java:192)

The message is very self explanatory. I changed the Snippet and the groovylet respectively to:

import com.google.appengine.api.datastore.Text

class Snippet implements Serializable {
 
 String name
 Text text
 String language
 
}

// create.groovy
def snippet = new Snippet();
snippet.name = params.name;
snippet.language = params.language;
snippet.text = params.text as Text;

def snippetEntity = snippet as Entity
snippetEntity.save()


Note that I changed property type from String to Text and had to set each property separately in the groovylet because there is no autocasting support for Text types.

You can check the last deployed version in AppEngine here: Snippetr
And the source code here: http://code.google.com/p/snippetr/

sábado, 17 de julho de 2010

Powerful Excel Data Driven Tests with NUnit

Creating a good suite of automated data driven tests calls for an easier and more maintainable way for inputing data and asserting output results - it should be easy for everyone (team members and even customers) to modify and create new scenarios.

Often the complexity involved on scenario creation is not technical at all, the business rules normally makes it much more difficult. That´s why we need better tools/GUI for creating and maintaining these scenarios.

Turns out spreadsheet softwares like Excel happens to be the right tool for the job - well known GUI, capable of performing complex calculations and very good for organizing and structuring data. Then, why not take advantage of such powerful tools integrating them with an XUnit framework? Well that´s what this post is about.

Integrating NUnit Parameterized Tests and Excel Data Reader


Recently I had the same need and since I´m on a .NET project, I came up with a helper class for creating NUnit´s data driven testcases from Excel spreadsheets: ExcelTestCaseDataReader (this could probably be achieved by using any XUnit framework and Excel library in any language or plataform).

ExcelTestCaseDataReader basically provides a fluent interface (this was my first time applying the builder pattern for creating such an interface so don´t be too demanding) which encapsulates the complexity for reading excel spreadsheets - either from an embedded resource or from the file system, using the (very good) Excel Data Reader library - and turns them into NUnit´s testcases.

Fortunately NUnit has some very good and flexible built-in features which help us creating this kind of parameterized tests. Combining NUnit´s TestCaseSource attribute and the ExcelTestCaseDataReader does the magic.

Setting things up


The example below shows how to create and configure an ExcelTestCaseDataReader instance which will load the tests.xls workbook from the file system and include the content of Sheet1 and Sheet2.

var testCasesReader = new ExcelTestCaseDataReader()
.FromFileSystem(@"e:\testcases\tests.xls")
.AddSheet("Sheet1")
.AddSheet("Sheet2");

Then , to get the TestCaseData list, you should invoke the GetTestCases method passing it a delegate which should know how to extract the details from each row and transform them into a TestCaseData.

var testCases = testCasesReader.GetTestCases(delegate(string sheetName, DataRow row, int rowNum)
 {
      var testName = sheet + rowNum;
      IDictionary testDataArgs = new Hashtable();
      var testData = new TestCaseData(testDataArgs);
      return testData;
  }
);

Adding to NUnit


Now you just need a test method which uses the TestCaseSource attribute. There are numerous options for yielding the data, and one of them is to define an IEnumerable as a public method of your test class. In the example below the MyTest method takes its TestCases from a static method called "SampleTestCaseData".

[Test]
[TestCaseSource("SampleTestCaseData")]
public void MyTest(IDictionary testData)
{
  //do the assertions here
}     

And finally the SampleTestCaseData snippet. It just iterates on the testCases list we created earlier (with ExcelTestCaseDataReader) and yields each of them.

public static IEnumerable<TestCaseData> SampleTestCaseData
{
    get
    {
        foreach (TestCaseData testCaseData in testCases)
        {
            yield return testCaseData;
        }
    }
}

Conclusion


NUnit is a great testing tool and has great data driven tests capabilities. Sometimes you need to provide non tech people the ability to maintain test scenarios or maybe just need a better tool - with a good and user friendly GUI - for scenario creation.

The ExcelTestCaseDataReader is just a basic example of what can be achieved by integrating excel (front end) and nunit (back end). And how it could help alleviating the burden of such boring tasks.

This small framework has been very useful for me and my team and although is´s very limited it can be easily extended to achieve your own needs.

Looking forward to hear about different approaches on data driven tests automation!

Relato de um viciado em testes

No projeto onde trabalho há quase um ano a funcionalidade mais importante da aplicação é a habilidade de gerar Balanced Scorecards das diversas estruturas da empresa baseados em variados tipos de configurações, fórmulas, cargas de métricas e complexas regras de cálculos. Tudo isso parametrizável pelo usuário.
Para testar se esses BSCs estão sendo devidamente calculados: aplicação de parametrizações, interpretação de fórmulas, consolidações de indicadores, etc, criamos uma série de testes de aceitação que estressam diretamente o componente BalancedScorecardServices.

Como estruturamos nossa suíte?

Num primeiro momento cogitamos implementar essa suíte através da GUI, mas logo descartamos essa idéia devido as várias dependências que seriam criadas. Ficaríamos dependentes da aplicação estar implantada no servidor de aplicação (IIS) e pior que isso criaríamos uma possível amarração com o markup da tela - complexidades desnecessárias. Daí a decisão de estimularmos diretamente o componente de geração de BSCs - mesma interface utilizado pela GUI.



A manutenção da suiíte consiste em definir os parâmetros de entrada e os resultados esperados para cada cenário de teste numa planilha excel (criamos algumas extensões do Nunit que sabem ler essas planilhas e transformar cada uma das linhas num testcase). Além disso mantemos as configurações e cargas de métricas num banco de dados que completam os cenários cadastrados na planilha.


O servidor de integração contínua (Hudson) é automaticamente disparado à cada nova alteração no código, daí ele gera os binários, implanta no ambiente de testes de aceitação e roda a suíte de testes. Na ocorrência de qualquer erro ou falha todo time é notificado via build radiator e email.

Design Estratégico ou sorte do acaso?

Sorte nada, essa foi uma das decisões mais acertadas do nosso time. Desde a primeira versão desse componente (7 sprints atrás) optamos por investir um esforço extra na criação e automatização desse mecanismo de testes. Primeiro porque sabíamos que tratava-se do core da aplicação e que havia um alto grau de complexidade envolvido na geração e principalmente nos testes desses BSCs e segundo porque tínhamos uma visão bem clara do quanto ainda evoluiríamos esse módulo.

Percebemos também que somente testando os cálculos dessa forma teríamos condições de ter um ciclo de feedback mais rápido e preciso (suíte + CI). Só assim seríamos capazes de evoluir o componente e continuamente adicionar novas funcionalidades com mais segurança.

Atualmente temos aproximadamente 200 cenários diferentes de testes que são executados no mínimo 10 vezes por dia. Imaginem o tempo que gastaríamos se os testes fossem feitos manualmente via GUI?

Confiança, tranquilidade, agilidade, qualidade...

Confiamos cegamente nessa suíte. Até agora, tivémos no mínimo quatro grandes refactorings nesse componente e outros estão por vir. Ora por questões de performance, ora por adição de novas funcionalidades ou correção de defeitos. É muito bom poder mexer à vontade no código, reestruturá-lo, virá-lo de cabeça pra baixo daí apertar um botão e, se tudo estiver verde, ter certeza que tudo continua funcionando como antes.
Tem sido uma ótima experiência e certamente continuarei utilizando esse tipo de abordagem em projetos futuros.

Segue a dica então. Fiquem atentos aos componente críticos e invista, sem medo, numa suíte de testes de aceitação automatizados. Por mais difícil que possa parecer sempre há um jeito. Recomendo fortemente.

domingo, 27 de junho de 2010

Débito Técnico no Agile Brazil 2010

Um dos tópicos mais abordados no Agile Brazil 2010 foi a metáfora Débito Técnico - cunhada por Ward Cunningham.
Logo no keynote inaugural, Martin Fowler abordou o assunto na segunda parte de sua SuiteOfTalks: Economics of Software Design. É uma palestra muito interessante que começa com uma discussão sobre custo X qualidade de software. Não o tipo de qualidade que estamos acostumados a discutir, a externa, e sim a obscura e difícil de mensurar qualidade interna do software - diretamente relacionada à qualidade do design e do código. Terminadas as introduções MF apresenta a Design Stamina Hypothesis, introduz a metáfora Technical Debt e descreve as formas de débitos técnicos usando o Technical Debt Quadrant.
O assunto voltou à tona durante um workshop sobre Release Planning ministrado por Philippe Kruchten. Nesse workshop jogamos um jogo criado por James King (ARPG - The Agile Release Planning Game: Mission to Mars).
No jogo, os participantes são um grupo de engenheiros e cientistas que estão "ilhados" em Marte depois de uma aterrisagem pra lá de problemática. Agora eles precisam reconstruir a infraestrutura básica para sobrevivência antes de voltar para o objetivo original da missão: descobertas cientifícas.
Os participantes devem então montar uma estratégia para: reconstrução da nave danificada, construção de uma base e/ou uma retomada aos objetivos iniciais de pesquisa. Enquanto montam essa estratégia decisões relacionadas à velocidade e qualidade devem ser contrapostas e avaliadas.
O jogo introduz conceitos de planejamento de releases, velocidade, priorização, débito técnico e planejamento adaptativo. Durante o jogo, fica bem claro o impacto dos débitos técnicos não pagos na velocidade.
O palestrante indicou também outro jogo que ajuda a explicar o conceito de débito técnico para gerentes e product owners: o Hard Choices.
Ambos os jogos são disponibilizados sob a licença Creative Commons e estão disponíveis para o público.

A utilização de jogos em treinamentos ágeis parece uma boa alternativa principalmente quando é preciso explicar conceitos mais complexos como débito técnico. Fica aí a dica.

O assunto foi novamente pauta em um dos open spaces que ocorreram no evento. O moderador, mais uma vez, foi Philippe Kruchten.

Na prática

Gosto muito dessa metáfora. Representa muito bem o impacto da qualidade interna do software na velocidade do time e sua capacidade de continuamente adicionar novas funcionalidades ao longo do tempo.

Um dos desafios é como compartilhá-los com nossos clientes. Como justificar a inclusão desses itens nos sprints/iterações. Obviamente, antes disso, é preciso mapeá-los e devidamente incluí-los no backlog, mais que isso, devem ser estimados, tanto o valor da dívida (fácil) quanto o da taxa de juros (difícil).

Fabio Pereira sugere em seu blog uma Techical Debt Wall Retrospective para mapear e priorizar os débitos técnicos de um projeto (imagem abaixo).
Só assim seremos capazes de decidir pelo pagamento, ou não, e qual pagar primeiro. Lógico que nem todos precisam ser pagos, podem existir casos onde continuar pagando os juros é uma opção (not worth, pouca dor = juros baixos) . Porém só chegaremos a esse tipo de conclusão se tivermos uma visão dos débitos dentro do backlog, com quais estórias eles se relacionam, quais componentes sofrem impacto e como tudo isso afeta a análise de valor.