2010-07-22 17 views
18

Do tej pory używałem prowizorycznej procedury testowania jednostkowego - w zasadzie cały ładunek programów testów jednostkowych jest uruchamiany automatycznie przez plik wsadowy. Chociaż wiele z nich wyraźnie sprawdza ich wyniki, o wiele więcej oszustów - zrzucają wyniki do plików tekstowych, które są wersjonowane. Każda zmiana w wynikach testu zostanie oznaczona przez subversion i mogę łatwo określić, jaka była zmiana. Wiele testów wyprowadza pliki kropek lub inną formę, która pozwala uzyskać wizualną reprezentację wyników.Jak dostosować testy jednostek do cmake i ctest?

Problem polega na tym, że przełączam się na używanie cmake. Idąc z przepływem cmake, korzystamy z kompilacji out-of-source, co oznacza, że ​​wygoda zrzucania wyników kończy się we współdzielonym folderze source/build, a ich wersjonowanie wraz ze źródłem nie działa.

Jako zamiennik, chciałbym zrobić to, aby , jak, było powiedzenie narzędziu do testowania urządzenia, gdzie znaleźć pliki oczekiwanych wyników (w drzewie źródłowym) i poprosić o wykonanie porównania. W przypadku niepowodzenia powinien dostarczyć rzeczywiste wyniki i listy różnic.

Czy to możliwe, czy też powinienem przyjąć zupełnie inne podejście?

Oczywiście, mogę może zignorować ctest i po prostu dostosować to, co zawsze robiłem do kompilacji poza źródłem. Mógłbym, na przykład, wyposażyć mój folder-where-all-the-builds-live (z liberalnym użyciem "ignore" oczywiście). Czy to jest przy zdrowych zmysłach? Prawdopodobnie nie, ponieważ każda kompilacja kończy się oddzielną kopią oczekiwanych wyników.

Przedstawiono również porady dotyczące zalecanego sposobu przeprowadzania testów jednostkowych za pomocą cmake/ctest wdzięczności. Zminake zmarnowałem sporo czasu, nie dlatego, że jest źle, ale dlatego, że nie rozumiałem, jak najlepiej z nim pracować.

EDIT

W końcu postanowiłem zachować cmake/ctest bok testów jednostkowych jak najprostsze. Aby przetestować rzeczywisty przeciwko oczekiwanych rezultatów, znalazłem dom dla następujących funkcji w mojej bibliotece ...

bool Check_Results (std::ostream    &p_Stream , 
        const char    *p_Title , 
        const char    **p_Expected, 
        const std::ostringstream &p_Actual ) 
{ 
    std::ostringstream l_Expected_Stream; 

    while (*p_Expected != 0) 
    { 
    l_Expected_Stream << (*p_Expected) << std::endl; 
    p_Expected++; 
    } 

    std::string l_Expected (l_Expected_Stream.str()); 
    std::string l_Actual (p_Actual.str()); 

    bool l_Pass = (l_Actual == l_Expected); 

    p_Stream << "Test: " << p_Title << " : "; 

    if (l_Pass) 
    { 
    p_Stream << "Pass" << std::endl; 
    } 
    else 
    { 
    p_Stream << "*** FAIL ***" << std::endl; 
    p_Stream << "===============================================================================" << std::endl; 
    p_Stream << "Expected Results For: " << p_Title << std::endl; 
    p_Stream << "-------------------------------------------------------------------------------" << std::endl; 
    p_Stream << l_Expected; 
    p_Stream << "===============================================================================" << std::endl; 
    p_Stream << "Actual Results For: " << p_Title << std::endl; 
    p_Stream << "-------------------------------------------------------------------------------" << std::endl; 
    p_Stream << l_Actual; 
    p_Stream << "===============================================================================" << std::endl; 
    } 

    return l_Pass; 
} 

Typowy test jednostka teraz wygląda mniej więcej tak ...

bool Test0001() 
{ 
    std::ostringstream l_Actual; 

    const char* l_Expected [] = 
    { 
    "Some", 
    "Expected", 
    "Results", 
    0 
    }; 

    l_Actual << "Some" << std::endl 
      << "Actual" << std::endl 
      << "Results" << std::endl; 

    return Check_Results (std::cout, "0001 - not a sane test", l_Expected, l_Actual); 
} 

Gdzie muszę wielokrotnego użytku funkcja przesypywania danych, przyjmuje parametr typu std::ostream&, dzięki czemu może zrzucić do strumienia rzeczywistych wyników.

+0

Powinieneś dodać swoją edycję jako odpowiedź, ponieważ odpowiada ona na twoje własne pytanie – Joakim

Odpowiedz

16

Używałbym samodzielnego trybu skryptowego CMake do uruchamiania testów i porównywania wyników. Zwykle dla programu testów jednostkowych napiszesz add_test(testname testexecutable), ale możesz uruchomić dowolne polecenie jako test.

Jeśli napiszesz skrypt "runtest.cmake" i wykonasz program testowy urządzenia poprzez to, skrypt runtest.cmake może zrobić wszystko, co mu się podoba - włączając w to narzędzie cmake -E compare_files. Chcesz coś jak poniżej w pliku CMakeLists.txt:

enable_testing() 
add_executable(testprog main.c) 
add_test(NAME runtestprog 
    COMMAND ${CMAKE_COMMAND} 
    -DTEST_PROG=$<TARGET_FILE:testprog> 
    -DSOURCEDIR=${CMAKE_CURRENT_SOURCE_DIR} 
    -P ${CMAKE_CURRENT_SOURCE_DIR}/runtest.cmake) 

To działa skrypt (cmake -P runtest.cmake) i definiuje 2 zmienne: TEST_PROG, zestaw do ścieżki pliku wykonywalnego testową, a SourceDir , ustaw na bieżący katalog źródłowy. Najpierw musisz wiedzieć, który program uruchomić, a drugi, gdzie znaleźć oczekiwane pliki wyników testu.Zawartość runtest.cmake byłoby:

execute_process(COMMAND ${TEST_PROG} 
       RESULT_VARIABLE HAD_ERROR) 
if(HAD_ERROR) 
    message(FATAL_ERROR "Test failed") 
endif() 

execute_process(COMMAND ${CMAKE_COMMAND} -E compare_files 
    output.txt ${SOURCEDIR}/expected.txt 
    RESULT_VARIABLE DIFFERENT) 
if(DIFFERENT) 
    message(FATAL_ERROR "Test failed - files differ") 
endif() 

Pierwszy execute_process uruchamia program testowy, który będzie napisać „Output.txt”. Jeśli to zadziała, to następny execute_process skutecznie działa cmake -E compare_files output.txt expected.txt. Plik "expected.txt" jest znanym dobrym wynikiem w twoim drzewie źródłowym. Jeśli występują różnice, powoduje to błąd, dzięki czemu można zobaczyć nieudany test.

To, czego nie można zrobić, to wydrukować różnice; CMake nie ma ukrytej w nim pełnej implementacji "diff". W tej chwili korzystasz z Subversion, aby zobaczyć co linie uległy zmianie, więc oczywistym rozwiązaniem jest zmienić ostatni człon do:

if(DIFFERENT) 
    configure_file(output.txt ${SOURCEDIR}/expected.txt COPYONLY) 
    execute_process(COMMAND svn diff ${SOURCEDIR}/expected.txt) 
    message(FATAL_ERROR "Test failed - files differ") 
endif() 

ta nadpisuje drzewa źródłowego z wyjściem budować na niewydolność następnie uruchamia svn diff na nim. Problem polega na tym, że nie powinieneś tak naprawdę zmieniać drzewa źródłowego w ten sposób. Po uruchomieniu testu po raz drugi, przechodzi! Lepszym sposobem jest zainstalowanie narzędzia wizualnego diff i uruchomienie go na wyjściowym i oczekiwanym pliku.

+0

Brzmi całkiem blisko, a na pewno informacyjny - dziękuję. BTW - problem "uruchom test jeszcze raz i to mija" tak naprawdę nie istnieje. Jeśli test się nie powiedzie, nie jestem na tyle głupi, by popełnić błędne wyniki, jako nowe oczekiwane rezultaty. Różnica, której chcę, to między wersją roboczą a wersją nagłówka. Mimo to ma sens, aby zachować faktyczne wyniki w drzewie kompilacji i zrobić różnicę przy użyciu innego programu, więc kopia robocza nie jest zmieniana. "Nie jestem na tyle głupi, aby popełnić ten błąd" wydaje się trochę kuszącym losem. – Steve314

+0

To, co robiłem również w przeszłości (choć było to więcej pracy za niewiele więcej korzyści), polegało na tym, że program testowy generował wyniki, a także sprawdzał je pod względem znanych dobrych wyników. Użyłem 'config_file (expected.dat.in expected.dat COPYONLY)' aby uzyskać oczekiwany wynik skopiowany do drzewa kompilacji. W ten sposób test może powiedzieć "eh, masz problem zaczynający się od 163. wpisu!", Zamiast później uruchamiać różnicę. – richq

+0

Pracuję nad [CMake framework] (http://jaws.rootdirectory.de) dla "kickstarting" projektów C++. Jest to licencjonowane jako [CC0] (http://creativecommons.org/publicdomain/zero/1.0/), a licencja StackOverflow to CC by-sa. Czy byłoby w porządku z tobą, gdybym zaadaptował twoje rozwiązanie w warunkach Stardobie, jednocześnie dając pełne kredyty? – DevSolar

Powiązane problemy