Pokazywanie postów oznaczonych etykietą Unit testing. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą Unit testing. Pokaż wszystkie posty

31 sierpnia 2014

[python] mock_open - testowanie uchwytu do pliku

Coś czego brakowało mi podczas pisania skryptów, czyli metody na zmockowanie bibliotecznej funkcji open (do otwierania plików). Framework do mocków posiada specjalną metodę mock.mock_open(), jednak wszystkie przykłady jakie znalazłem na sieci pokazywały działanie tego mechanizmu w interpreterze, co jakoś nie współgrało z moimi TestSuite-ami. Wydaje mi się, że da się to rozwiązać w bardziej elegancji sposób, ale póki co jest to coś co działa.

Przykład: zapisywanie i odczytywanie z pliku:
#! /usr/bin/env python3
import os

def fileWrite(fileName):
    f = open(fileName, 'w+')
    f.write('text\n')
    f.close()

def fileRead(fileName):
    f = open(fileName, 'r')
    count = 0
    for _ in f:
        count += 1
    f.close()
    return count
Po pierwsze nie udało mi się skorzystać z patch za pomocą dekoratora, a jedynie przez with. Także wywołanie jest troszkę inne, jako drugi parametr, przekazywany jest mock, stworzony przez mock.mock_open() (zamiast domyślnie wygenerowanego z MagicMock). Parametr create odpowiada za wygenerowanie atrybutów, jego brak sprawia, że zgłaszany jest brak atrybutu asdf.open() przez test. Tego jeszcze nie ogarniam.
W test_filterWrite() zawołanie mocka m, wygeneruje handler - czyli zadziała jak open(), które zwiększy licznik wywołań, dlatego można z tego skorzystać, dopiero po sprawdzeniu asercji (linijka 14).

Jeżeli potrzebujemy aby z uchwytu można było pobrać dane (przykład test_fileRead), istnieje alternatywne rozwiązanie z przesłonięciem __enter__ oraz __iter__. Nie zwiększa to licznika open i assert w linijce 27, będzie działał poprawnie.
import unittest
from unittest import mock
from unittest.mock import mock_open
import asdf

class TestFindFile(unittest.TestCase):
    def test_fileWrite(self):
        m = mock_open()
        with mock.patch('asdf.open', m, create=True):
            fileName = 'aaa.txt'
            asdf.fileWrite(fileName)

            m.assert_called_once_with(fileName, 'w+')
            file_handler = m()
            file_handler.write.assert_called_once_with('text\n')
            file_handler.close.assert_called_once_with()

    def test_fileRead(self):
        m = mock_open()
        with mock.patch('asdf.open', m, create=True):
            fileName = 'aaa.txt'
            file_handler = m.return_value.__enter__.return_value
            file_handler.__iter__.return_value = ['line1', 'line2']

            self.assertEqual(2, asdf.fileRead(fileName))

            m.assert_called_once_with(fileName, 'r')
            file_handler.close.assert_called_once_with()

27 sierpnia 2014

[python] mockowanie iterowanych obiektów pętli

Problem, który wciąż do mnie powracał. Jak zmockować funkcję (generator), wołaną z pętli for. Ponieważ taka funkcje może zwrócić kilka wyników, cały czas próbowałem zaprząc do działania "side_effect" - bezskutecznie. Myślałem, że jej działanie jest podobne do generatora, cóż znów się czegoś nauczyłem. Pętla wymaga aby na obiekcie (z którego pobierane są dane) dało się iterować, dlatego "return_value" zwracające np. listę rozwiązuje problem. "side_effect" nadaje się bardziej do mockowania zachowań wewnątrz pętli.

Przykład z os.walk(), która zwraca trzy elementową krotkę:
import os

def findFile(fileName):
    for root, dirs, files in os.walk('~/'):
        if fileName in files:
            return os.path.join(root, fileName)

    return None
Test:
import unittest
import mock
from mock import call
import asdf

class TestFindFile(unittest.TestCase):
    @mock.patch('asdf.os')
    def test_findFIle(self, mockOs):
        fileName = 'aaa.txt'

        mockOs.walk.return_value = [('/home/user',        ('folder',),      ('bbb.txt',)),
                                    ('/home/user/folder', ('fol1', 'fol2'), (fileName,))]
        mockOs.path.join.return_value = '/home/user/folder' + fileName

        self.assertEqual('/home/user/folder' + fileName, asdf.findFile(fileName))

        mockOs.walk.assert_called_once_with('~/')
        mockOs.path.join.assert_called_once_with('/home/user/folder', fileName)

29 czerwca 2014

[python] unittest/mocks - część II

Garść informacji na temat unittest/mock, które sobie ostatnie przyswoiłem, m.in. czytają ciekawy artykuł:

Dopasowanie czegokolwiek - mock.ANY

Odpowiednik testing::_ w GoogleMock, czyli akceptowanie każdego argumentu z jakim zostanie wywołany mock, co pozwala na rozluźnienie zależności (coupling).
class Gearbox:
    def moveGear(self, gearLevel):
        print('Gear to level: ' + str(gearLevel))
Test
class TestVehicle(unittest.TestCase):

    def test_tractorStartOnRandomGear(self):
        mock_gearbox = mock.create_autospec(Gearbox)
        tractor = Vehicle(mock_gearbox)
        tractor.runEngine()
        mock_gearbox.moveGear.assert_called_once_with(mock.ANY)

Mock vs. MagicMock vs. create_autospec

Z tego co udało mi się zrozumieć MagicMock-i, różnią się od zwykłych Mock-ów tym, że posiadają predefiniowane wartości dla pewnych pospolitych metod np.: __len__ == 1.
Ponieważ mock.Mock i mock.MagicMock akceptują wołania z dowolną ilością argumentów, najlepiej korzystać mock.create_autospec. Taki mock w przypadku gdy ilość argumentów się nie zgadza rzuci wyjątek.

Dekorator @mock.patch zamiast dependency injection

Dekoratory z biblioteki mock, pozwalają na zgrabne przesłanianie klas i modułów w testowanym kodzie, dzięki czemu korzystamy z dobrodziejstw dependency injection, bez niepotrzebnego rozbudowywania "konstruktorów".

Kolejność dekoratorów jest istotna

Każdy moduł importuje inne moduły do swojej lokalnej przestrzeni, dlatego należy pamiętać, by nie mock-ować modułów (np. os) w pliku z testami (bo tylko tutaj będzie zmock-owany). Należy explicit wskazać na moduł gdzie jest nasz testowany kod (np. mycode.os).

Kiedy korzysta się z wielu dekoratorów należy pamiętać o ich kolejności. Mapowanie dekoratorów do parametrów następuje w odwrotnej kolejności niż deklaracje. Przykład poniżej.
import os
import sys

class FilesSystem:
    def countFolders(self):
        l = [dir for dir in os.listdir('.') if os.path.isdir(dir)]
        sys.exit(len(l))
Test
import unittest
import mock
import asdf

class TestFileSystem(unittest.TestCase):

    @mock.patch('asdf.os')
    @mock.patch('asdf.sys')
    def test_countFolders(self, mock_sys, mock_os):
        mock_os.listdir.return_value = ['folder1', 'folder2']
        mock_os.path.isdir.return_value = True

        fs = FilesSystem()
        fs.countFolders()

        mock_os.listdir.assert_called_once_with('.')
        expected = [call.mock_os.path.attribute.isdir('folder1'),
                    call.mock_os.path.attribute.isdir('folder2')]
        self.assertTrue(mock_os.path.isdir.mock_calls == expected)
        mock_sys.exit.assert_called_once_with(2)
Innym rodzajem dekoratora jest @mock.patch.object, który pozwala na z-mockowanie wybranej metody z klasy, gdy cała reszta nadal będzie wołała oryginalną implementację.
class Accounts:
    def isUser(self, user):
        print('isUser ' + user)

    def assign(self, user, movie):
        print('assign ' + user + ' ' + movie)
Test
class TestVideoLib(unittest.TestCase):

    @mock.patch.object(Accounts, 'isUser')
    def test_whenUserExist_thenHeCouldRentAMovie(self, mock_isUser):
        mock_isUser.return_value = True
        accounts = Accounts()
        videoLib = VideoLib(accounts)
        videoLib.rent('Jhon', 'Titanic')

        mock_isUser.assert_called_once_with('Jhon')

Otwarte kwestie: Odpowiednik StrictMock, mock-owanie metody open, list comprehension - które niekiedy sprawa mi problemy, gdy chce jest otestować.

18 stycznia 2014

[python] doctest

doctest jest modułem dodanym do standardowej biblioteki Python-a, umożliwiający testowanie wyjścia z interpretera. Testy ujęte są w docstring, wejście (action) rozpoznawane jest po >>>, tak jak to wygląda w interpreterze, następna linia jest oczekiwanym wynikiem. Domyślnie, dostest informuje jedynie o porażkach (można to jednak zmodyfikować).
import doctest

def fun():
    '''
    # Testing sorted()
    >>> sorted([1, 3, 2])
    [1, 2, 3]

    # Testing max()
    >>> max(1, 2)
    1
    '''
    pass

doctest.testmod()
Wynik:
Failed example:
    max(1, 2)
Expected:
    1
Got:
    2
**********************************************************************
1 items had failures:
   1 of   2 in __main__.fun
***Test Failed*** 1 failures.

23 listopada 2013

python(3) - mock-i w testach jednostkowych

Python - czasami dziwie się jak coś z tak chujową dokumentacją może istnieć, no ale ... gdyby problemy nie istniały nie było by czego rozwiązywać. Swoje eksperymenty oparłem na tym linku:
Bawiłem się jedynie MagicMock-iem, chociaż w bibliotece istnieje coś takiego jak Mock. Poniżej trzy, testy do trzech metod, których przetestowanie sprawiło mi problemy. Na początku ważna kwestia, MagicMock jest klasą, więc tworząc obiekt mock-a, należy pamiętać o nawiasach, aby wywołać konstruktor! Za pomocą parametru spec, wskazujemy też klasę, z które zaczerpnięty zostanie interfejs mock-owanej klasy.

Pierwszy z testów (test_show), miał wykazać sekwencyjne zawołanie metody Deciratir.rich(). Robi się to za pomocą dziwnego obiektu call, którego nasyca się atrybutami z jakimi zostanie wywołana mock-owana metoda. Inna kwestia to wartość zwracana. Za pomocą pola return_value, można ustalić jaka wartość będzie zawsze zwracana z mocka.

Drugi test, pokazuje w działaniu jedną z metod (asercji), dostarczoną przez bibliotekę. Tutaj oczekujemy, że metoda zostanie zawołana tylko raz z określonym parametrem (assert_called_once_with).

Trzeci test, również bada, czy mock-i zostały zawołane z określonymi parametrami, ale tym razem, każdy z nich zwraca inną wartość, aby przetestować bardzo specyficzną ścieżkę wykonania programu. Służy temu pole side_effect, z którego pobierane są kolejne zwracane wartości (można też podobna włożyć tam metody, które zostaną zawołane).
#!/usr/bin/env python3
# python -m unittest discover --pattern=test_unittest.py

import unittest
from unittest.mock import MagicMock
from unittest.mock import call

class TestPrinter(unittest.TestCase):
    def setUp(self):
        self.decorMock = MagicMock(spec=Decorator, name='decorator')()

    def test_show(self):
        self.decorMock.rich.return_value = True

        sut = Printer(self.decorMock)
        sut.show('asdf')
        expected = [call.decorMock.attribute.rich('asdf'),
                    call.decorMock.attribute.rich('xxx')]
        self.assertTrue(self.decorMock.rich.mock_calls == expected)

    def test_show_once(self):
        sut = Printer(self.decorMock)
        sut.show_once()
        self.decorMock.rich.assert_called_once_with('once')

    def test_show_comlicated(self):
        self.decorMock.rich.side_effect = [False, True]

        sut = Printer(self.decorMock)
        self.assertEqual(sut.show_complicated(), 2)
        expected = [call.decorMock.attribute.rich('a'),
                    call.decorMock.attribute.rich('b')]
        self.assertTrue(self.decorMock.rich.mock_calls == expected)

class Decorator():
    def rich(self, descr):
        raise Exception()

class Printer():
    def __init__(self, decorator):
        self._decorator = decorator

    def show(self, descr):
        self._decorator.rich(descr)
        if self._decorator.rich('xxx'):
            return True
        return False

    def show_once(self):
        self._decorator.rich('once')

    def show_complicated(self):
        if self._decorator.rich('a'):
            return 1
        if self._decorator.rich('b'):
            return 2
        return 0
Z otwartych kwestii, wciąż nie wiem jak testować dowolne parametry przekazywane do mock-a. Testowanie biblioteki do testów trwa nadal.

22 lipca 2013

Trochę o Unit Testach

Zbiorczy zestaw linków, traktujących o testach jednostkowych i trochę moich notatek.

http://www.youtube.com/watch?v=RlfLCWKxHJ0
    Zastosowanie dependeny injection oraz prawo Demeter w testowaniu.

http://www.youtube.com/watch?v=wEhu57pih5w
    Testy jednostkowy pozwalają nam wykryć błędy w działaniu małych komponentów. Dają przewagę nad testami systemowymi, pozwalając szybko zorientować się gdzie leży przyczyna błędu. Całościowe test, też mogą to wykazać, ale znacznie trudniej jest ustalić, gdzie leży usterka, albo co jest jej przyczyną.
    Mieszanie mechanizmu tworzenie obiektów z business logic sprawia, że bardzo trudno napisać dobry test. Nie można tworzyć obiektów w izolacji. Jedyne co można tworzyć to cały łańcuch zależnych od siebie obiektów. Nie mamy wtedy już do czynienia z testami jednostkowymi (np. test silnika), tylko z testami systemowymi (test działania całego samochodu).

http://blog.stevensanderson.com/2009/08/24/writing-great-unit-tests-best-and-worst-practises/
    Tutaj autor, przestawia TDD jako proces projektowania (i nie jest procesem testowania). TDD i testy jednostkowe pomagają dostarczyć komponenty oprogramowania, które indywidualnie zachowują się zgodnie z założeniami projektowymi. Jedynym odstępstwem, w którym testy jednostkowe naprawdę potrafią wykryć błędy jest proces refactoringu. Trochę inna wizja niż ta prezentowana na Google Talks.

Ciekawy pattern do nazywania testów np. ProductPurchaseAction_IfStockIsZero_RendersOutOfStockView()
  • Subject
  • Scenario
  • Result

http://www.exubero.com/junit/antipatterns.html
    Mowa o antywzorcach, jakie najczęściej pojawiają się podczas tworzenia testów jednostkowych. "Unit testing is the first of a long series of steps to verify that the code works correctly."

24 marca 2013

python -m (library module as a script)

Python posiada przydatną opcję, z którą można wykonać swoje programy (-m), czyli wykonanie modułu jako skryptu:
-m mod : run library module as a script (terminates option list)
Do nauki posłuży prosty program obliczający ciąg Fibonacciego, ale filtrujący po liczbach nieparzystych. Dodatkowo stworzyłem testy dla dwóch funkcji.
import unittest

def add(f1, f2):
    return f1 + f2

def swap(f1, f2):
    f3 = add(f1, f2)
    return f2, f3

def print_odd(num):
    print 'Odd fibo ', num

def fibo(f1 = 0, f2 = 1):
    [print_odd(num) for num in [f1, f2] if num % 2]
    for _ in xrange(20):
        f1, f2 = swap(f1, f2)
        if f2 % 2:
            print_odd(f2)

class TestSequenceFunctions(unittest.TestCase):
    def setUp(self):
        pass

    def test_add(self):
        self.assertEqual(add(2, 1), 3)

    def test_swap(self):
        self.assertEqual(swap(1, 2), (2, 3))

fibo()
Jednym z takich, przydatnych wbudowanych modułów jest profiler (przydatna instrukcja). Jak widać punkcja print_odd(), została zwołana tylko 14 razy, bo tyle jest nieparzystych liczb, wśród pierwszych 20-tu elementów ciągu.
$ python -m cProfile asdf.py

         57 function calls in 0.007 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.007    0.007 asdf.py:1(<module>)
       20    0.000    0.000    0.000    0.000 asdf.py:1(add)
        1    0.000    0.000    0.007    0.007 asdf.py:11(fibo)
       20    0.000    0.000    0.000    0.000 asdf.py:4(swap)
       14    0.007    0.001    0.007    0.001 asdf.py:8(print_odd)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
Drugi przydatny moduł, o którym się dowiedziałem służy do wykonywania testów jednostkowych.
$ python -m unittest discover --pattern=asdf.py

..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

29 października 2012

StrictMock i referencja w konstruktorze [TRICK]

Napotkałem ostatnio taki problem (mowa tu o testach charakteryzacyjnych).

Przykład: Chcemy przetestować metodę Filter::run(). Klasa do działania potrzebuje obiektów Inc i Show. Nie mają one czysto abstrakcyjnych interfejsów (ale zrobimy dla nich mocki). Show potrzebuje do działania referencji do Inc.

Problem pojawia się, gdy mock zrobimy StrictMock-iem i będziemy chcieli go jeszcze zainicjować. Tutaj ShowMock chcemy zainicjować IncMock (linijka 49-50).
#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/ref.hpp>
#include <gmock/gmock.h>

using namespace std;
using namespace ::testing;

class Inc {
public:
    virtual int inc(int v) { return v++; }
};

class IncMock : public Inc {
public:
    MOCK_METHOD1(inc, int(int));
};

class Show {
    Inc& obj;
public:
    Show(Inc& i) : obj(i) {}
    virtual void show(int v) { cout << "Val: " << obj.inc(v) << endl; }
};

class ShowMock : public Show {
public:
    ShowMock(Inc& i) : Show(i) {}
    MOCK_METHOD1(show, void(int));
};

class Filter {
    boost::shared_ptr<Show> sw;
    boost::shared_ptr<Inc> in;
public:
    Filter(boost::shared_ptr<Show> s, 
           boost::shared_ptr<Inc> i) : sw(s), in(i) {}
    void run(int val) {
        if (val > 5)
            sw->show(val);
        in->inc(val);
    }
};

TEST(FilterTest, test) {
    boost::shared_ptr<IncMock> l_incMock(new StrictMock<IncMock>());

//  NOT WORKING
//  boost::shared_ptr<ShowMock> l_showMock(new StrictMock<ShowMock>
//                                           (*l_incMock));
//  NOT WORKING
//  boost::shared_ptr<ShowMock> l_showMock(new StrictMock<ShowMock>
//                                           (boost::cref(*l_incMock)));

    boost::shared_ptr<ShowMock> l_showMock(new StrictMock<ShowMock>
                                             (boost::ref(*l_incMock)));
    Filter l_filter(l_showMock, l_incMock);

    int l_val = 8;
    EXPECT_CALL(*l_incMock, inc(l_val)).WillOnce(Return(l_val + 1));
    EXPECT_CALL(*l_showMock, show(l_val));

    l_filter.run(l_val);
}

int main(int argc, char *argv[]) {
    ::testing::InitGoogleMock(&argc, argv);
    return RUN_ALL_TESTS();
}
Pierwsze rozwiązanie wypluje error przy kompilacji
In file included from /home/niegodziwy/Downloads/gmock-1.6.0/include/gmock/gmock.h:64:0,
                 from main.cpp:4:
/home/niegodziwy/Downloads/gmock-1.6.0/include/gmock/gmock-generated-nice-strict.h: In constructor ‘testing::StrictMock<M>::StrictMock(const A1&) [with A1 = IncMock, MockClass = ShowMock]’:
main.cpp:48:77:   instantiated from here
/home/niegodziwy/Downloads/gmock-1.6.0/include/gmock/gmock-generated-nice-strict.h:174:51: error: no matching function for call to ‘ShowMock::ShowMock(const IncMock&)’
/home/niegodziwy/Downloads/gmock-1.6.0/include/gmock/gmock-generated-nice-strict.h:174:51: note: candidates are:
main.cpp:28:5: note: ShowMock::ShowMock(Inc&)
main.cpp:28:5: note:   no known conversion for argument 1 from ‘const IncMock’ to ‘Inc&’
main.cpp:26:7: note: ShowMock::ShowMock(const ShowMock&)
main.cpp:26:7: note:   no known conversion for argument 1 from ‘const IncMock’ to ‘const ShowMock&’
Co się okazuje, StrictMock ma tylko konstruktory explicit, dlatego podziała trick z boost::ref().
template <typename A1>
explicit StrictMock(const A1& a1) : MockClass(a1) {
  ::testing::Mock::FailUninterestingCalls(
      internal::ImplicitCast_<MockClass*>(this));
}
Niestety nie wiem dlaczego nie działa boost::cref() ?!

12 października 2012

SetArgReferee w DoAll

Czasami kod z którym mamy do czynienia, przekazuje nam wynik swojego działa zapisując go do argumentu przekazanego przez referencję. Chciało by się żeby wyglądało to troszkę schludniej, jednakże z powodów optymalizacyjnych (C++11 wprowadza coś takiego jak move constructor), trzeba z tym żyć, no i czasami testować. Google Mock wychodzi na przeciw, dostarczając m.in. metodę SetArgReferee() (jest też wersja dla wskaźników).

Poniżej będziemy testować klasę Simple, która posiada jedną metodą do zwracania pierwszego elementu wektora - returnFirst().

Przez "dependency injection", do klasy wstrzykiwany jest obiekt, który zadziała na wektorze (obróci go) nim zostanie zwrócony ten pierwszy (czyli ostatni element). Ten obiekt sobie z-mockujemy i tutaj przyda nam się SetArgReferee.
class IVecReverse {
public:
    virtual bool myReverse(std::vector<int>& v) const = 0;
};

class VecReverse : public IVecReverse {
public:
    bool myReverse(std::vector<int>& v) const {
        if (not v.empty()) {
            std::reverse(v.begin(), v.end());
            return true;
        }
        return false;
    }
};

class Simple {
public:
    Simple(IVecReverse& vs) : VecReverse(vs) {}

    int returnFirst(std::vector<int>& v) {
        if (VecReverse.myReverse(v))
            return v[0];
        return -1;
    }
protected:
    IVecReverse& VecReverse;
};
A teraz test. A więc z-mockowana metoda myReverse(), powinna przyjąć jakiś wektor (v1), który na końcu ma być odwrócony (to będzie nasze v2). Całą tą akcję trzeba zamknąć w DoAll(). W nawiasach <> dla SetArgReferee, wskazujemy numer parametru, dla którego robimy podmianę (mamy jeden argument więc będzie zero).
#include <iostream>
#include <algorithm>
#include <vector>
#include <boost/assign/std/vector.hpp>
#include <boost/assign/list_of.hpp>
#include <gtest/gtest.h>
#include <gmock/gmock.h>

using namespace boost::assign;
using namespace ::testing;

class VecReverseMock : public IVecReverse {
public:
    MOCK_CONST_METHOD1(myReverse, bool (std::vector<int>&));
};

class SimpleTest : public Test {
public:
    VecReverseMock vrMock;
};

TEST_F(SimpleTest, testReturnFirst)
{
    Simple simple(vrMock);
    std::vector<int> v1 = boost::assign::list_of(3)(2)(4);
    std::vector<int> v2 = boost::assign::list_of(4)(2)(3);

    // Return musi byc ostatnie
    EXPECT_CALL(vrMock, myReverse(v1))
            .WillOnce(DoAll(SetArgReferee<0>(v2), Return(true)));
    EXPECT_EQ(4, simple.returnFirst(v1));
}

int main(int argc, char *argv[])
{
    ::testing::InitGoogleMock(&argc, argv);
    return RUN_ALL_TESTS();
}
A teraz krótko o DoAll(a1, a2, ..., an) - jego zadaniem jest wykonanie wszystkich akcji od "a1" do "an", wszystkie poza "an" powinny zwracać void, "an" musi coś zwrócić bo taki będzie wynik całego DoAll (w naszym przypadku jest to Return(true)).
[----------] 1 test from SimpleTest
[ RUN      ] SimpleTest.testReturnFirst
[       OK ] SimpleTest.testReturnFirst (1 ms)
[----------] 1 test from SimpleTest (1 ms total)

28 września 2012

Konstruktor a SetUp (Google Test)

Chciałem się dowiedzieć jak właściwie działa metoda SetUp(), dla klas testów w konfrontacji z konstruktorem takiej klasy.
#include <gmock/gmock.h>
#include <stdio.h>

using namespace ::testing;

class SimpleTest : public Test
{
public:
    SimpleTest() {
        std::cout << "Constructor()" << std::endl;
    }

    void SetUp() {
        std::cout << "SetUp()" << std::endl;
    }

    void TearDown() {
         std::cout << "TearDown()" << std::endl;
    }

    ~SimpleTest() {
        std::cout << "Destructor()" << std::endl;
    }
};

TEST_F(SimpleTest, test1)
{
    std::cout << "test1 " << std::endl;
}

TEST_F(SimpleTest, test2)
{
    std::cout << "test2 " << std::endl;
}

int main(int argc, char *argv[])
{
    ::testing::InitGoogleMock(&argc, argv);
    return RUN_ALL_TESTS();
}
Najwyraźniej każde makro TEST_F, tworzy klasę dziedziczącą z naszej klasy testującej. SetUp() oraz Constructor() wołane są za każdym razem przed rozpoczęciem testu. Odpowiednio TearDown() oraz Destructor(), także wołane są na końcu każdego testu.
[==========] Running 2 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 2 tests from SimpleTest
[ RUN      ] SimpleTest.test1
Constructor()
SetUp()
test1 
TearDown()
Destructor()
[       OK ] SimpleTest.test1 (0 ms)
[ RUN      ] SimpleTest.test2
Constructor()
SetUp()
test2 
TearDown()
Destructor()
[       OK ] SimpleTest.test2 (0 ms)
[----------] 2 tests from SimpleTest (1 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test case ran. (4 ms total)
[  PASSED  ] 2 tests.

GoogleTest/Mock instalacja

Piekło zamarzło, nie udało mi się zainstalować narzędzi "Google Mock" i "Google Test" z repozytorium Ubuntu ("google-mock", "libgtest-dev"). Tzn. udało się, ale nijak nie zrozumiałem jak z linkować to co dostałem z moim projektem/testem. Według nich powinienem zadziałać skryptem (gtest-config), tylko gdzie go można znaleźć...?
g++ $(gtest-config --cppflags --cxxflags) -o foo.o -c foo.cpp
g++ $(gtest-config --ldflags --libs) -o foo foo.o
W ramach kompilacji chciałem przeprowadzić standardowe budowanie ze skryptów make. Wpierw okazało się, że autoconf/make(?) nie jest już wspierany (albo odradzany), i że powianiem skorzystać z CMake. Tak też uczyniłem, lecz to co zostało wyplute, nijak nie chciało współgrać z linkerem.

Skończyło się ręcznym kompilowaniu bibliotek. Wpierw ustawiłem sobie kilka zmiennych środowiskowych, żeby było bardziej przejrzyście.
export GMOCK_DIR=~/Downloads/gmock-1.6.0/
export GTEST_DIR=~/Downloads/gmock-1.6.0/gtest/
export GMOCK_INCLUDE=~/Downloads/gmock-1.6.0/include/
export GTEST_INCLUDE=~/Downloads/gmock-1.6.0/gtest/include/
export GMOCK_LIB=~/Downloads/gmock-1.6.0
Kompilacja do statycznych bibliotek
g++ -I${GTEST_INCLUDE} -I${GTEST_DIR} -I${GMOCK_INCLUDE} \
    -I${GMOCK_DIR} -c ${GTEST_DIR}/src/gtest-all.cc
g++ -I${GTEST_INCLUDE} -I${GTEST_DIR} -I${GMOCK_INCLUDE} \
    -I${GMOCK_DIR} -c ${GMOCK_DIR}/src/gmock-all.cc
ar -rv libgmock.a gtest-all.o gmock-all.o
Prosty test:
#include <gtest/gtest.h>

using namespace testing;

int main()
{
    EXPECT_EQ(1, 2);
    return 0;
}
I wreszcie kompilacja testu:
g++ -I${GTEST_INCLUDE} -I${GMOCK_INCLUDE} \
    main.cpp ${GMOCK_LIB}/libgmock.a -lpthread -o main
Oraz wynik:
$ ./main 
main.cpp:7: Failure
Value of: 2
Expected: 1

23 marca 2011

Testy jednostkowe

Ot zainteresowałem się ostatnio testami jednostkowymi. W moim przypadku wybór padł na CppUnit. Moja obecna platforma to Windows+Eclipse+cygwin więc, instalacji dokonałem właśnie za pośrednictwem cygwin-a
      
Devel | cppunit: C++ unit testing framework (1.12.1-1)
      
Aby dołączyć bibliotekę do środowiska Eclipse skorzystałem z tego artykułu
* http://beans.seartipy.com/2007/11/26/unit-testing-c-programs-using-cppunit-in-eclipse-ide-on-windows/
      
Znalazłem tylko dwa oficjalne artykuły traktujące o tym jak to trzeba ugryźć, ale przykład był dla mnie za mały, żebym czuł, że wszystko do mnie trafiło.
* http://cppunit.sourceforge.net/doc/1.11.6/cppunit_cookbook.html
* http://cppunit.sourceforge.net/doc/1.11.6/money_example.html
      
Mniej więcej, tworzymy klasy dziedziczące po TestFixture, do których zadań należy przetestowania pojedynczej klasy lub jakiejś funkcjonalności. W środku TextFixture tworzymy obiekt TestSuite, w którym zbieramy wskaźniki do metod, które to z kolei, służą do właściwego testowania.
      
Składać (TestSuite) możemy klepiąc kod, albo korzystając z makr (szybsze i bezpieczniejsze rozwiązanie):
      
CPPUNIT_TEST_SUITE( FluTest );
CPPUNIT_TEST( testTemerature );
CPPUNIT_TEST( testNose );
CPPUNIT_TEST_SUITE_END();


A w owych metodach testujących korzystamy z makr np.
CPPUNIT_ASSERT(warunek)
CPPUNIT_ASSERT_EQUAL(expected, actual)

Więcej tutaj:
* http://cppunit.sourceforge.net/doc/1.11.6/group___assertions.html
      
Są jeszcze inne fajne rzeczy jak, np. drukowanie raportów o testach do xml-a, ale ...
      
Wtyczka do CppUnit (ECUT) dla Eclipse.
      
Przy okazji znalazłem na googlu coś takiego jak ECUT
* http://sourceforge.net/projects/ecut/
czyli coś go generowania "szablonów kodu testującego" na podstawie kodu właściwego.
Ładnie też pokazuje jak się testy wykonały.
Wtyczkę (ECUT_1.0_M3-RC3a) zainstalowałem, posiłkując się opisem z:
* http://xrigher.co.cc/cpp/set-up-cppunit-eclipse-plugin-ecut-for-c-on-ubuntu/
     
Podczas konfiguracji wybrałem libcppunit.a, a miałem jeszcze do wyboru libcppunit.dll.a, ale poszło. Nie znalazłem żadnej "papierowej" dokumentacji, bo też jej bardzo nie szukałem. Za to po ściągnięciu EcutDemo_M3.zip ze strony projektu, znalazłem w środku przykład wraz z wideo tutorialem :D
     
Podsumowanie.
ECUT ciekawy, choć niby testy powinny być pisane, przed napisaniem właściwego kodu. Unit testy/CppUnit, hmm dobre, ale trzeba pamiętać o przetestowaniu każdej małej jednostki (metody/funkcji/klasy ?). Jak zabierzemy się tylko za te większe, to błędów możemy szukać jak zwykle ...
     
A mi się śni w szybkim tworzenia kod, WYBIÓRCZE składowanie w UnitTestach danych przykładowych (wykorzystywanych przy debugowaniu), dla niektórych metod, wtedy po naprawach mógłbym sprawdzić czy czegoś nie popsułem. Hmm, czasem przy debugu złożonych metod, musiałem wewnątrz ciała dużej metody coś za hard-kodować, ciekawe czy jakby mój sen nie był wybiórczy, to bym się przed tym ustrzegł?