Издеваясь над самим СУТОМ


Мой вопрос касается модульного тестирования. Предположим, что у нас есть класс ниже;

class X
{
    public function p1(){
       //logic
       $a = $this->p2();
       //more logic
    }

    public function p2(){
       //even more logic
    }
}

При написании модульного теста для метода p1, должен ли я имитировать метод p2?

Я думаю, что тест, написанный для метода p1, должен выполнять только метод p1, а не p2. Но для того, чтобы понять, что я должен получить макет класса X и вызвать метод p1 на этом макете экземпляра, как показано ниже.

$xMock = $this->getMockBuilder('\X')
    ->setMethods(array('p2'))
    ->getMock();

$xMock->expects($this->any())
    ->method('p2')
    ->will($this->returnValue($value));

$resultTobeAsserted = $xMock->p1();
К сожалению, делать это кажется мне немного неправильным. Я обсуждал эту тему. с моими коллегами и это сводилось к тому, как вы определяете свой SUT(тестируемую систему). Если тестер рассматривает конкретный метод, который тестируется, как SUT, то другие методы, которые вызываются из SUT, будут казаться зависимостями, и, естественно, тестер захочет издеваться над ними. С другой стороны, если тестер рассматривает весь класс как SUT, то эти вызовы методов станут частью теста, так что не будет никакой причины издеваться над ними.

Верен ли этот вывод? Какого рода мышление даст более надежные юнит-тесты?

3   5  

3 ответа:

При написании модульного теста для метода p1, должен ли я имитировать метод p2?

- Нет.

Вы вызываете метод в классе и ожидаете, что что-то произойдет.

С помощью mocking p2 вы делаете исключения для деталей реализации в классе.

К сожалению, делать это кажется мне немного неправильным.

Я говорю, что вырубка это пятно на.

Какой тип мышления даст более прочную единицу тесты?

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

Если вы тестируете один метод и имитируете часть реализации этого метода (внутренний вызов метода), то вы тестируете конкретную реализацию, и если тест не проходит, вы не знаете, изменилось ли внешнее поведение.


Я писал об этом более подробно. in:

The UNIT in unit testing.

Который подробно описывает еще пару моментов о том, почему я думаю, что важно тестировать поведение, а не методы.

Короче говоря

Модульное тестирование в PHP - это тестирование наблюдаемого поведения класса!

Поведение:

  • возвращаемые значения
  • вызов других методов
  • изменение глобального состояния (запись в файлы, БД, $GLOBALS)

Тест такая вещь. Не обращайте внимания на детали реализации.

Если тестер рассматривает конкретный метод, который тестируется, как SUT, то другие методы, которые вызываются из SUT, будут казаться зависимостями, и, естественно, тестер захочет издеваться над ними.

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

Ваше мышление правильно. Если вам нужен тестовый метод p1, вас не волнует p2, потому что вы предполагаете, что он был протестирован в другом тесте. Таким образом, вы можете просто издеваться/stub p2. Но вы должны помнить, что вы должны издеваться/заглушать только p2. Поэтому ваш пример должен выглядеть примерно так:

$xMock = $this->getMockBuilder('\X')
    ->setMethods(array('p2'))
    ->getMock();

$xMock->expects($this->any())
    ->method('p2')
    ->will($this->returnValue($value));

$resultTobeAsserted = $xMock->p1();