模拟创建新对象的继承类或如何消除我的类的气味

问题描述 投票:0回答:1

抱歉标题混乱,我正在尝试将单元测试添加到我的(可能)臭代码库中。 我有类似以下课程的内容:

namespace App\Service;

use App\Messages\ItemMessage;
use App\Messages\LocationMessage;
use App\Messages\Message;
use App\Settings\Settings;

class MassMessenger
{
    protected static function sendMessagesForDay (int $day, string $content, string $title, Message $messageType): void
    {
        $posts = \App\Repository\Post::getPostForDay($day);
        foreach ($posts as $post) {
            $message = new $messageType($post->getId(), $content, $title);
            $message->triggerMail();
        }
    }

    public static function sendItem (int $day, Settings $settings): void
    {
        $messageType = new ItemMessage(0, '', '');
        self::sendMessagesForDay($day, $settings->getEmailContent(), $settings->getEmailTitle(), $messageType );
    }

    public static function sendLocation (int $day, Settings $settings): void
    {
        $messageType = new LocationMessage(0, '', '');
        self::sendMessagesForDay($day, $settings->getEmailContent(), $settings->getEmailTitle(), $messageType );
    }
}

Message类是LocationMessage和ItemMessage继承的抽象类。 我想测试公共方法,我不太关心模拟存储库,所以它不是一个完全干净的单元测试。不过,我也不想创建一个集成测试,在其中询问 PHPMailer 消息是否已通过。

所以现在我对如何设计这些测试感到困惑,我尝试了一下:


<?php

namespace Tests\Service;

use App\Messages\ItemMessage;
use App\Service\MassMessenger;
use App\Settings\Settings;
use PHPUnit\Framework\TestCase;

class MassMessengerTest extends TestCase
{
    public function testSendItem()
    {
        $day = 1;
        $settings = $this->createMock(Settings::class);
        $settings->method('getEmailContent')->willReturn('Test Content');
        $settings->method('getEmailTitle')->willReturn('Test Title');

        $messageMock = \Mockery::mock( ItemMessage::class);
        $messageMock->shouldReceive('triggerMail')->once();
        MassMessenger::sendItem($day, $settings);

        // The test will fail if triggerMail is not called
    }


    protected function tearDown(): void
    {
        parent::tearDown();
        \Mockery::close();
    }
}

它(当然)没有检测到模拟,可能是因为我没有用它做任何事情

Mockery\Exception\InvalidCountException : Method triggerMail(<Any Arguments>) from Mockery_0_App_Messages_ItemMessage should be called 

当我重载该类(例如模拟硬依赖项)时,我收到此错误:

TypeError : App\Service\MassMessenger::sendMessagesForDay(): Argument #4 ($messageType) must be of type App\Messages\Message, App\Messages\ItemMessage given, called in /xxx/src/Service/MassMessenger.php on line 24 

此外,我很愿意听到任何关于如何消除代码异味的建议,我仍在学习软件设计模式,但还没有找到正确的模式来创建在这里可以很好测试的代码。

我还尝试通过模拟创建的消息依赖项来直接测试 sendMessagesForDay 函数,该消息依赖项独立工作但与其他测试一起工作,但失败了

Mockery\Exception\RuntimeException: Could not load mock App\Messages\Message, class already exists

并添加

* @runInSeparateProcess
* @preserveGlobalState disabled

只是让测试悄然失败。我认为我遇到这些问题是因为 sendMessage 创建了模拟对象的新实例。由于测试这个已经很难了,我想我最好重构该类以使其更好地可测试。

任何建议都非常感谢,谢谢!

php unit-testing design-patterns phpunit mockery
1个回答
0
投票

为了“消除异味”并使类单元可测试,您应该对所需的类使用依赖注入,而不是在它们上调用静态方法。 我不知道在您的应用程序中创建

MassMessenger
类时注入依赖项是否更有意义,或者将它们注入到每个静态方法中是否更有意义。

第一个示例可能看起来像(未经测试)

class MassMessenger
{
    protected $post;
    protected $itemMessage;
    protected $locationMessage;
    public function __construct($post, $itemMessage, $locationMessage = null)
    {
        $this->post = $post;
        $this->itemMessage = $itemMessage;
        $this->locationMessage = $locationMessage;
    }
 
    protected function sendMessagesForDay (int $day, string $content, string $title, Message $message): void
    {
        $posts = $post->getPostForDay($day);
        foreach ($posts as $post) {
            $message->triggerMail($post->getId(), $content, $title);
        }
    }

    public function sendItem (int $day, Settings $settings): void
    {
        $this->sendMessagesForDay($day, $settings->getEmailContent(), $settings->getEmailTitle(), $this->itemMessage );
    }

    public function sendLocation (int $day, Settings $settings): void
    {
        $this->sendMessagesForDay($day, $settings->getEmailContent(), $settings->getEmailTitle(), $this->locationMessage );
    }
}

那么你的测试可能看起来像这样

class MassMessengerTest extends TestCase
{
    public function testSendItem()
    {
        $day = 1;
        $settings = $this->createMock(Settings::class);
        $settings->method('getEmailContent')->willReturn('Test Content');
        $settings->method('getEmailTitle')->willReturn('Test Title');

        $messageMock = \Mockery::mock( ItemMessage::class);
        $messageMock->shouldReceive('triggerMail')->once();
        $messenger = new MassMessenger($post, $messageMock);
        $messenger->sendItem($day, $settings);

        // The test will fail if triggerMail is not called
    }


    protected function tearDown(): void
    {
        parent::tearDown();
        \Mockery::close();
    }
}

一般情况

  • 在函数中调用静态方法会使它们更难测试
  • 在函数中实例化对象使它们更难测试
© www.soinside.com 2019 - 2024. All rights reserved.