quarta-feira, 17 de novembro de 2010

Bons frameworks C++ para criação automática de mocks

Frameworks para criação automática de objetos substitutos (“mocks”) em C++ foram evoluindo num passo muito mais lento que os para Java, C# ou mesmo Ruby. A dificuldade de criar uma boa arquitetura para simulação da criação automática sem perder a portatilidade entre compiladores e também a falta de desenvolvedores para melhorar os frameworks contribuiram para esse atraso.

Ainda há poucos frameworks altamente portáteis e, menos ainda, estáveis, mas alguns funcionam bem para a maioria dos casos. Há projetos bem estáveis e portáteis, como Google Mock ou MockPP, mas que não abrangem a criação automática de objetos substitutos. Neles, é preciso criar uma classe substituta (implementando a interface que se deseja simular) e usar macros para gerar os métodos.

Os frameworks de geração automática em C++ geralmente exploram o modelo de representação interna (layout ABI) de classes usado em um determinado compilador. Combinado ao uso de templates e algumas técnicas como Fast Delegates, é feito a geração do código na hora da declaração. Como o uso da representação interna varia de compilador para compilador (se bem que em muitos a representação é parecida), alguns projetos deixam de adicionar a compatibilidade com determinados compiladores - até pelo fato de faltar desenvolvedores interessados (ou habilitados) em fazer a adaptação.

Exemplo base

Para exemplificar o uso dos frameworks, tomarei por base o simples código abaixo:

class Piada
{
public:
Piada(const string &autor, const string &texto)
: _autor( autor ), _texto( texto )
{
}
// ... gets e sets
private:
// ... ctor, dtor, ops...
string _autor;
string _texto;
};


// Piada retirada de: http://confiar.atspace.com/curtas_boas.htm
const Piada PIADA_TOSCA( "Desconhecido",
"Você conhece a piada do fotógrafo ? Ainda não foi revelada." );


class RepositorioPiada
{
public:
virtual ~RepositorioPiada() {};
// ...
virtual bool Existe() const = 0;
virtual void Adicionar(const Piada &piada) = 0;
virtual void Alterar(const Piada &piada) = 0;
// ...
};

class ServicoPiada
{
public:
ServicoPiada(RepositorioPiada &repositorio)
: _repositorio( repositorio )
{
}
//...
void Salvar(const Piada &piada)
{
if ( _repositorio.existe( piada ) )
_repositorio.alterar( piada );
else
_repositorio.adicionar( piada );
}
private:
//...
RepositorioPiada _repositorio;
};


Na maioria dos exemplos será feito o uso do UnitTest++, com o qual todos são compatíveis.


Isolator ++


Começando por uma opção paga, lançada mês passado, o Isolator ++ possui uma característica extremamente rara em relação aos concorrentes: permite criar substitutos para classes concretas, métodos não virtuais e métodos estáticos. Além disso, funciona em conjunto como alguns frameworks de teste populares: Google Test, UnitTest++, Yaffut, CPPUnit e Boost::Test.


Porém, ele tem algumas limitações consideráveis, na versão avaliada durante esse post: só funciona com o Visual Studio (2008 e 2010) e métodos abstratos (ex: virtual void FazAlgo() = 0;) devem ser declarados com a macro PURE do Visual Studio (ex: virtual void FazAlgo() = PURE;), algo que eles pretendem resolver na próxima versão. Assim, o Isolator ++ tem grandes vantagens e desvantagens importantes que limitam sua abrangência. Por ser um produto novo e pago, deve evoluir rapidamente, o que torna seu futuro promissor.

TEST_F(TesteServicoPiada, DeveSalvarUmaPiadaNova)
{
RepositorioPiada *rp = FAKE<RepositorioPiada>();
WHEN_CALLED(rp->Existe( PIADA_TOSCA )).ReturnVal( false );

ServicoPiada servico( rp );
servico.Salvar( PIADA_TOSCA );

ASSERT_WAS_CALLED( rp->Adicionar( PIADA_TOSCA ) );
ISOLATOR_CLEANUP();
}

Acima um teste realizado com o Isolator ++. Repare o uso de FAKE para criação do objeto substituto, WHEN_CALLED para criação da expectativa, ASSERT_WAS_CALLED para verificar a chamada de um método e ISOLATOR_CLEANUP para a destruição dos objetos do Isolator ++.


AMOP


AMOP (Automatic Mock Object for C++) é um projeto de código aberto que conheci há uns dois anos (logo assim que surgiu) e teve poucas atualizações de lá pra cá, o que o fez permanecer com alguns bugs inconvenientes. Ele possui suporte ao Visual Studio, ao GCC e ao  C++ Builder 2009 – sendo o suporte a este último uma contribuição minha ao projeto – e funciona em conjunto com UnitTest++.

TEST_F(TesteServicoPiada, DeveSalvarUmaPiadaNova)
{
MockObject< RepositorioPiada > mock;

mock.call( &RepositorioPiada::Existe )
.expect( PIADA_TOSCA )
.returning( false );

mock.call( &RepositorioPiada::Adicionar )
.expect( PIADA_TOSCA );

ServicoPiada servico( mock );
servico.Salvar( PIADA_TOSCA );

mock.verify();
}

Acima um teste realizado com o AMOP. Repare uso de MockObject parametrizado para a criação do objeto substituto, do método call para criação da expectativa e do método verify para a verificação das expectativas criadas. O método verify não necessita ser chamado, pois é chamado automaticamente na destruição do objeto mock, que ocorre no fim do escopo do teste.


MockitoPP


O MockitoPP é a versão C++ do Mockito, projeto originalmente escrito em Java. De código aberto, é compatível com GCC e Visual Studio e pode ser usado com Google Test, o Hamcrest ou a Boost (regex). Venho usando exporadicamente há cerca de um ano e sinto que a versão atual apresenta poucos problemas (no GCC, era preciso colocar o operador < em algumas classes).

TEST(TesteServicoPiada, DeveSalvarUmaPiadaNova)
{
mock_object< RepositorioPiada > mock;

mock( &RepositorioPiada::Existe )
.when( PIADA_TOSCA )
.thenReturn( false );

mock( &RepositorioPiada::Adicionar )
.when( PIADA_TOSCA )
.thenReturn();

RepositorioPiada &rp = mock.getInstance();

ServicoPiada servico( mock );
servico.Salvar( PIADA_TOSCA );

ASSERT_TRUE( mock.verify( &RepositorioPiada::Existe ).exactly( 1 ) );
ASSERT_TRUE( mock.verify( &RepositorioPiada::Adicionar ).exactly( 1 ) );
}

Acima um teste realizado com MockitoPP. Repare o uso mock_object parametrizado para a criação do objeto substituto, do método when para criação da expectativa e do método verify para a verificação das expectativas criadas. Como pode ser visto, sua sintaxe é bem parecida com a AMOP, porém tendo mais opções de verifição de comportamento (veja a documentação no site).


HippoMocks


O HippoMocks é outro projeto de código aberto, compatível com GCC, Visual Studio e Comeau. O framework de testes usado pelo HippoMocks é o Yaffut, que reune as qualidades dos outros frameworks de teste (veja os detalhes na página do Yaffut). No que visto há cerca de um ano, tem boa estabilidade, sendo ligeiramente melhor que a do MockitoPP.

FUNC( DeveSalvarUmaPiadaNova )
{
MockRepository mocks;
RepositorioPiada *mock = mocks.InterfaceMock< RepositorioPiada >();

mocks.ExpectCall( mock, RepositorioPiada::Existe )
.With( PIADA_TOSCA )
.Return( false );

mocks.ExpectCall( mock, RepositorioPiada::Adicionar )
.With( PIADA_TOSCA );

ServicoPiada servico( mock );
servico.Salvar( PIADA_TOSCA );
}

Acima um teste realizado com HippoMocks. Repare que o objeto substituto é criado através do método InterfaceMock, da classe MockRepository. As espectativas são criadas usando ExpectCall e, ao final, elas são verificadas automaticamente.


Comparativo


O comparativo a seguir é subjetivo, mas mostra uma percepção prática dos frameworks em relação aos critérios julgados relevantes na escolha. A nota varia entre 1 e 4, sendo 1=Ruim, 2=Razoável, 3=Bom e 4=Ótimo.


comparacao


Pelo fato da estabilidade, pessoalmente, dou preferência ao uso de HippoMocks. Apesar disto, gosto de poder usar os matchers do Hamcrest, que já vem “nativos” no MockitoPP.


Ficaram de fora da análise alguns frameworks que ainda não experimentei, mas parecem interessantes: M0cxx0r e MockItNow. Caso você conheça algum outro, por favor deixe seu comentário.

domingo, 7 de novembro de 2010

premake é uma mão na roda

Pra você que desenvolve código multiplataforma ou quer manter seu projeto compatível com diferentes compiladores e IDEs, o premake pode poupar algumas horas de trabalho. Com ele, não é preciso manter arquivos make sincronizados ou ter que ter todos os ambientes configurados no seu computador para poder testá-los.

O Premake é uma ferramenta de configuração de build compatível com as linguagens C, C++ e C# que gera o arquivo de projeto para MS Visual Studio (2002, 2003, 2006, 2008 e Express), GNU Make, Cygwin, MinGW, Apple XCode, CodeBlocks, CodeLite, SharpDevelop e MonoDevelop. Na versão experimental 4.1, permite builds para 32 e 64 bits em Windows, GNU/Linux, Mac OS X, Playstation 3 e Xbox 360.

image

O Premake também possui um ambiente para scripts Lua, linguagem na qual ele foi escrito e interpreta. Por exemplo, o seguinte script poderia ser usado para gerar um projeto C++ para modo console, com as configurações de build para debug e release:


-- Uma solução contém projetos e define as configurações disponíveis para eles
solution "MyApplication"
configurations { "Debug", "Release" }

-- Um projeto configura o build e define os parâmetros para as configurações
project "MyApplication"
kind "ConsoleApp"
language "C++"
files { "**.h", "**.cpp" }

configuration "Debug"
defines { "DEBUG" }
flags { "Symbols" }

configuration "Release"
defines { "NDEBUG" }
flags { "Optimize" }


Veja o User Guide para mais informações.


Outro uso útil é quando temos um projeto opensource interessante que não possui o arquivo de projeto para um determinado IDE. Assim, podemos dar uma olhada em como está o arquivo make do projeto e criar um script Lua para que o Premake crie o arquivo de projeto. Muito mais simples que passar horas criando o arquivo de projeto na mão…