A03 - Criando e executando testes com o CTest
Questão
Como podemos lidar com o estágio de testes do nosso projeto com CMake?
Objetivos
Aprenda a produzir executáveis de teste com CMake.
Aprenda a executar seus testes através do CTest.
O teste é uma atividade essencial no ciclo de desenvolvimento. Um conjunto de testes bem projetado irá ajudá-lo a detectar bugs e também pode facilitar a integração de novos desenvolvedores.
Nesta atividade, vamos analisar como usar o CTest para definir e executar nossos testes.
Adicionando testes ao seu projeto
No CMake e CTest, um teste é qualquer comando retornando um código de saída. Não importa como o comando é emitido ou executado: pode ser um executável C++ ou um script Python. Contanto que a execução retorne um código de saída zero ou não-zero, o CMake poderá classificar se o teste foi bem sucedido ou se falhou, respectivamente.
Existem duas etapas para integrar seu sistema de compilação CMake com a ferramenta CTest:
Chamar o comando
enable_testing
a qual não requer argumentos.Adicionar testes com o comando
add_test
.
add_test(NAME <name> COMMAND <command> [<arg>...]
[CONFIGURATIONS <config>...]
[WORKING_DIRECTORY <dir>]
[COMMAND_EXPAND_LISTS])
Este comando aceita apenas os argumentos nomeados, NAME
e COMMAND
obrigatoriamente. O primeiro especifica o nome de identificação do teste, enquanto o
o último configura o comando de execução.
Seu primeiro projeto de teste
Vamos construir uma simples biblioteca para somar inteiros e um executável usando esta biblioteca. Use o projeto base localizado na pasta
source/code/day-1/05_hello-ctest
.cmake_minimum_required(VERSION 3.18) project(hello-ctest LANGUAGES CXX) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_library(sum_integers sum_integers.cpp) add_executable(sum_up main.cpp) target_link_libraries(sum_up PRIVATE sum_integers)Juntamente com a biblioteca e o executável principal, também produziremos um executável para testar a biblioteca
sum_integers
.add_executable(cpp_test test.cpp) target_link_libraries(cpp_test PRIVATE sum_integers)Agora é hora de configurar o CTest:
enable_testing()e declarar nosso teste, especificando qual comando para executar:
add_test( NAME cpp_test COMMAND $<TARGET_FILE:cpp_test> )Observe o uso de gerador de expressão (gen-exp) para evitar especificar o caminho completo para o executável
cpp_test
.Podemos agora compilar e executar nosso teste:
$ cmake -S. -Bbuild $ cd build $ cmake --build . $ ctest
Uma solução está na subpasta solution
.
Exercício 6: Executando os testes através de um script shell
Qualquer comando pode ser usado para executar testes. Neste exercício, vamos estender o código cmake anterior para testar o executável principal dentro de um script de shell. Use o projeto base localizado na pasta
source/code/day-1/06_bash-ctest
.
Encontre o executável bash apropriado para executar
test.sh
. Você deve usar o comandofind_program
do CMake.find_program(BASH_EXECUTABLE NAMES bash REQUIRED)A variável
BASH_EXECUTABLE
será o programa shell.Adicione outra invocação de
add_test
que será equivalente a executar:$ ./test.sh sum_upSugestões:
Use a localização absoluta de
test.sh
.Você pode usar a sintaxe gerador de expressão para dar a localização do executável:
$<TARGET_FILE:sum_up>
.Construa o projeto e execute o CTest.
Uma solução está na subpasta solution
.
Exercício 7: Executando os testes através de um script Python
É muito mais comum hoje em dia usar python, em vez de scripts de shell.
Neste exercício, adicionaremos mais dois testes ao nosso projeto.
Esses novos testes executarão o programa principal por meio de um script Python.
Use o projeto base localizado na pasta source/code/day-1/07_python-ctest
.
#. Encontre o intérprete Python para executar test.py
. Você deve usar
o comando find_package
do CMake:
find_package(Python REQUIRED)O intérprete estará disponível na variável
Python_EXECUTABLE
.
Adicione outra invocação de
add_test
que será equivalente a executar:$ python test.py --executable sum_up
Sugestões:
Use a localização absoluta de
test.py
.Você pode usar a sintaxe gerador de expressão para dar a localização do executável:
$<TARGET_FILE:sum_up>
.
O script
test.py
aceita o comando por linha de argumento--short
. Adicione outro teste que use essa opção no comando.Construa o projeto e corra o CTest.
Uma solução está na subpasta solution
.
A interface de linha de comando do CTest
Como usar o CTest efetivamente.
Agora, demonstraremos a interface de linha de comando da CTest (CLI) usando a solução do exercício anterior.
O comando ctest
faz parte da instalação do CMake. Podemos encontrar ajuda em seu uso com:
$ ctest --help
Lembre-se, para executar seus testes através do CTEST, primeiro você precisará estar na pasta de compilação:
$ cd build
$ ctest
Isso executará todos os testes em seu suíte de teste. Você pode listar os nomes dos testes no conjunto de testes com:
$ ctest -N
As opções de verbosidade também são muito úteis, especialmente quando a depuração de falhas.
Com --output-on-failure
, o CTest imprimirá a saída de testes com falha.
Se você gostaria de exibir a invocação completa para cada teste, use
tha opção --verbose
.
Pode-se selecionar subconjuntos de testes para executar:
Por nome, com a flag
-R <regex>
. Qualquer teste cujo nome pode ser capturado pelo regex será executado. A opção-RE <regex>
exclui testes por nome usando um regex.Por label, com a flag
-R <regex>
. Qualquer teste cujo label pode ser capturado pelo regex será executado. A opção-RE <regex>
exclui testes por label usando um regex.Por numero, com a flag
-I [Start,End,Stride,test#,test#|Test file]
. Esta geralmente não é a opção mais conveniente para selecionar subconjuntos de testes.
É possível executar testes com falha com:
$ ctest --rerun-failed
Finalmente, você pode paralelizar a execução do teste:
$ ctest -j N
$ ctest --parallel N
Cuidado! A ordem de execução de testes não é garantida: se alguns testes são interdependentes, você terá que declarar explicitamente em sua construção.
Propriedades do teste: rótulos, tempo limite e custo
Quando você usa add_test
, Você dá um nome único para cada teste. Como nós vimos,
Você pode usar esses nomes para filtrar quais testes serão executados na suíte.
Isso pode ser extremamente valioso quando a suíte de teste é muito grande e você precisará verificar
apenas um subconjuntos dos testes.
No entanto, o mecanismo de nomenclatura não permite que os testes sejam agrupados facilmente.
Poderíamos, em princípio, adicionar um sufixo a todos os testes em um determinado grupo e depois filtrá-los com
um regex apropriado, mas e se tivéssemos vários grupos para os quais os testes pudessem
pertencer. Esta é uma situação muito comum na prática!
Felizmente, podemos definir propriedades noson testes e os rótulos (labels) estão entre os
propriedades disponíveis.
set_tests_properties(test1 [test2...] PROPERTIES prop1 value1 prop2 value2)
Exercício 8: Definir etiquetas em testes
Vamos executar alguns testes usando Python e queremos agrupá-los em duas categorias:
quick
para testes com um tempo de execução muito curto.long
para testes de benchmarking com um tempo de execução mais longo.
Use o projeto base localizado na pasta source/code/day-1/08_ctest-labels
.
Encontre o interpretador Python:
find_package(Python REQUIRED)
O interpretador estará disponível na variável
Python_EXECUTABLE
.Enable testing.
Adicione os seis testes da pasta
test
. Dê a cada um deles um nome único.Use
set_tests_properties
para definir rótulos para os testes:feature-a.py
,feature-b.py
, efeature-c.py
devem ser do grupoquick
.feature-d.py
,benchmark-a.py
, ebenchmark-b.py
devem ser do grupolong
.
Verifique se tudo funciona como esperado
Uma solução está na subpasta solution
.
Entre as muitas propriedades que podem ser definidas em testes, gostaríamos de destacar as seguintes:
WILL_FAIL
. O CTest marcará os testes comoOK
quando o comando correspondente retorna com um código de saída diferente de zero. Use esta propriedade para testar falhas esperadas.COST
. Na primeira vez que você executa seus testes, o CTest arazenará o tempo de execução de cada um.Desta forma, as execuções subseqüentes do conjunto de testes começarão a partir dos testes com maiores tempo de execução.
TIMEOUT
. Alguns testes podem ser executados por um longo período: você pode definir um tempo limite explícito se quiser ser mais ou menos tolerante das variações de tempo na execução
Exercícios 9, 10, 11: Mais propriedades!
Vamos brincar com as propriedades que acabamos de introduzir.
Use o projeto base localizado na pasta
source/code/day-1/09_ctest-will-fail
.
Crie um projeto sem uma linguagem.
Encontre o interpretador Python.
Habilite testing.
Adicione script de teste
test.py
.Tente executar os testes e observe o que acontece. Agora defina a propriedade
WILL_FAIL
para verdade e observe o que muda ao executar os testes.Uma solução está na subpasta
solution
.Use o projeto base localizado na pasta
source/code/day-1/10_ctest-cost
.
Ativar testes no
CMakeLists.txt
.Adicionar testes em execução a cada um dos scripts na pasta
test
.Execute os testes em paralelo e observe quanto tempo sua execução leva.
Re-execute os testes e observe como o CTest ordena sua execução.
Agora defina a propriedade
COST
. O que mudou quando re-executou os testes.Uma solução está na subpasta
solution
.Use o projeto base localizado na pasta
source/code/day-1/11_ctest-timeout
.
Crie um projeto sem uma linguagem.
Encontre o interpretador Python.
Habilite testing.
Adicione script de teste
test.py
.Tente executar os testes e observar quanto tempo o teste leva para executar. Agora defina o.
TIMEOUT
para um valor menor do que você observou e re-execute os testes.Uma solução está na subpasta
solution
.
Para obter uma lista completa de propriedades que podem ser definidas faça:
$ cmake --help-properties
ou visite a documentação cmake online.
Keypoints
Qualquer comando pode ser definido como um teste no cmake.
Os testes podem ser executados através do CTest.