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ć.

28 czerwca 2014

[Notki] Działanie i budowanie plików dll

Notki z godzinnego podcastu Gynvaela Coldwinda odnośnie budowania oraz działania plików dll. Tutaj tylko budowanie.



Plik asdf.c.
#include <stdio.h>

__declspec(dllimport) int add(int a, int b);

int main(void) {
    printf("5+6 ==  %d\n", add(5, 6));
    return 0;
}
Biblioteka (my_dll.c) będzie udostępniać tylko jedną funkcję. __declspec nie jest konieczne, jeżeli będziemy korzystać z pliku z definicjami.
__declspec(dllexport) int add(int a, int b) {
    return a + b;
}
Plik z definicjami my_dll.def.
LIBRARY my_dll
EXPORTS
    add
    dodaj=add
Budowanie:

Najpierw kompilujemy do pliku obiektowego, następnie trzeba posłużyć się dllwrap, który jest rodzajem linkera, dla plików dll. Dodawanie pliku z definicjami nie jest obowiązkowe, przydaje się dopiero wtedy, gdy chcemy stworzyć aliasy na nasze funkcje.

dlltool pozwala na tworzenie bibliotek (dla kompilatorów) (.a), coś z czym będziemy linkować dany program. Jest to konieczne, bowiem nie każdemu kompilatorowi możemy powiedzieć, aby korzystał z dll, akurat gcc to potrafi. W tej bibliotece będzie wyłącznie informacja dla linker, że dany kod znajduje się w jakiejś dll-ce. Wymagany jest plik z definicjami exportu.
> gcc -c my_dll.c
> dllwrap my_dll.o -o my_dll.dll --def my_dll.def 
> dlltool -l libmy_dll.a -D my_dll.dll -d my_dll.def

> gcc asdf.c my_dll.dll
# alternatywne rozwiązanie z wskazaniem biblioteki libmy_dll.a 
# będzie poszukiwana w obecnym katalogu
> gcc asdf.c -L. -lmy_dll

Name mangling

Funkcje stworzone w C++, są exportowane pod innymi nazwami np. _Z4addii - udekorowane są dodatkowymi informacjami. Wynika to z faktu, że C++ pozwala na istnieje kilku funkcji o tej samej nazwie, pod warunkiem, że różnią się one przyjmowanymi typami. Aby móc w kodzie C++ skorzystać z funkcji add (funkcji C z dll) należy skorzystać z takiej deklaracji:
extern "C" {
    int add(int a, int b);
}
W odwrotnej sytuacji, gdy mamy kod w C i chcemy skorzystać z funkcji C++ w dll, trzeba posłużyć się tą udekorowaną nazwą _Z4addii.