На самом деле отличная тема, я распишу подробно, потому что многие тупо не понимают нужно ли тестировать сущности, как их тестировать и когда.
Тестировать класс в том виде, что он есть у тебя бессмысленно: тут нет логики. Ну, типа ты тестируешь, что PHP вызовет метод и что выполнится тривиальное условие.
Но я могу показать, как примерно выглядит тест, если появится какая-то логика и кое-что поменять.
Тебя интересует соблюдение инвариантов. OK, скачиваешь
эту библиотеку и пишешь:
PHP:
final class Human
{
public function __construct(int $id, string $name, int $age)
{
Assertion::regex($name, '/^[\p{L}-]{2,}$/', sprintf('Username is not valid: %s', $name));
Assertion::greaterOrEqThan($age, 18);
$this->id = $id;
$this->name = $name;
$this->age = $age;
}
}
Далее, возможно, для твоей модели действительно подходит именно возраст и он не предполагает изменения в течение времени, но в 99% случаев возраст должен меняться:
PHP:
final class Human
{
public function __construct(int $id, string $name, DateTime $dateOfBirth, DateTime $now)
{
Assertion::regex($name, '/^[\p{L}-]{2,}$/', sprintf('Username is not valid: %s', $name));
Assertion::greaterOrEqThan($now->diff($dateOfBirth)->y, 18);
$this->id = $id;
$this->name = $name;
$this->dateOfBirth = $dateOfBirth;
}
}
Далее, ты хочешь иметь возможность менять имя и ДР. Тут на самом деле отмечу, но ДР многие сайты менять не позволяют, потому что в реальности он меняться никак не может. Может, конечно, корректироваться, вероятно даже можно при наличии доказательств поменять дату в паспорте, но обычно такой кейс просто не учитывают. А вот имя человек может запросто поменять. Поэтому я для примера буду считать, что ДР мы менять не можем (даже как админы; в самых исключительных случаях это может сделать программист прямо в БД и такое будет, допустим, раз в 5 лет).
Позволяем менять имя:
PHP:
final class Human
{
public function __construct(int $id, string $name, DateTime $dateOfBirth, DateTime $now)
{
Assertion::regex($name, '/^[\p{L}-]{2,}$/', sprintf('Username is not valid: %s', $name));
Assertion::greaterOrEqThan($now->diff($dateOfBirth)->y, 18);
$this->id = $id;
$this->name = $name;
$this->dateOfBirth = $dateOfBirth;
}
public function rename(string $newName): void
{
Assertion::regex($name, '/^[\p{L}-]{2,}$/', sprintf('Username is not valid: %s', $name));
$this->name = $name;
}
}
И тут мы видим дублирование инварианта. В принципе, это не страшно, но если мы не поленимся, но выделим «имя» в отдельный VO:
PHP:
final class Name
{
private $name;
public function __construct(string $name)
{
Assertion::regex($name, '/^[\p{L}-]{2,}$/', sprintf('Username is not valid: %s', $name));
$this->name = $name;
}
public function __toString()
{
return $this->name;
}
}
final class Human
{
public function __construct(int $id, Name $name, DateTime $dateOfBirth, DateTime $now)
{
Assertion::greaterOrEqThan($now->diff($dateOfBirth)->y, 18);
$this->id = $id;
$this->name = $name;
$this->dateOfBirth = $dateOfBirth;
}
public function rename(Name $newName): void
{
$this->name = $newName;
}
}
OK, но тестировать по-прежнему нечего: ведь мы по сути будем тестировать сам PHP и библиотеку beberlei/assert.
Чтобы пример с тестированием имел смысл, добавим немного логики а-ля ВКонтакте: нельзя менять имя чаще, чем раз в 24 часа + имя должно быть подтверждено модератором (заодно поменяю Human -> VkUser):
PHP:
final class VkUser
{
public function rename(Name $newName, DateTime $now): void
{
if ($this->name->equals($newName)) {
return;
}
if ($now->getDiffInHours($this->lastTimeNameChangedAt) < 24) {
throw new NameCannotBeChangedFrequently();
}
$this->nameOnModeration = $newName;
}
public function acceptName(DateTime $now): void
{
if ($this->nameOnModeration === null) {
return;
}
$this->name = $this->nameOnModeration;
$this->lastTimeNameChangedAt = $now;
$this->nameOnModeration = null;
}
}
Соответственно, команду «VkUser->rename()» может вызывать сам пользователь, а «VkUser->acceptName()» — модератор. Это контролируется где-то на уровне выше с помощью штатных средств фреймворка, всяких ACL и т.д.
Но как теперь тестировать? Добавить геттеры и проверять состояние? Как правило, там где есть логика, есть и события. Именно по событиям мы понимаем, что нужно добавить что-то в очередь на модерацию, отправить email/push/SMS, добавить данные в статистику и т.д.
Введём события:
PHP:
final class VkUser
{
use Events;
public function rename(Name $newName, DateTime $now): void
{
if ($this->name->equals($newName)) {
return;
}
if ($now->getDiffInHours($this->lastTimeNameChangedAt) < 24) {
throw new NameCannotBeChangedFrequently();
// or $this->apply(new NameChangingRequestWasDeclined($this->id, ...));
}
$this->apply(new UserRequestedNewName($this->id, $newName));
}
public function acceptName(DateTime $now): void
{
if ($this->nameOnModeration === null) {
return;
}
$this->apply(new UserNameWasAccepted($this->id, $this->nameOnModeration, $now));
}
private function onUserRequestedNewName(UserRequestedNewName $event): void
{
$this->nameOnModeration = $event->getNewName();
}
private function onUserNameWasAccepted(UserNameWasAccepted $event): void
{
$this->name = $event->getNewName();
$this->lastTimeNameChangedAt = $event->getAcceptingDate();
$this->nameOnModeration = null;
}
}
OK, теперь мы можем по событию UserRequestedNewName добавить имя в очередь на модерацию (это может быть отдельной SQL-таблицей, из которой мы порциями будем давать модераторам имена; мы же взяли в пример ВК, а там таких запросов будет много), а по событию UserNameWasAccepted писать в статистику, также логгировать, чтобы было понятно какой именно модератор заппрувил имя, чтобы потом можно было такого модератора уволить за недобросовестность.
И теперь то, о чём ты спрашивал: ну а как это тестировать-то? А тесты с событиями пишутся как-то так:
https://github.com/broadway/broadway/blob/master/examples/event-sourced-domain-with-tests/InvitesTest.php
PHP:
/**
* @test
*/
public function user_can_request_name_changing()
{
$this->scenario
->given(function () {
return VkUser::register(42, 'Vano', ...);
})
->when(function (VkUser $user) {
$user->rename('Ivan', new DateTime('2000-0'));
})
->then([
new UserRequestedNewName(42, 'Ivan')
]);
$this->scenario
->given(function () {
$user = VkUser::register(42, 'Vano', ...);
$user->rename('Ivan');
$user->acceptName(...);
})
->when(function (VkUser $user) {
$user->rename('VanoVano', new DateTime(...));
})
->then([
new NameChaningRequestWasDeclined(42)
]);
}
А до тех пор, пока у тебя тупая CRUD-логика, это нахрен не нужно.