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.
O CTest faz parte do conjunto de programas CMake. É um executor de testes. Você pode definir um conjunto de testes, executá-los e gerar relatórios através dele.
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_testinga 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_programdo CMake.find_program(BASH_EXECUTABLE NAMES bash REQUIRED)A variável
BASH_EXECUTABLEserá o programa shell.Adicione outra invocação de
add_testque 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_testque 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.pyaceita 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:
quickpara testes com um tempo de execução muito curto.longpara 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_propertiespara definir rótulos para os testes:feature-a.py,feature-b.py, efeature-c.pydevem ser do grupoquick.feature-d.py,benchmark-a.py, ebenchmark-b.pydevem 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 comoOKquando 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_FAILpara 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.
TIMEOUTpara 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.