Модульное тестирование PHP - протестируйте функцию с экземпляром службы внутри - PullRequest
0 голосов
/ 14 октября 2019

Я изучаю искусство тестирования и мне нужна ваша помощь. У меня есть функция (устаревший код), которую нужно протестировать, эта функция получает некоторые данные в качестве параметров, временно сохраняет их в базе данных и отправляет во внешний API. Если ответ API равен 200, тогда данные в таблице обновляются, чтобы сохранить их определенным образом, в противном случае они удаляются.

Это код:

public function sendAssignments(Challenge $challenge, int $reportType)
{
    $reportModel      = new Report($challenge, $reportType);
    $pointsList       = $this->calculateAssignments($challenge, $reportType);
    $pharmsToSend     = $pointsList['pharmsCollection'];
    $pointsArray      = $pointsList['pointsArray'];

    $savedAt          = Date('Y-m-d H:i:s');
    $symfonyService   = new SymfonyService();
    $requestUid       = \uniqid('AS-', false);

    $sentAt           = Date('Y-m-d H:i:s');
    $reportModel->persistReportAssignments($pharmsToSend, $savedAt, $sentAt, false);
    $symfonyResult    = $symfonyService->sendReportPoints(
        $requestUid, 
        $challenge->challenge_id, 
        $pointsArray
    );

    if ($symfonyResult->code == "200") {
        // Actualizamos los datos
        $reportModel->persistReportAssignments($pharmsToSend, $savedAt, $sentAt, true);
        FlashMessage::addSuccess('Se han asignado los puntos del reporte.', 'Asignación correcta');
    } else {
        $reportModel->deleteFailedAssignmentsFromDatabase($savedAt);
        FlashMessage::addError($symfonyResult->msg, 'Error '.$symfonyResult->code.' - No se han asignado los puntos');
    }

    if ($reportType == Report::REPORT_CONNECTED) {
        return redirect()->route('reports.showConnectedReport', ['challenge'=>$challenge]);
    } else if ($reportType == Report::REPORT_MANUAL) {
        return redirect()->route('reports.showManualReport', ['challenge'=>$challenge]);
    }
}

Я тестирую функцию, а невызов API, поэтому я хочу издеваться над классом SymfonyService, для этого я написал новый класс:

class SymfonyServiceMock
{
    public function sendReportPoints()
    {
        return ["operation"=>"ok", "code" => 200];
    }
}

Но проблема в том, что SymfonyService создается внутри функции, поэтому я не могу использовать класс SymfonyServiceMockсмоделировать вызов API. И вот мое сомнение, могу ли я провести рефакторинг этой функции, чтобы передать ей SymfonyService? Правильный ли это подход?

Я имею в виду такой рефакторинг, как этот:

public function sendAssignments(Challenge $challenge, int $reportType, SymfonyService $symfonyService)
{
    // The rest of the code here, but without instantiating SymfonyService
}

Если у вас есть еще одна лучшая идея о том, как это будет наилучшим способом ее тестирования (следуя принципам SOLID)конечно) Я был бы рад узнать.

Заранее большое спасибо

РЕДАКТИРОВАТЬ: У меня была эта идея, я зарегистрировал SymfonyService на AppServiceContainer, итеперь я внедряю SymfonyService в контроллер, чтобы я мог выполнить рефакторинг следующим образом:

public function sendAssignments(Challenge $challenge, int $reportType)
{
    $this->executeSendAssignmentsOperation($challenge, $reportType);

    if ($reportType == Report::REPORT_CONNECTED) {
        return redirect()->route('reports.showConnectedReport', ['challenge'=>$challenge]);
    } else if ($reportType == Report::REPORT_MANUAL) {
        return redirect()->route('reports.showManualReport', ['challenge'=>$challenge]);
    }
}

public function executeSendAssignmentsOperation(Challenge $challenge, int $reportType)
{
    $reportModel      = new Report($challenge, $reportType);
    $pointsList       = $this->calculateAssignments($challenge, $reportType);
    $pharmsToSend     = $pointsList['pharmsCollection'];
    $pointsArray      = $pointsList['pointsArray'];

    $savedAt          = Date('Y-m-d H:i:s');
    $sentAt           = Date('Y-m-d H:i:s');

    $reportModel->persistReportAssignments($pharmsToSend, $savedAt, $sentAt, false);
    $symfonyResult    = $this->_symfonyService->sendReportPoints(
        $challenge, 
        $pointsArray,
        $reportType
    );

    $sentAt           = Date('Y-m-d H:i:s');

    if ($symfonyResult->code == "200") {
        // Actualizamos los datos
        $reportModel->persistReportAssignments($pharmsToSend, $savedAt, $sentAt, true);
        FlashMessage::addSuccess('Se han asignado los puntos del reporte.', 'Asignación correcta');
    } else {
        $reportModel->deleteFailedAssignmentsFromDatabase($savedAt);
        FlashMessage::addError($symfonyResult->msg, 'Error '.$symfonyResult->code.' - No se han asignado los puntos');
    }
}

С этим рефактором теперь я могу написать тест. Но мне все еще интересно, является ли это правильным подходом к проблеме. Если нет, не могли бы вы объяснить, почему?

1 Ответ

0 голосов
/ 14 октября 2019

У меня та же проблема с PhpUnit. Невозможно смоделировать класс, созданный в тестируемой системе.

Единственное решение, которое сработало для меня, - это тестирование кода с помощью Kahlan (https://github.com/kahlan/kahlan), который позволяет имитировать классы на лету. Но это требует, чтобы классы были автозагрузчиками через композитор.

Может быть, это немного поможет:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...