2016-07-12 6 views
7

Я пытался реализовать некоторые модульные тесты для модуля. Пример модуль с именем alphabet.py выглядит следующим образом:Смещение глобальной переменной

import database 

def length_letters(): 
    return len(letters) 

def contains_letter(letter): 
    return True if letter in letters else False 


letters = database.get('letters') # returns a list of letters 

Я хотел бы, чтобы дразнить ответ из базы данных с некоторыми значениями моего выбора, но ниже код не похож на работу.

import unittests 
import alphabet 
from unittest.mock import patch 


class TestAlphabet(unittest.TestCase): 
    @patch('alphabet.letters') 
    def setUp(self, mock_letters): 
     mock_letters.return_value = ['a', 'b', 'c'] 

    def test_length_letters(self): 
     self.assertEqual(3, alphabet.length_letters()) 

    def test_contains_letter(self): 
     self.assertTrue(alphabet.contains_letter('a')) 

Я видел много примеров, в которых «патч» применяется к методам и классам, но не к переменным. Я предпочитаю не исправлять метод database.get, потому что я могу использовать его снова с разными параметрами позже, поэтому мне нужен другой ответ.

Что я здесь делаю неправильно?

ответ

2

Вам не нужно использовать mock. Просто импортировать модуль и изменить значение глобальной в setUp():

import alphabet 

class TestAlphabet(unittest.TestCase): 
    def setUp(self): 
     alphabet.letters = ['a', 'b', 'c'] 
+3

Несчастливым следствием этого подхода является то, что любой другой тест, который использует эту переменную уровня модуля, потерпит неудачу, если вы не сохраните старое значение и не вернете его. Подделка позаботится об этом для вас. –

+1

Вы можете установить значение 'alphabet.letters' обратно на то, что было в функции' tearDown'. – tomas

+0

Кроме того, поскольку 'setUp' привязан ко всему тестовому классу, вы можете использовать это значение только для букв. Ответ Уилла ниже позволяет вам делать несколько макетов для разных тестовых случаев, и они очищаются в конце, поэтому нет риска случайного загрязнения. – raindrift

10

Попробуйте это:

import unittests 
import alphabet 
from unittest.mock import patch 


class TestAlphabet(unittest.TestCase): 
    def setUp(self): 
     self.mock_letters = mock.patch.object(
      alphabet, 'letters', return_value=['a', 'b', 'c'] 
     ) 

    def test_length_letters(self): 
     with self.mock_letters: 
      self.assertEqual(3, alphabet.length_letters()) 

    def test_contains_letter(self): 
     with self.mock_letters: 
      self.assertTrue(alphabet.contains_letter('a')) 

Вы должны применить макет в то время как индивидуальные тесты на самом деле работает, а не только в setUp(). Мы можем создать макет в setUp() и применить его позже с помощью менеджера контекста with ....

+0

Это то, о чем я просил, но ответ Джона выглядит лучше для приведенного примера. Тем не менее, я считаю ваш полезным для других случаев. Спасибо. – Funkatic

+0

Без проблем, рад помочь! – Will

0

У меня возникла проблема, когда я пытался издеваться над переменными, которые использовались вне любой функции или класса, что является проблематичным, потому что они используются в момент, когда вы пытаетесь высмеять класс, прежде чем вы сможете издеваться над значениями.

В результате я использовал переменную окружения. Если существует переменная среды, используйте это значение, иначе используйте приложение по умолчанию. Таким образом, я мог бы установить значение переменной среды в моих тестах.

В моем тесте, я имел этот код до того, как класс был импортирован

os.environ["PROFILER_LOG_PATH"] = "./" 

В моем классе:

log_path = os.environ.get("PROFILER_LOG_PATH",config.LOG_PATH) 

По умолчанию мой config.LOG_PATH является /var/log/<my app name>, но теперь, когда тест запущен, путь к журналу установлен в текущий каталог. Таким образом, вам не нужен root-доступ для запуска тестов.

+1

В идеале, ваши тесты должны быть одинаковыми во всех средах без какой-либо дополнительной настройки. В противном случае они могут перейти на вашу локальную машину, но сбой в другом месте. – Funkatic

Смежные вопросы