Является ли хорошей практикой модульное тестирование соответствия принципу замещения Лискова?

Предположим, что есть класс с именем Sprinter:

public class Sprinter {

    protected int travelMeters;

    public void run(int seconds) {
        this.travelMeters = 9 * seconds;
    }

    public int getTravelMeters(){
        return travelMeters;
    }
}

И тип SprintGenius, наследующий Sprinter:

class SprintGenius extends Sprinter {

    public void run(int seconds) {
        this.travelMeters = 10 * seconds;
    }
}

По логике нужно создать 2 класса юнит-тестов, по одному для каждого типа.

В рамках модульного теста Sprinter я бы получил:

@Before
public void setUp() {
  Sprinter sprinter = new Sprinter();
}

public void testSprinterShouldRun90metersWithin10Seconds() {
  sprinter.run(10);
  assertEquals(sprinter.getTraveledMeters(),90);
}

В рамках модульного теста SprintGenius я бы получил:

@Before
public void setUp() {
  Sprinter sprinter = new SprintGenius();
}

public void testSprinterShouldRun100metersWithin10Seconds() {
  sprinter.run(10);
  assertEquals(sprinter.getTraveledMeters(),100);
}

В обоих приведенных выше тестах я бы проверил количество пройденных метров за 10 секунд.

Очевидно, что эти оба теста будут зелеными.

А как насчет нарушения принципа замещения Лискова?

Действительно, любой клиентский код должен ожидать, что любой спринтер пробежит ровно 10 метров за 9 секунд.

3 решения (первые два решения сигнализируются ПРАВИЛАМИ разработчикам ВСЕЙ команды и должны быть приняты и сохранены, даже если не все хорошо освоят концепцию Лискова)

1) В классе Sprinter повторяются все тесты, но на этот раз на основе Sprinter sprinter = new SuperGenius() и ожидаются 90 метров. => что должно потерпеть неудачу, и это именно то, что нам нужно! => предотвратить нарушение принципа Лискова.

2) В классе SprintGenius всегда добавляйте аналогичный «клон» каждого теста на основе базового класса, исходя из точно таких же ожиданий. Итак, если у вас есть 2 разных теста, мы получим 4 теста. 2 объявляет Sprinter как Sprinter и 2 объявляет Sprinter как SprintGenius.

3) Никогда не наследуйте от класса-конкретного (предполагаю, что это была ваша первая реакция, когда вы читали этот пост:)), предпочитайте композицию, если она подходит! Чтобы этой проблемы не было.

Как лучше всего предотвратить нарушение принципа замещения Лискова, основываясь на том факте, что многие разработчики игнорируют принцип Лискова и часто испытывают соблазн наследовать от конкретных классов вместо использования другого лучшего способа, такого как композиция или другая иерархия наследования?

Я не хочу, чтобы у меня возникали проблемы из-за того, что разработчик наследует от моего написанного класса (не сообщая мне..), вводя его в общий огромный список разнородных списков Sprinter и обращаясь ко мне с «привет, странное поведение!» и часы отладки...

Я, конечно, не хочу объявлять ВСЕ мои конкретные классы "окончательными" :)


person Mik378    schedule 02.11.2012    source источник
comment
LSP приложения здесь? Вы не передаете объект в качестве параметра какой-либо функции, вы используете объект, который вы определяете в том же классе.   -  person SJuan76    schedule 02.11.2012
comment
Я сказал: внедрить его в общий огромный список разнородных списков Sprinter и столкнуться со мной с «привет, странное поведение!» Итак, конечно, LSP возникает, когда любой клиентский код манипулирует объектами Sprinters, заставляя подкласс влиять на ожидаемое поведение базового класса. Я согласен, в посте нет кода, так как его довольно легко вывести.   -  person Mik378    schedule 02.11.2012
comment
Но это выходит за рамки модульного тестирования.   -  person SJuan76    schedule 02.11.2012


Ответы (2)


Это не является нарушением принципа замещения Лисков. Это плохой дизайн. Оба ваших класса отличаются не поведением, а данными. Таким образом, у вас должен быть только один класс, и клиенты должны ожидать, что любой спринтер пробежит заданное расстояние пропорционально его скорости.

Поэтому вы должны добавить свойство скорости и иметь один класс с конкретным поведением.

После этого вы можете подумать о создании действительно расширенных классов с новым поведением и подумать о тестировании.

При таком параметре скорости принцип замещения Лискова не должен нарушаться для бегунов других видов, даже если они бегут с разной скоростью.

Ваш вопрос звучит так: мой класс, расширяющий человека, не проходит тест, потому что я изменил имя человека с «Питер» на «Роберт».

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

person Piotr Müller    schedule 02.11.2012
comment
Хороший ответ! Спасибо :) Так это нарушает принцип подстановки Питера? (парень, который ничего не понимает ;) я шучу) :) - person Mik378; 02.11.2012
comment
LSP говорит о предусловии и постусловии. Примером предварительного условия является то, что значение seconds, переданное в run(), является целым числом. Примером постусловия является то, что this.travelMeters равно значению скорости объекта, умноженному на количество секунд, заданное run(). Если постусловие ограничено таким образом, что Sprinter должен иметь определенное значение скорости, тогда LSP говорит, что SprintGenius перестает быть подклассом Sprinter в смысле LSP, даже если код все еще компилируется. - person rwong; 10.03.2013

Модульный тест предназначен для тестирования конкретного модуля и не может и не должен использоваться для чего-то большего. Соответствие принципу замещения Лискова является более широкой проблемой в области системы, а не области модуля. Более того, это не то, что нужно тестировать в коде. Это скорее проблема чистого дизайна, не связанная с реализацией. Я не думаю, что LSP можно реализовать с помощью автоматических инструментов. Это должно быть обработано во время проверки проекта, а затем во время проверки кода (которая должна проверять соответствие дизайну).

person SomeWittyUsername    schedule 02.11.2012