在作为开发人员的旅程中,我们经常发现自己创建了需要将报告或页面导出到PDF格式的应用程序。长期以来,我们将各种库用于此任务,例如 mpdf , fpdf , wkhtmltopdf 等。但是,今天,以我的拙见,我们拥有市场上PDF一代最好的包装之一,即Browsershot。配置和生成PDF文件很简单。
但是,这是一些开发人员面临的问题:如何为使用浏览器的类编写测试?让我们深入研究。
想象一下,我们有一个名为GeneratePDF的类,该类以文件名,呈现的URL以及纸张大小作为参数。该课程将使我们的PDF保存到AWS S3。
!!示例写在Laravel应用程序中,并使用害虫进行自动测试。
<?php
declare(strict_types=1);
namespace App\Actions;
use Illuminate\Support\Facades\Storage;
use Spatie\Browsershot\Browsershot;
class GeneratePdf
{
public function handle(
string $fileName,
string $url,
string $paperSize = 'A4'
): string | false {
$path = '/exports/pdf/' . $fileName;
$content = Browsershot::url($url)
->format($paperSize)
->noSandbox()
->pdf();
if (!Storage::disk('s3')->put($path, $content)) {
return false;
}
return $path;
}
}
很棒!我们的操作将保存PDF并返回路径,以便我们可以在电子邮件中使用它,将其保存在数据库中,等等。该课程的唯一责任是生成PDF并返回路径。
但是现在,我们如何测试这个小家伙?
编写我们的测试
好吧,在此阶段,让我们编写一个简单的测试,看看一切是否按预期工作。
it('should generate a pdf', function () {
Storage::fake('s3');
$pdf = (new GeneratePdf())->handle(
fileName: 'my-file-name.pdf',
url: 'https://www.google.com'
);
Storage::disk('s3')
->assertExists($pdf);
});
但是,您可能会注意到我们的测试需要一段时间才能执行。但是为什么?
我们的测试花了一段时间,因为浏览器向 google.com 提出了获取其内容并为您创建PDF的请求。
好吧,这只是一个测试,这有什么危害?让我们思考:
- 如果使用浏览器的多个类以上的类别?
- 如果您没有互联网连接怎么办? 测试失败。
- 如果您使用的是付费管道服务怎么办? 测试将花费更长的时间,您将为此付出更多。
那么,我们如何更有效地编写测试?
使用 嘲弄
嘲弄
要模拟类的行为,我们可以使用Mockery
库,该库已在phpunit和害虫中可用。
此库提供了一个接口,我们可以在其中模仿或间谍我们的班级行为以对所谓的方法做出断言。
但是有一个问题(总会有),一个静态调用...
BrowserShot::url(...)
静态方法的问题
静态方法很棒,尤其是对于助手类,例如检查CPF(巴西社会安全号码)是否有效的方法。在这种情况下,由于我们无法访问$this
,因此我们可以使这些方法静止不动。
但是,这是有代价的...
编写静态方法的单元测试很简单。我们称这种方法并做出必要的断言,这样简单。但是,如果我需要模拟一个调用静态方法然后调用其非静态方法的类,该怎么办?
根据嘲弄documentation,它不支持嘲笑公共静态方法。为了解决这个问题,有一种黑客绕过这种行为,涉及创建一个别名。 (您可以阅读更多有关here的信息)。
it('should generate a pdf', function () {
Storage::fake('s3');
mock('alias:' . Browsershot::class)
->shouldReceive('url->format->noSandbox->pdf');
$pdf = (new GeneratePdf())->handle(
fileName: 'my-file-name.pdf',
url: 'https://www.google.com'
);
Storage::disk('s3')
->assertExists($pdf);
});
好吧,但是这有什么作用?当我们使用alias:
时,我们正在告诉作曲家:
“嘿,当我需要浏览器时,将其带到我这里,而不是原始班级。”
捕获是,即使嘲笑也不建议使用alias:
或overload:
。这可能导致类名称碰撞错误,应在单独的PHP进程中运行以避免这种情况。
所以,我的朋友,我该如何编写此测试?
实际上,让我们更改有关如何使用浏览器的方法:)
依赖分析和依赖注射
通过分析Browsershot::url
方法,我们可以发现它的作用,而且非常简单。
public static function url(string $url): static
{
return (new static())->setUrl($url);
}
很棒,为了避免使用alias:
或overload:
,我们可以简单地将浏览器注入我们的课程。现在,看起来像这样:
<?php
declare(strict_types=1);
namespace App\Actions;
use Illuminate\Support\Facades\Storage;
use Spatie\Browsershot\Browsershot;
class GeneratePdf
{
public function __construct(
private Browsershot $browsershot
) {
}
public function handle(
string $fileName,
string $url,
string $paperSize = 'A4'
): string | false {
$path = '/exports/pdf/' . $fileName;
$content = $this->browsershot->setUrl($url)
->format($paperSize)
->noSandbox()
->pdf();
if (!Storage::disk('s3')->put($path, $content)) {
return false;
}
return $path;
}
}
以这种方式,模拟变得更轻,效率得多:
it('should generate a pdf', function () {
Storage::fake('s3');
$mock = mock(Browsershot::class);
$mock->shouldReceive('setUrl->format->noSandbox->save');
$pdf = (new GeneratePdf($mock))->handle(
fileName: 'my-file-name.pdf',
url: 'https://www.google.com'
);
Storage::disk('s3')
->assertExists($pdf);
});
如果您使用的是Laravel,则可以使用$this->mock
方法,该方法直接与框架的容器进行交互。
我们的测试现在看起来像这样:
it('should generate a pdf', function () {
Storage::fake('s3');
Storage::put('pdf/my-file-name.pdf', 'my-fake-file-content');
$this->mock(Browsershot::class)
->shouldReceive('setUrl->format->noSandbox->save');
$pdf = app(GeneratePdf::class)->handle(
fileName: 'my-file-name.pdf',
url: 'https://www.google.com'
);
Storage::disk('s3')
->assertExists($pdf);
});
这样做,我们使我们的课程松散地结合在一起,使我们能够在没有太多麻烦的情况下进行广泛的测试,并且我们可以使用强大的模式,即依赖性注入。
直到下一次,伙计们。 ð§