Pokazywanie postów oznaczonych etykietą mock. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą mock. 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ć.

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.

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

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