Уведомления

Группа в Telegram: @pythonsu

#1 Июнь 1, 2020 20:26:27

Master_Sergius
Зарегистрирован: 2013-09-12
Сообщения: 271
Репутация: +  7  -
Профиль   Отправить e-mail  

mockito mock(os.remove) и проверка вызовов

Здравствуйте,
Я продолжаю играться с тестами, и вот есть у меня такая функция:

  
 # file src/secret_service.py
 
def remove_obsolete_files(file_list):
    for filename in file_list:
        try:
             # кусок кода вырезан: валидация, добавление правильного пути и т.п.
             os.remove(full_filename)
        except:
            # filename can be None 
            pass

Я хочу проверить, что os.remove будет вызываться с правильными full_filename. Обычно, я юзал mock.patch:

  
import mock
from src.secret_service import remove_obsolete_files
 
def test_remove_obsolete_files():
     mock_remove = mock.MagicMock()
     with mock.patch('src.secret_service.os.remove', mock_remove):
           remove_obsolete_files(['file1', None, 'file2'])
     # тогда mock_remove имеет атрибут mock_calls и я могу проверить как угодно

Но, в текущем проекте повсюду используется библиотека mockito, а не mock, и с ней у меня не получается такого сделать, то есть даже замокать os.remove не удается через эти его when.thenReturn, verify и т.д.
Можно ли это сделать с помощью mockito и как?



———————————————————————————
Мой блог о семействе *nix: http://nixtravelling.blogspot.com/

Отредактировано Master_Sergius (Июнь 1, 2020 20:26:53)

Офлайн

#2 Июнь 1, 2020 21:14:51

FishHook
От:
Зарегистрирован: 2011-01-08
Сообщения: 8312
Репутация: +  568  -
Профиль   Отправить e-mail  

mockito mock(os.remove) и проверка вызовов

Master_Sergius
Знаете, почему лично я люблю юнит-тесты? Потому что они очень ярко маркируют плохой код. Если юнит-тест пишется трудно, если возникают вопросы, это значит, что код плохой.
В вашем случае, код плохой, потому что нарушает принцип единой ответственности. Ваша функция удаляет ненужные файлы? Ок, это её ответственность. А вот получать полное имя на основе короткого, это ответственность другой функции.

 def get_full_name(name):
    pass
def remove_obsolete_files(file_list):
    for filename in file_list:
        try:
             os.remove(get_full_name(filename ))
        except:
            # filename can be None 
            pass

Есть проблемы с тестированием функции get_full_name? Нету. Значит этот код лучше.



Офлайн

#3 Июнь 2, 2020 00:11:48

py.user.next
От:
Зарегистрирован: 2010-04-29
Сообщения: 9846
Репутация: +  853  -
Профиль   Отправить e-mail  

mockito mock(os.remove) и проверка вызовов

Офлайн

#4 Июнь 2, 2020 11:56:40

Master_Sergius
Зарегистрирован: 2013-09-12
Сообщения: 271
Репутация: +  7  -
Профиль   Отправить e-mail  

mockito mock(os.remove) и проверка вызовов

FishHook
Master_SergiusЗнаете, почему лично я люблю юнит-тесты? Потому что они очень ярко маркируют плохой код. Если юнит-тест пишется трудно, если возникают вопросы, это значит, что код плохой.В вашем случае, код плохой, потому что нарушает принцип единой ответственности. Ваша функция удаляет ненужные файлы? Ок, это её ответственность. А вот получать полное имя на основе короткого, это ответственность другой функции.

Полностью с вами согласен, но хотелось бы сам цикл протестировать все равно



———————————————————————————
Мой блог о семействе *nix: http://nixtravelling.blogspot.com/

Офлайн

#5 Июнь 2, 2020 12:11:34

Master_Sergius
Зарегистрирован: 2013-09-12
Сообщения: 271
Репутация: +  7  -
Профиль   Отправить e-mail  

mockito mock(os.remove) и проверка вызовов

py.user.next
https://mockito-python.readthedocs.io/en/latest/the-functions.html#mockito.patch

Надо же…. совпадение? не думаю. Немного разница есть, тут надо verify использовать, но я разобрался как, большое спасибо всем.



———————————————————————————
Мой блог о семействе *nix: http://nixtravelling.blogspot.com/

Офлайн

#6 Июнь 2, 2020 14:17:15

FishHook
От:
Зарегистрирован: 2011-01-08
Сообщения: 8312
Репутация: +  568  -
Профиль   Отправить e-mail  

mockito mock(os.remove) и проверка вызовов

Master_Sergius
смысл юнит тестов не в тестировании циклов, а в том, что у каждого юнита есть ответственность и вы тестируете эту ответственность. Функция может либо возвращать некие данные, либо изменять состояние какого-то объекта. В вашем случае функция получает список строк и изменяет состояние объекта os. Этой функции плевать, что именно происходит с файловой системой, это не её ответственность. Вам надо сравнить состояние изменяемого объекта до и после применения функции. Если объект изменился ожидаемо, значит функция работает правильно.

my_module.py

 import os
def get_full_name(name):
     return 'bala-bla' + name
def remove_obsolete_files(file_list):
    for filename in file_list:
        try:
             os.remove(get_full_name(filename ))
        except:
            # filename can be None 
            pass

test.py
 class _os:
    def __init__(self):
         self.filenames = ['a', 'b', 'd', 'e']
    def remove(self, name):
         self.filenames = [x for x if x != name]
def _get_full_name(name):
     return name
@mock(os=_os, get_full_name=_get_full_name)
def test_remove_obsolete_files(file_list):
    filenames_to_remove =  ['a', 'b', 'c']
    remove_obsolete_files(filenames_to_remove )
    assert os.filenames == ['d', 'e']

декоратор mock здесь условный, может быть сколько угодно реализаций, как замокать объект. Главное - смысл юнит тестирования. Тестируйте только ответственность юнита, и мокайте зависимости.



Отредактировано FishHook (Июнь 2, 2020 14:19:38)

Офлайн

#7 Июнь 2, 2020 15:00:06

Master_Sergius
Зарегистрирован: 2013-09-12
Сообщения: 271
Репутация: +  7  -
Профиль   Отправить e-mail  

mockito mock(os.remove) и проверка вызовов

Мысль Вашу понял, но некоторые вещи хотелось бы уточнить. Двайте изменим чуть задачу, допустим получение списка полных имен я вынес в отдельную функцию, а os.remove я мокаю в любом случае. То есть, имеем такую функцию:

  
def remove_obsolete_files(file_list):
    for filename in file_list:
        try:
             os.remove(filename)
        except:
            # filename can be None
            pass

Какой тест (или тесты) мы можем написать к данной функции? Просто проверить, был ли вызов os.remove с таким-то аргументом, так ведь?



———————————————————————————
Мой блог о семействе *nix: http://nixtravelling.blogspot.com/

Офлайн

#8 Июнь 2, 2020 15:13:54

FishHook
От:
Зарегистрирован: 2011-01-08
Сообщения: 8312
Репутация: +  568  -
Профиль   Отправить e-mail  

mockito mock(os.remove) и проверка вызовов

Master_Sergius
Какой тест (или тесты) мы можем написать к данной функции? Просто проверить, был ли вызов os.remove с таким-то аргументом, так ведь?
Я же вам написал выше - ваша функция изменяет что-то в файловой системе. Задача теста проверить, то ли она изменяет что нужно. Сама файловая система нам не важна, что имеено происходит по вызову os.remove - не имеет значения. У нас есть некий набор файлов в файловой системе. После применения функции набор файлов должен измениться. Что такое набор файлов - не важно. Что такое файл - плевать. Важно вот это самое изменение, за которое отвечает функция. Соответственно, все что нам не важно мы заменяем на затычки - объекты, которые ведут себя предсказуемо в рамках теста.

    
 class _os:
    def __init__(self):
         self.filenames = ['a', 'b', 'd', 'e'] 
    def remove(self, name):
         self.filenames = [x for x if x != name]
   
@mock(os=_os())
def test_remove_obsolete_files(file_list):
    filenames_to_remove =  ['a', 'b', 'c']
    remove_obsolete_files(filenames_to_remove)
    assert os.filenames == ['d', 'e']

мы знаем, что у нас есть зависимость - некий os и у него обязательно должен быть метод remove. Мы создаем объект-заглушку для него, чтобы абстрагироваться от реальной файловой системы. Мы знаем состояние этого объекта до теста (потому что сами это состояние и создали.) Мы знаем его состояние после теста. Сравниваем - это результат теста.



Офлайн

#9 Июнь 2, 2020 15:54:14

Master_Sergius
Зарегистрирован: 2013-09-12
Сообщения: 271
Репутация: +  7  -
Профиль   Отправить e-mail  

mockito mock(os.remove) и проверка вызовов

Хм, интересный подход, у меня была другая задумка, но наверное сделаю по-вашему. Спасибо.



———————————————————————————
Мой блог о семействе *nix: http://nixtravelling.blogspot.com/

Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version