TDD не работает в Python? - PullRequest
3 голосов
/ 30 апреля 2010

Предположим, у нас есть класс UserService с атрибутом current_user. Предположим, он используется в AppService классе.

У нас есть AppService, покрытые тестами. В тестовой настройке мы заглушаем current_user с некоторым фиктивным значением:

UserService.current_user = 'TestUser'

Предположим, мы решили переименовать current_user в active_user. Мы переименовываем его в UserService, но забываем внести изменения в его использование в AppService.

Мы проводим тесты, и они проходят! Тестовая настройка добавляет атрибут current_user, который все еще (ошибочно, но успешно) используется в AppService.

Теперь наши тесты бесполезны. Они проходят, но приложение не будет работать на производстве.

Мы не можем полагаться на наш набор тестов ==> TDD невозможен.

TDD не работает в Python?

Ответы [ 3 ]

2 голосов
/ 30 апреля 2010

Проблема действительно не в TDD и не в Python. Прежде всего, TDD не дает вам доказательства того, что после прохождения всех ваших тестов ваше приложение будет в порядке. Представьте себе, например, Функция multiplyBy2 (), которую можно протестировать с помощью входных данных 1,2,3 и выходных 2,4,8, а теперь представьте, что вы реализовали multiplyBy2 как возведение в квадрат. Все ваши тесты пройдены, у вас 100% покрытие кода, и ваша реализация неверна. Вы должны понимать, что TDD может только дать вам гарантию, что если ваш тест не пройден, что-то не так с вашим приложением, ни больше, ни меньше. Таким образом, как предлагается в другом ответе, проблема в том, что у вас нет теста, который не проходит. Если бы вы использовали какой-то статически типизированный язык, компилятор сделал бы для вас этот тест и пожаловался бы на использование несуществующего метода. Это не означает, что вы должны использовать статически типизированный язык, это просто означает, что вам нужно написать больше тестов на динамически типизированном языке. Если вы заинтересованы в обеспечении правильности кода, вам следует взглянуть на проектирование по контракту для обеспечения корректности, по крайней мере, во время выполнения и формальных спецификаций, чтобы иметь доказательства по крайней мере для некоторых алгоритмов, но это, я думаю, довольно далеко от стандартного кодирования. *

1 голос
/ 01 мая 2010

ОК, я нашел решение. Библиотека Python Mock делает то, что я хочу .

Ниже приведен код, которым я заканчиваю.

Определения моделей и услуг:

class User(object):
    def __init__(self):
        self.roles = []


class UserService(object):
    def get_current_user(self):
        return None # get from environment, database, etc.

    current_user = property(get_current_user)


class AppService(object):
    def __init__(self, userService):
        self.userService = userService

    def can_write(self):
        return 'admin' in self.userService.current_user.roles

Вот как можно протестировать can_write метод AppService с разными пользователями:

class AppServiceTests(unittest.TestCase):
    def test_can_write(self):
        user = User()

        @patch_object(UserService, 'current_user', user)
        def can_write():
            appService = AppService(UserService())
            return appService.can_write()

        user.roles = ['admin']
        self.assertTrue(can_write())

        user.roles = ['user']
        self.assertFalse(can_write())

Если вы переименуете свойство current_user только в классе UserService, вы получите ошибку при попытке исправить объект. Это то поведение, которое я искал.

0 голосов
/ 30 апреля 2010

Перед изменением что-то должно отличаться в поведении объекта, в зависимости от значения current_user. Давайте назовем это чем-то предикатом (). И простите мой питон; рассмотрим этот псевдокод:

UserService.current_user = 'X'
assertFalse(obj.predicate())
UserService.current_user = 'Y'
assertTrue(obj.predicate())

OK? Итак, это ваш тест. Получите это, чтобы пройти. Теперь измените тестируемый класс, чтобы current_user был переименован в active_user. Теперь проверка не пройдёт, либо при первом утверждении, либо при втором. Поскольку вы больше не изменяете значение поля, ранее известного как current_user, предикат будет ложным или истинным в обоих случаях. Теперь у вас есть очень сфокусированный тест, который предупредит вас, когда класс изменится таким образом, что аннулирует настройку другого теста.

...