Páginas

SyntaxHighlighter

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.