Код модульного тестирования Python, который вызывает функции Python на уровне ОС/модуля

У меня есть модуль/скрипт python, который делает несколько из них

  1. На различных вложенных уровнях внутри сценария я беру входные данные командной строки, проверяю их, применяю разумные значения по умолчанию.
  2. Я также проверяю, существуют ли несколько каталогов

Выше приведены только два примера. Я пытаюсь выяснить, какова лучшая «стратегия» для проверки этого. Что я сделал, так это создал функции-оболочки для raw_input и os.path.exists в моем модуле, а затем в своем тесте я переопределяю эти две функции, чтобы получать входные данные из моего списка массивов или выполнять какое-то насмешливое поведение. Этот подход имеет следующие недостатки

  1. Функции-обертки существуют только для тестирования, и это загрязняет код.
  2. Я должен не забывать использовать функцию-оболочку в коде каждый раз, а не просто вызывать os.path.exists или raw_input

Любые блестящие предложения?


person Kannan Ekanath    schedule 19.02.2013    source источник
comment
Я добавил еще несколько тегов, чтобы этот вопрос привлек больше внимания.   -  person User    schedule 19.02.2013


Ответы (3)


Короткий ответ: monkey patch эти системные вызовы.

В ответе на вопрос Как отобразить перенаправленный стандартный ввод в Питон?

Вот простой пример для raw_input() с использованием lambda, который отбрасывает подсказку и возвращает то, что мы хотим.

Тестируемая система

$ cat ./name_getter.py
#!/usr/bin/env python

class NameGetter(object):

    def get_name(self):
        self.name = raw_input('What is your name? ')

    def greet(self):
        print 'Hello, ', self.name, '!'

    def run(self):
        self.get_name()
        self.greet()

if __name__ == '__main__':
    ng = NameGetter()
    ng.run()

$ echo Derek | ./name_getter.py 
What is your name? Hello,  Derek !

Прецедент:

$ cat ./t_name_getter.py
#!/usr/bin/env python

import unittest
import name_getter

class TestNameGetter(unittest.TestCase):

    def test_get_alice(self):
        name_getter.raw_input = lambda _: 'Alice'
        ng = name_getter.NameGetter()
        ng.get_name()
        self.assertEquals(ng.name, 'Alice')

    def test_get_bob(self):
        name_getter.raw_input = lambda _: 'Bob'
        ng = name_getter.NameGetter()
        ng.get_name()
        self.assertEquals(ng.name, 'Bob')

if __name__ == '__main__':
    unittest.main()

$ ./t_name_getter.py -v
test_get_alice (__main__.TestNameGetter) ... ok
test_get_bob (__main__.TestNameGetter) ... ok

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

OK
person Johnsyweb    schedule 19.02.2013

Решение 1. Я бы сделал что-то подобное, потому что это работает:

def setUp(self):
    self._os_path_exists = os.path.exists
    os.path.exists = self.myTestExists # mock

def tearDown(self):
    os.path.exists = self._os_path_exists

Это не так приятно.

Решение 2. Как вы сказали, реструктуризация вашего кода невозможна, верно? Это сделало бы его хуже для понимания и неинтуитивным.

person User    schedule 19.02.2013

Johnnysweb точно знает, что вам нужно сделать, но вместо создания собственного вы можете импортировать и использовать макет. Mock специально разработан для модульного тестирования и позволяет чрезвычайно просто делать то, что вы пытаетесь сделать. Он встроен в Python 3.3.

Например, если вы хотите запустить модульный тест, который заменяет os.path.isfile и всегда возвращает True:

try:
    from unittest.mock import patch
except ImportError:
    from mock import patch

class SomeTest(TestCase):

    def test_blah():
        with patch("os.path.isfile", lambda x: True):
            self.assertTrue(some_function("input"))

Это может сэкономить вам МНОГО шаблонного кода, и это вполне читабельно.

Если вам нужно что-то более сложное, например, заменить supbroccess.check_output, вы можете создать простую вспомогательную функцию:

def _my_monkeypatch_function(li):
     x,y = li[0], li[1]
     if x == "Reavers":
        return "Gorram"
     if x == "Inora":
        return "Shiny!"
     if x == y:
        return "The Ballad of Jayne"

def test_monkey():
     with patch("subprocess.check_output", _my_monkeypatch_function):
            assertEquals(subprocess.check_output(["Mudder","Mudder"]),
                                                 "The Ballad of Jayne")
person Community    schedule 30.12.2013