Laravel Eloquent 模型单元测试
Laravel Eloquent Model Unit testing
我正在尝试编写一个测试用例来测试 Laravel 4.2
中两个 Eloquent 模型之间关系的关联和分离
这是我的测试用例:
class BookingStatusSchemaTest extends TestCase
{
private $statusText = "Confirmed";
private $bookingStub;
private $statusStub;
public function testMigrateService()
{
$this->createTestData();
$booking = $this->bookingStub;
$status = $this->statusStub;
/**
* Check that the booking has no status. OK
*/
$this->assertNull($booking->status);
/**
* Check that status has no booking. OK
*/
$this->assertEquals(count($status->bookings), 0);
/**
* Add a status to the booking. OK
*/
$booking->status()->associate($this->statusStub);
/**
* Check that status has a booking. NOT OK - This gives error
*/
$this->assertEquals(count($status->bookings), 1);
/**
* Check that the booking has a status. OK
*/
$this->assertNotNull($booking->status);
/**
* Do NOT delete the status, just set the reference
* to it to null.
*/
$booking->status = null;
/**
* And check again. OK
*/
$this->assertNull($booking->status);
}
private function createTestData()
{
$bookingStatus = BookingStatus::create([
'status' => $this->statusText
]);
$booking = Booking::create([ ]);
$this->bookingStub = $booking;
$this->statusStub = $bookingStatus;
}
}
当我执行它时,我得到:
There was 1 failure:
1) BookingStatusSchemaTest::testMigrateService
Failed asserting that 1 matches expected 0.
预订模式:
class Booking extends Eloquent {
/**
* A booking have a status
*/
public function status()
{
return $this->belongsTo('BookingStatus');
}
}
预订状态模型:
class BookingStatus extends Eloquent
{
protected $table = 'booking_statuses';
protected $guarded = [ 'id' ];
protected $fillable = ['status'];
/**
* A booking status belongs to a booking
*/
public function bookings()
{
return $this->hasMany('Booking');
}
}
这是 bookingstatus 的迁移架构:
Schema::create('booking_statuses', function(Blueprint $table)
{
$table->increments('id');
$table->string('status');
$table->timestamps();
});
这里是预订:
Schema::create('bookings', function(Blueprint $table)
{
$table->increments('id');
$table->unsignedInteger('booking_status_id')->nullable();
$table->timestamps();
});
我必须添加/更改什么才能验证我的测试用例中的关系?
It's been a while and I had totally forgotten about this question.
Since OP still sems interested in it, I'll try to answer the question
in some way.
所以我假设实际任务是:如何测试两个 Eloquent 模型之间的正确关系?
我认为是 Adam Wathan 首先建议放弃像 "Unit Tests" 和 "Functional Tests" 和 "I-have-no-idea-what-this-means Tests" 这样的术语,而只是将测试分成两个 concerns/concepts:特征和单元,其中 Features 简单地描述了应用程序的功能,例如 "A logged in user can book a flight ticket",而 Units 描述了它的较低级别的单元及其公开的功能,例如 "A booking has a status".
我非常喜欢这种方法,考虑到这一点,我想重构您的测试:
class BookingStatusSchemaTest extends TestCase
{
/** @test */
public function a_booking_has_a_status()
{
// Create the world: there is a booking with an associated status
$bookingStatus = BookingStatus::create(['status' => 'confirmed']);
$booking = Booking::create(['booking_status_id' => $bookingStatus->id]);
// Act: get the status of a booking
$actualStatus = $booking->status;
// Assert: Is the status I got the one I expected to get?
$this->assertEquals($actualStatus->id, $bookingStatus->id);
}
/** @test */
public function the_status_of_a_booking_can_be_revoked()
{
// Create the world: there is a booking with an associated status
$bookingStatus = BookingStatus::create(['status' => 'confirmed']);
$booking = Booking::create(['booking_status_id' => $bookingStatus->id]);
// Act: Revoke the status of a booking, e.g. set it to null
$booking->revokeStatus();
// Assert: The Status should be null now
$this->assertNull($booking->status);
}
}
此代码未经测试!
请注意函数名称读起来像是对预订及其功能的描述。您并不真正关心实现,您不必知道 Booking 在哪里或如何获得其 BookingStatus - 您只是想确保如果有带有 BookingStatus 的 Booking,您可以获得该 BookingStatus。或者撤销它。或者也许改变它。或者做任何事情。您的测试显示了您希望如何与该单元交互。所以写测试然后努力让它通过。
您测试中的主要缺陷可能是您有点 "afraid" 会发生一些魔法。相反,将您的模型视为普通的旧 PHP 对象 - 因为它们就是这样!而且你不会 运行 在 POPO 上进行这样的测试:
/**
* Do NOT delete the status, just set the reference
* to it to null.
*/
$booking->status = null;
/**
* And check again. OK
*/
$this->assertNull($booking->status);
这是一个非常广泛的话题,关于它的每一个陈述都不可避免地带有偏见。有一些准则可以帮助您相处,例如 "only test your own code",但要将所有和平放在一起真的很难。幸运的是,前面提到的 Adam Wathan 有一个非常出色的视频课程,名为“Test Driven Laravel”,他在其中试驾了整个真实世界的 Laravel 应用程序。它可能有点贵,但它值得每一分钱,并且比 Whosebug 上的一些随机家伙更能帮助您理解测试方式 :)
要测试您是否设置了正确的 Eloquent 关系,您必须 运行 断言关系 class ($model->relation()
)。
你可以断言
- 通过断言
$model->relation()
是 HasMany
、BelongsTo
、HasManyThrough
...等的实例,这是正确的关系类型
- 使用
$model->relation()->getRelated()
与正确的模型相关
- 它使用正确的外键
$model->relation()->getForeignKey()
- 外键通过使用
Schema::getColumListing($table)
作为列存在于 table 中(这里,如果是 HasMany
关系,$table
要么是 $model->relation()->getRelated()->getTable()
或者 $model->relation()->getParent()->getTable()
如果是 BelongsTo
关系)
例如。假设你有一个 Parent
和一个 Child
模型,其中一个 Parent
通过 children()
方法使用 parent_id
作为外来的有许多 Child
钥匙。 Parent
映射 parents
table 和 Child
映射 children
table.
$parent = new Parent;
# App\Parent
$parent->children()
# Illuminate\Database\Eloquent\Relations\HasMany
$parent->children()->getRelated()
# App\Child
$parent->children()->getForeignKey()
# 'parent_id'
$parent->children()->getRelated()->getTable()
# 'children'
Schema::getColumnListing($parent->children()->getRelated()->getTable())
# ['id', 'parent_id', 'col1', 'col2', ...]
编辑
此外,这不会触及数据库,因为我们从不保存任何东西。但是,需要迁移数据库,否则模型将不会与任何 table 关联。
我正在尝试编写一个测试用例来测试 Laravel 4.2
中两个 Eloquent 模型之间关系的关联和分离这是我的测试用例:
class BookingStatusSchemaTest extends TestCase
{
private $statusText = "Confirmed";
private $bookingStub;
private $statusStub;
public function testMigrateService()
{
$this->createTestData();
$booking = $this->bookingStub;
$status = $this->statusStub;
/**
* Check that the booking has no status. OK
*/
$this->assertNull($booking->status);
/**
* Check that status has no booking. OK
*/
$this->assertEquals(count($status->bookings), 0);
/**
* Add a status to the booking. OK
*/
$booking->status()->associate($this->statusStub);
/**
* Check that status has a booking. NOT OK - This gives error
*/
$this->assertEquals(count($status->bookings), 1);
/**
* Check that the booking has a status. OK
*/
$this->assertNotNull($booking->status);
/**
* Do NOT delete the status, just set the reference
* to it to null.
*/
$booking->status = null;
/**
* And check again. OK
*/
$this->assertNull($booking->status);
}
private function createTestData()
{
$bookingStatus = BookingStatus::create([
'status' => $this->statusText
]);
$booking = Booking::create([ ]);
$this->bookingStub = $booking;
$this->statusStub = $bookingStatus;
}
}
当我执行它时,我得到:
There was 1 failure:
1) BookingStatusSchemaTest::testMigrateService
Failed asserting that 1 matches expected 0.
预订模式:
class Booking extends Eloquent {
/**
* A booking have a status
*/
public function status()
{
return $this->belongsTo('BookingStatus');
}
}
预订状态模型:
class BookingStatus extends Eloquent
{
protected $table = 'booking_statuses';
protected $guarded = [ 'id' ];
protected $fillable = ['status'];
/**
* A booking status belongs to a booking
*/
public function bookings()
{
return $this->hasMany('Booking');
}
}
这是 bookingstatus 的迁移架构:
Schema::create('booking_statuses', function(Blueprint $table)
{
$table->increments('id');
$table->string('status');
$table->timestamps();
});
这里是预订:
Schema::create('bookings', function(Blueprint $table)
{
$table->increments('id');
$table->unsignedInteger('booking_status_id')->nullable();
$table->timestamps();
});
我必须添加/更改什么才能验证我的测试用例中的关系?
It's been a while and I had totally forgotten about this question. Since OP still sems interested in it, I'll try to answer the question in some way.
所以我假设实际任务是:如何测试两个 Eloquent 模型之间的正确关系?
我认为是 Adam Wathan 首先建议放弃像 "Unit Tests" 和 "Functional Tests" 和 "I-have-no-idea-what-this-means Tests" 这样的术语,而只是将测试分成两个 concerns/concepts:特征和单元,其中 Features 简单地描述了应用程序的功能,例如 "A logged in user can book a flight ticket",而 Units 描述了它的较低级别的单元及其公开的功能,例如 "A booking has a status".
我非常喜欢这种方法,考虑到这一点,我想重构您的测试:
class BookingStatusSchemaTest extends TestCase
{
/** @test */
public function a_booking_has_a_status()
{
// Create the world: there is a booking with an associated status
$bookingStatus = BookingStatus::create(['status' => 'confirmed']);
$booking = Booking::create(['booking_status_id' => $bookingStatus->id]);
// Act: get the status of a booking
$actualStatus = $booking->status;
// Assert: Is the status I got the one I expected to get?
$this->assertEquals($actualStatus->id, $bookingStatus->id);
}
/** @test */
public function the_status_of_a_booking_can_be_revoked()
{
// Create the world: there is a booking with an associated status
$bookingStatus = BookingStatus::create(['status' => 'confirmed']);
$booking = Booking::create(['booking_status_id' => $bookingStatus->id]);
// Act: Revoke the status of a booking, e.g. set it to null
$booking->revokeStatus();
// Assert: The Status should be null now
$this->assertNull($booking->status);
}
}
此代码未经测试!
请注意函数名称读起来像是对预订及其功能的描述。您并不真正关心实现,您不必知道 Booking 在哪里或如何获得其 BookingStatus - 您只是想确保如果有带有 BookingStatus 的 Booking,您可以获得该 BookingStatus。或者撤销它。或者也许改变它。或者做任何事情。您的测试显示了您希望如何与该单元交互。所以写测试然后努力让它通过。
您测试中的主要缺陷可能是您有点 "afraid" 会发生一些魔法。相反,将您的模型视为普通的旧 PHP 对象 - 因为它们就是这样!而且你不会 运行 在 POPO 上进行这样的测试:
/**
* Do NOT delete the status, just set the reference
* to it to null.
*/
$booking->status = null;
/**
* And check again. OK
*/
$this->assertNull($booking->status);
这是一个非常广泛的话题,关于它的每一个陈述都不可避免地带有偏见。有一些准则可以帮助您相处,例如 "only test your own code",但要将所有和平放在一起真的很难。幸运的是,前面提到的 Adam Wathan 有一个非常出色的视频课程,名为“Test Driven Laravel”,他在其中试驾了整个真实世界的 Laravel 应用程序。它可能有点贵,但它值得每一分钱,并且比 Whosebug 上的一些随机家伙更能帮助您理解测试方式 :)
要测试您是否设置了正确的 Eloquent 关系,您必须 运行 断言关系 class ($model->relation()
)。
你可以断言
- 通过断言
$model->relation()
是HasMany
、BelongsTo
、HasManyThrough
...等的实例,这是正确的关系类型 - 使用
$model->relation()->getRelated()
与正确的模型相关
- 它使用正确的外键
$model->relation()->getForeignKey()
- 外键通过使用
Schema::getColumListing($table)
作为列存在于 table 中(这里,如果是HasMany
关系,$table
要么是$model->relation()->getRelated()->getTable()
或者$model->relation()->getParent()->getTable()
如果是BelongsTo
关系)
例如。假设你有一个 Parent
和一个 Child
模型,其中一个 Parent
通过 children()
方法使用 parent_id
作为外来的有许多 Child
钥匙。 Parent
映射 parents
table 和 Child
映射 children
table.
$parent = new Parent;
# App\Parent
$parent->children()
# Illuminate\Database\Eloquent\Relations\HasMany
$parent->children()->getRelated()
# App\Child
$parent->children()->getForeignKey()
# 'parent_id'
$parent->children()->getRelated()->getTable()
# 'children'
Schema::getColumnListing($parent->children()->getRelated()->getTable())
# ['id', 'parent_id', 'col1', 'col2', ...]
编辑 此外,这不会触及数据库,因为我们从不保存任何东西。但是,需要迁移数据库,否则模型将不会与任何 table 关联。