如何从存根函数参数获取属性?

2023-12-05

我有一项服务,它应该创建一个电子邮件类对象并将其传递给第三类(电子邮件发送者)。

我想检查由该函数生成的电子邮件正文。

服务.php

class Service
{
    /** @var EmailService */
    protected $emailService;

    public function __construct(EmailService $emailService)
    {
        $this->emailService = $emailService;
    }

    public function testFunc()
    {
        $email = new Email();
        $email->setBody('abc'); // I want to test this attribute

        $this->emailService->send($email);
    }
}

电子邮件.php:

class Email
{
    protected $body;

    public function setBody($body)
    {
        $this->body = $body;
    }
    public function getBody()
    {
        return $this->body;
    }
}

电子邮件服务.php

interface EmailService
{
    public function send(Email $email);
}

因此,我为 emailService 和电子邮件创建了一个存根类。但我无法验证电子邮件的正文。我也无法检查 $email->setBody() 是否被调用,因为电子邮件是在测试函数内创建的

class ServiceSpec extends ObjectBehavior
{
    function it_creates_email_with_body_abc(EmailService $emailService, Email $email)
    {
        $this->beConstructedWith($emailService);

        $emailService->send($email);
        $email->getBody()->shouldBe('abc');
        $this->testFunc();
    }
}

我懂了:

Call to undefined method Prophecy\Prophecy\MethodProphecy::shouldBe() in /private/tmp/phpspec/spec/App/ServiceSpec.php on line 18 

在真实的应用程序中,正文是生成的,所以我想测试它是否正确生成。我怎样才能做到这一点?


在 PHPSpec 中,您不能对创建的对象进行这种断言(甚至在规范文件中创建它们时对存根或模拟进行此类断言):唯一可以匹配的是 SUS (System Under Spec)及其返回值(如果有)。

我会写一个小指南来帮助你通过测试and提高您的设计和可测试性


从我的角度来看有什么问题

new里面的用法Service

为什么是错误的

Service有两个责任:创建一个Email对象并完成其工作。这打破了 SRP坚实的原则。此外,您失去了对对象创建的控制,正如您所发现的,这变得非常难以测试

使规范通过的解决方法

我建议使用工厂(如下所示)来完成此类任务,因为可以显着提高可测试性,但在这种情况下,您可以通过重写规范来使测试通过,如下所示

class ServiceSpec extends ObjectBehavior
{
    function it_creates_email_with_body_abc(EmailService $emailService) 
    { 
        $this->beConstructedWith($emailService);

        //arrange data
        $email = new Email();
        $email->setBody('abc');

        //assert
        $emailService->send($email)->shouldBeCalled();

        //act
        $this->testFunc();
    }
}

只要setBodySUS 实现中没有改变,这有效。 不过我不会推荐它,因为从 PHPSpec 的角度来看这应该是一种味道。

使用工厂

创建工厂

class EmailFactory()
{
    public function createEmail($body)
    {
        $email = new Email();
        $email->setBody($body);

        return $email;
    }
}

及其规格

public function EmailFactorySpec extends ObjectBehavior
{
    function it_is_initializable()
    {
        $this->shouldHaveType(EmailFactory::class);
    }

    function it_creates_email_with_body_content()
    {
        $body = 'abc';
        $email = $this->createEmail($body);

        $email->shouldBeAnInstanceOf(Email::class);
        $email->getBody()->shouldBeEqualTo($body);
    }
}

现在你确定createEmail工厂的功能符合您的期望。正如你所注意到的,责任被封装在这里,你不需要在其他地方担心(考虑一个策略,你可以选择如何发送邮件:直接发送,将它们放入队列等等;如果你用原始方法处理它们,您需要在每个具体策略中测试电子邮件是否按照您的预期创建,而现在却没有)。

整合SUS工厂

class Service
{
    /** @var EmailService */
    protected $emailService;

    /** @var EmailFactory */
    protected $emailFactory;

    public function __construct(
      EmailService $emailService, EmailFactory $emailFactory
    ) {
        $this->emailService = $emailService;
        $this->emailFactory = $emailFactory;
    }

    public function testFunc()
    {
        $email = $this->emailFactory->createEmail('abc');
        $this->emailService->send($email);
    } 
}

最后使规范通过(正确的方法)

function it_creates_email_with_body_abc(
  EmailService $emailService, EmailFactory $emailFactory, Email $mail
) {
    $this->beConstructedWith($emailService);
    // if you need to be sure that body will be 'abc', 
    // otherwise you can use Argument::type('string') wildcard
    $emailFactory->createEmail('abc')->willReturn($email);
    $emailService->send($email)->shouldBeCalled();

    $this->testFunc();
}

我自己没有尝试过这个例子,可能会有一些拼写错误,但我 100% 确定这种方法:我希望所有读者都清楚。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何从存根函数参数获取属性? 的相关文章

随机推荐

  • Power Query:通过乘以另一列来转换列

    我想做类似的事情Power Query 根据另一列转换一列 但我陷入了如何修改语法以实现我的特定目标的困境 与链接的问题类似 假设我有下表 Table 1 Column A Column B Column C 1 4 7 2 5 8 3 6
  • 我可以在 Python 中多次等待同一个任务吗?

    我需要做很多工作 但幸运的是 很容易解耦到不同的任务中进行异步执行 其中一些是相互依赖的 我很清楚任务如何能够await其他多个人来获取他们的结果 但是 我不知道如何让多个不同的任务等待同一个协程 并且都得到结果 这文档据我所知也没有提到这
  • input 与 raw_input:Python 交互式 Shell 应用程序?

    我正在研究这个问题的答案 Python 交互式 Shell 类型应用程序 我的代码看起来像这样 def main while True s input gt if s hello print hi if s exit break if na
  • 将未知长度的切片中的值分配给 Go 中的结构体?

    我从github上的一些有用的包中找到了下面的案例 它看起来是如此丑陋和愚蠢 我认为更好的代码设计可以避免这种情况 但是如果遇到这种情况 是不是可以写得更简洁呢 有没有更好的方法来代替连续检查切片长度 package main type N
  • 真实设备产生过多日志

    我正在为 Android 开发一个简单的程序 我在真实设备 samsung young android 2 3 上进行了测试 我的程序会生成一些日志输出以用于调试目的 一切都很好 但我的设备开始在 Eclipse LogCat 窗口中生成大
  • hibernate中的cascade和inverse有什么区别,它们的用途是什么?

    如何在hibernate中使用级联和逆 定义它们的过程 标签是什么 它们彼此相关吗 它们有何用处 通过中间表建立多对多关系的情况 Cascade 表示是否将在子表中创建 更新记录 而 Inverse 表示是否会在中间表中创建 更新记录 例如
  • 如何使用非标准代码页读取 EBCDIC 数据,而不弄乱数字?

    这是给老手的 我正在从大型机 DB2 表中读取二进制转储 该表具有 varchar char smallint integer 和 float 列 有趣的是 DB2 使用代码页 424 希伯来语 我需要我的代码独立于代码页 因此 我使用 S
  • 如何在R中动态更改图表标题?

    这是一个使用的示例mtcars按变量分割成单独的图 我创建的是一个散点图vs and mpg通过将数据集拆分为cyl 首先创建一个空列表 然后我用了lapply循环遍历 cyl 4 6 8 的值 然后filter按该值的数据 之后 我绘制了
  • 如何在github上发布.php页面而不是.html来演示一些php内容?

    我正在看以下演示 http blueimp github com jQuery File Upload 据我在演示中的理解 php 文件是从 github 提供的 这意味着 php 内容可以从 github 提供 运行 我了解通过此链接从
  • iPhone - 为什么编译器在构建 ARM 架构时找不到某些包含内容?

    我正在尝试在 iPhone 项目中使用 C 库 我对 iPhone 开发非常陌生 我的图书馆 我已经奋斗了好几天 试图将此库构建成一个静态库 我可以将其用于模拟器 i386 和 ARM7 使用库的包含配置和 makefile 我可以毫无问题
  • 如何使用 Visual Studio 2015 为网站项目配置不同的虚拟目录

    我有一个在 VS 2012 中开发的 c 网站类型项目 我想使用 VS 2015 但无法指定与默认根目录 不同的虚拟目录 在 vs 2012 中 sln 文件中有以下几行 VWDPort 59903 VWDDynamicPort true
  • 使用正则表达式搜索元胞数组

    我经常发现自己尝试搜索元胞数组 就像我想使用 sql 查询搜索数据库一样 在这种情况下 我有许多军事基地 bases shp bases shaperead us military bases shp 然后我想过滤形状文件以获得空军基地 例
  • 使输入拇指比其他拇指更大

    这里的 React 开发人员试图通过编码来学习 这里我有一个滑块 我试图使拇指变大 但它不会比它所在的输入更大 正如你所看到的 如果我使拇指变大 它不会完全显示它 关于如何使它看起来像我想要的有什么建议吗 英语不是我的母语 所以可能会有错误
  • C#中如何获取变量的数据类型?

    如何找出某个变量保存的数据类型 例如 int string char 等 我现在有这样的事情 private static void Main var someone new Person Console WriteLine someone
  • 反应选择的默认值,当我想发布具有空白页面输出的数据时,复选框不起作用

    我无法将获取的数据作为默认值放入下拉列表 反应选择 和复选框中 我有显示的下拉菜单 好 但默认值dishId 1 is Medium 所以我应该看到在我的下拉列表中已经选择了Medium 但事实并非如此 评论的问题相同 export def
  • DllImport 与 LoadLibrary,最好的方法是什么?

    我通常在 c NET 中使用 Win32 API 但不要在一份申请中声明所有内容 有时通常使用 user32 有时使用 gdi32 我认为当我声明所有 api 函数时 它们会使用大量内存 在 NET 中使用 API 的最佳方式是什么 当您编
  • Cordova - 内部超链接始终在 Safari 中打开

    我对 Cordova 很陌生 所以我可能不完全理解它的用途 让我从我总体上想要实现的目标开始 我们有一个支持移动设备的 asp net 网站 我基本上只是想用 iPhone 应用程序来包装它 当然 该站点在 IIS 服务器上运行 因此我只需
  • Java中捕获异常的顺序

    如果我没记错的话 应该首先捕获异常的子类 但是必须捕获任何 RuntimeException 和一个具体的检查异常 这应该首先捕获 try catch RuntimeException e catch IOException e 这个顺序正
  • Java 8 混乱 -> String::compareToIgnoreCase

    有人可以帮助我理解以下内容吗 This works fine List list Arrays asList a b A B str sort String compareToIgnoreCase 我可以将上述方法引用分配给任何变量吗 ho
  • 如何从存根函数参数获取属性?

    我有一项服务 它应该创建一个电子邮件类对象并将其传递给第三类 电子邮件发送者 我想检查由该函数生成的电子邮件正文 服务 php class Service var EmailService protected emailService pu