配置和测试 Laravel 任务计划
Configure and test Laravel Task Scheduling
环境
Laravel 版本:5.1.45 (LTS)
PHP 版本:5.6.1
描述
我正在尝试使用 Laravel Task Scheduling 每 1 分钟 运行 一个命令。
尝试
我已将此行添加到我的 cron 选项卡文件
* * * * * php artisan schedule:run >> /dev/null 2>&1
这是我的/app/Console/Kernel.php
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
\App\Console\Commands\Inspire::class,
];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
$schedule->command('inspire')->hourly();
$schedule->command('echo "Happy New Year!" ')->everyMinute(); //<---- ADD HERE }
}
我添加了这一行$schedule->command('echo "Happy New Year!" ')->everyMinute();
问题
我该如何测试?
如何触发回显显示?
我怎么知道我所做的是否正确?
command()
运行 是一个 artisan 命令。您要实现的目标 - 向 OS 发出命令 - 由 exec('echo "Happy New Year!"')
完成
测试取决于你想测试什么:
- 调度程序(每分钟)是否在工作?
在这种情况下,您不必这样做。在原始框架代码中测试。
- 命令是否成功?
好吧,您可以手动 运行 php artisan schedule:run
并查看输出。
调度程序默认不产生任何输出 (>> /dev/null 2>&1
)。但是,您可以通过链接 writeOutputTo()
或 appendOutputTo()
(https://laravel.com/docs/5.1/scheduling#task-output).
将 运行ned 脚本的输出重定向到任何文件
对于更复杂的逻辑,请改为编写控制台命令 (https://laravel.com/docs/5.1/artisan#writing-commands) 并使用 command()
- 这样您就可以编写漂亮的、可测试的代码。
如果你想对事件的安排进行单元测试,你可以使用这个例子。它基于默认的启发命令:
public function testIsAvailableInTheScheduler()
{
/** @var \Illuminate\Console\Scheduling\Schedule $schedule */
$schedule = app()->make(\Illuminate\Console\Scheduling\Schedule::class);
$events = collect($schedule->events())->filter(function (\Illuminate\Console\Scheduling\Event $event) {
return stripos($event->command, 'YourCommandHere');
});
if ($events->count() == 0) {
$this->fail('No events found');
}
$events->each(function (\Illuminate\Console\Scheduling\Event $event) {
// This example is for hourly commands.
$this->assertEquals('0 * * * * *', $event->expression);
});
}
基于 Michiel 的回答,我使用了 Illuminate\Console\Scheduling\Event
中包含的方法来测试事件是否是由于给定日期的 运行。
我已经使用 Carbon::setTestNow()
模拟了当前日期,因此 when()
和 skip()
过滤器中任何基于日期的逻辑都将按预期运行。
use Tests\TestCase;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Console\Scheduling\Event;
use Cron\CronExpression;
use Carbon\Carbon;
class ScheduleTest extends TestCase {
public function testCompanyFeedbackSchedule()
{
$event = $this->getCommandEvent('your-command-signature');
$test_date = Carbon::now()->startOfDay()->addHours(8);
for ($i=0; $i < 365; $i++) {
$test_date->addDay();
Carbon::setTestNow($test_date);
// Run the when() & skip() filters
$filters_pass = $event->filtersPass($this->app);
// Test that the Cron expression passes
$date_passes = $this->isEventDue($event);
$will_run = $filters_pass && $date_passes;
// Should only run on first friday of month
if ($test_date->format('l') === 'Friday' && $test_date->weekOfMonth === 1) {
$this->assertTrue($will_run, 'Task should run on '. $test_date->toDateTimeString());
} else {
$this->assertFalse($will_run, 'Task should not run on '. $test_date->toDateTimeString());
}
}
}
/**
* Get the event matching the given command signature from the scheduler
*
* @param string $command_signature
*
* @return Illuminate\Console\Scheduling\Event
*/
private function getCommandEvent($command_signature)
{
$schedule = app()->make(Schedule::class);
$event = collect($schedule->events())->first(function (Event $event) use ($command_signature) {
return stripos($event->command, $command_signature);
});
if (!$event) {
$this->fail('Event for '. $command_signature .' not found');
}
return $event;
}
/**
* Determine if the Cron expression passes.
*
* Copied from the protected method Illuminate\Console\Scheduling\Event@isEventDue
*
* @return bool
*/
private function isEventDue(Event $event)
{
$date = Carbon::now();
if ($event->timezone) {
$date->setTimezone($event->timezone);
}
return CronExpression::factory($event->expression)->isDue($date->toDateTimeString());
}
}
也是在 的基础上,至少在 L8 中是有区别的。
代替$event->command
:
$events = collect($schedule->events())->filter(function (Event $event) {
return stripos($event->command, 'YourCommandHere');
});
需要$event->description
:
$events = collect($schedule->events())->filter(function (Event $event) {
return stripos($event->description, 'YourCommandHere');
});
我在研究 Tinker 时发现了这个:
>>> app()->make(\Illuminate\Console\Scheduling\Schedule::class)->events();
=> [
Illuminate\Console\Scheduling\CallbackEvent {#3496
+command: null,
+expression: "* * * * *",
+timezone: "UTC",
+user: null,
+environments: [],
+evenInMaintenanceMode: false,
+withoutOverlapping: false,
+onOneServer: false,
+expiresAt: 1440,
+runInBackground: false,
+output: "/dev/null",
+shouldAppendOutput: false,
+description: "App\Jobs\GenerateSuggestion",
+mutex: Illuminate\Console\Scheduling\CacheEventMutex {#3498
+cache: Illuminate\Cache\CacheManager {#282},
+store: null,
},
+exitCode: null,
},
]
我在 Laravel 8 中通过以下测试很幸运:
public function testRunsAt930()
{
Event::fake();
$this->travelTo(now()->startOfWeek()->setHour(9)->setMinute(30));
$this->artisan('schedule:run');
Event::assertDispatched(ScheduledTaskFinished::class, function ($event) {
return strpos($event->task->command, 'your-command-name') !== false;
});
}
调度程序在成功 运行 调度时触发 Illuminate\Console\Events\ScheduledTaskFinished
事件,因此您可以通过模拟您的时间来确定您的调度是否会在特定时间 运行实际上 运行 安排时间表,然后您只需收听该事件。
如果您不使用 L8,您可以使用类似 Carbonite 的时间模拟,尽管我认为其余的应该可以,尽管您可能需要在早期版本中使用 Artisan facade。
要查明时间表 运行 是否如上例所示,只需使用 Event::assertDispatched()
。虽然如果你想知道它是否没有 运行 你可以只使用 Event::assertNotDispatched()
作为 ScheduledTaskFinished::class
或者听 ScheduledTaskFailed::class
代替。
我知道这个问题已经得到解答,但我发现如果您使用的是 L8,这种方法可能会更简洁一些,甚至可能适用于更早的版本。
环境
Laravel 版本:5.1.45 (LTS)
PHP 版本:5.6.1
描述
我正在尝试使用 Laravel Task Scheduling 每 1 分钟 运行 一个命令。
尝试
我已将此行添加到我的 cron 选项卡文件
* * * * * php artisan schedule:run >> /dev/null 2>&1
这是我的/app/Console/Kernel.php
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
\App\Console\Commands\Inspire::class,
];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
$schedule->command('inspire')->hourly();
$schedule->command('echo "Happy New Year!" ')->everyMinute(); //<---- ADD HERE }
}
我添加了这一行$schedule->command('echo "Happy New Year!" ')->everyMinute();
问题
我该如何测试?
如何触发回显显示?
我怎么知道我所做的是否正确?
command()
运行 是一个 artisan 命令。您要实现的目标 - 向 OS 发出命令 - 由 exec('echo "Happy New Year!"')
测试取决于你想测试什么:
- 调度程序(每分钟)是否在工作?
在这种情况下,您不必这样做。在原始框架代码中测试。
- 命令是否成功?
好吧,您可以手动 运行 php artisan schedule:run
并查看输出。
调度程序默认不产生任何输出 (>> /dev/null 2>&1
)。但是,您可以通过链接 writeOutputTo()
或 appendOutputTo()
(https://laravel.com/docs/5.1/scheduling#task-output).
对于更复杂的逻辑,请改为编写控制台命令 (https://laravel.com/docs/5.1/artisan#writing-commands) 并使用 command()
- 这样您就可以编写漂亮的、可测试的代码。
如果你想对事件的安排进行单元测试,你可以使用这个例子。它基于默认的启发命令:
public function testIsAvailableInTheScheduler()
{
/** @var \Illuminate\Console\Scheduling\Schedule $schedule */
$schedule = app()->make(\Illuminate\Console\Scheduling\Schedule::class);
$events = collect($schedule->events())->filter(function (\Illuminate\Console\Scheduling\Event $event) {
return stripos($event->command, 'YourCommandHere');
});
if ($events->count() == 0) {
$this->fail('No events found');
}
$events->each(function (\Illuminate\Console\Scheduling\Event $event) {
// This example is for hourly commands.
$this->assertEquals('0 * * * * *', $event->expression);
});
}
基于 Michiel 的回答,我使用了 Illuminate\Console\Scheduling\Event
中包含的方法来测试事件是否是由于给定日期的 运行。
我已经使用 Carbon::setTestNow()
模拟了当前日期,因此 when()
和 skip()
过滤器中任何基于日期的逻辑都将按预期运行。
use Tests\TestCase;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Console\Scheduling\Event;
use Cron\CronExpression;
use Carbon\Carbon;
class ScheduleTest extends TestCase {
public function testCompanyFeedbackSchedule()
{
$event = $this->getCommandEvent('your-command-signature');
$test_date = Carbon::now()->startOfDay()->addHours(8);
for ($i=0; $i < 365; $i++) {
$test_date->addDay();
Carbon::setTestNow($test_date);
// Run the when() & skip() filters
$filters_pass = $event->filtersPass($this->app);
// Test that the Cron expression passes
$date_passes = $this->isEventDue($event);
$will_run = $filters_pass && $date_passes;
// Should only run on first friday of month
if ($test_date->format('l') === 'Friday' && $test_date->weekOfMonth === 1) {
$this->assertTrue($will_run, 'Task should run on '. $test_date->toDateTimeString());
} else {
$this->assertFalse($will_run, 'Task should not run on '. $test_date->toDateTimeString());
}
}
}
/**
* Get the event matching the given command signature from the scheduler
*
* @param string $command_signature
*
* @return Illuminate\Console\Scheduling\Event
*/
private function getCommandEvent($command_signature)
{
$schedule = app()->make(Schedule::class);
$event = collect($schedule->events())->first(function (Event $event) use ($command_signature) {
return stripos($event->command, $command_signature);
});
if (!$event) {
$this->fail('Event for '. $command_signature .' not found');
}
return $event;
}
/**
* Determine if the Cron expression passes.
*
* Copied from the protected method Illuminate\Console\Scheduling\Event@isEventDue
*
* @return bool
*/
private function isEventDue(Event $event)
{
$date = Carbon::now();
if ($event->timezone) {
$date->setTimezone($event->timezone);
}
return CronExpression::factory($event->expression)->isDue($date->toDateTimeString());
}
}
也是在
代替$event->command
:
$events = collect($schedule->events())->filter(function (Event $event) {
return stripos($event->command, 'YourCommandHere');
});
需要$event->description
:
$events = collect($schedule->events())->filter(function (Event $event) {
return stripos($event->description, 'YourCommandHere');
});
我在研究 Tinker 时发现了这个:
>>> app()->make(\Illuminate\Console\Scheduling\Schedule::class)->events();
=> [
Illuminate\Console\Scheduling\CallbackEvent {#3496
+command: null,
+expression: "* * * * *",
+timezone: "UTC",
+user: null,
+environments: [],
+evenInMaintenanceMode: false,
+withoutOverlapping: false,
+onOneServer: false,
+expiresAt: 1440,
+runInBackground: false,
+output: "/dev/null",
+shouldAppendOutput: false,
+description: "App\Jobs\GenerateSuggestion",
+mutex: Illuminate\Console\Scheduling\CacheEventMutex {#3498
+cache: Illuminate\Cache\CacheManager {#282},
+store: null,
},
+exitCode: null,
},
]
我在 Laravel 8 中通过以下测试很幸运:
public function testRunsAt930()
{
Event::fake();
$this->travelTo(now()->startOfWeek()->setHour(9)->setMinute(30));
$this->artisan('schedule:run');
Event::assertDispatched(ScheduledTaskFinished::class, function ($event) {
return strpos($event->task->command, 'your-command-name') !== false;
});
}
调度程序在成功 运行 调度时触发 Illuminate\Console\Events\ScheduledTaskFinished
事件,因此您可以通过模拟您的时间来确定您的调度是否会在特定时间 运行实际上 运行 安排时间表,然后您只需收听该事件。
如果您不使用 L8,您可以使用类似 Carbonite 的时间模拟,尽管我认为其余的应该可以,尽管您可能需要在早期版本中使用 Artisan facade。
要查明时间表 运行 是否如上例所示,只需使用 Event::assertDispatched()
。虽然如果你想知道它是否没有 运行 你可以只使用 Event::assertNotDispatched()
作为 ScheduledTaskFinished::class
或者听 ScheduledTaskFailed::class
代替。
我知道这个问题已经得到解答,但我发现如果您使用的是 L8,这种方法可能会更简洁一些,甚至可能适用于更早的版本。