Продолжаем работать над тест-ориентированной разработкой Кента Бека на примерах и переписываем примеры на PHP.
Глава 13 описывает тест, который должен возвращать true, если 2 объекта одного типа. В предыдущих главах оценка работала, но для этого одного примера я не могу выполнить это, и я не уверен, почему это не удается.
Имеется класс «Sum», который реализует интерфейс Expression:
class Sum implements Expression {
public $augend;
public $addend;public function __construct($augend, $addend)
{
$this->augend = $augend;
$this->addend = $addend;
}
// impl of Expression interface, but this smells to me, dupe implementation
// also in Money
public function plus($addend) {
return new Sum($this, $addend);
}
public function reduce($to) {
$amount = $this->augend->amount + $this->addend->amount;
return new Money($amount, $to);
}
}
И выражение:
interface Expression {
public function plus($addend);
public function reduce($to);
}
Я пытаюсь вызвать метод для объекта Bank (называемого Reduce), первый аргумент которого является объектом Sum, который имеет собственную реализацию Redu. Тем не менее, пример Java указывает первый аргумент как интерфейс, а не конкретный класс:
class Bank {
// the book defines the $source param as type Expression, which is legal
// in Java but not in PHP
public function reduce($source, $to) {
return $source->reduce($to);
}
}
и наконец, мой класс Money:
class Money implements Expression {
public $amount;
public $currency;
public function __construct($amount, $currency) {
$this->amount = $amount;
$this->currency = $currency;
}
public function currency(){
return $this->currency;
}
public function equals($compareObject) {
return $this->amount == $compareObject->amount
&& $this->currency() == $compareObject->currency();
}
// static factory method that returns Dollar
// (reduces dependence on subclasses)
static function dollar($amount) {
return new Dollar($amount, "USD");
}
static function franc($amount) {
return new Franc($amount, "CHF");
}
public function times($multiplier) {
return new Money($this->amount * $multiplier, $this->currency);
}// impl of Expression interface
public function plus($addend) {
return new Sum($this, $addend);
}
public function reduce($to) {
return $this;
}
}
При запуске этого теста:
$sum = new Sum(Money::dollar(3), Money::dollar(4));
$bank = new Bank();
$result = $bank->reduce($sum, "USD");
$this->assertEquals(Money::dollar(7), $result); //FAIL
Утверждение не удается, указав, что $ result является типом Money, а не типом Dollar, хотя я проверил, что свойства для каждого объекта совпадают:
$this->assertEquals(Money::dollar(7)->amount, $result->amount);$this->assertEquals(Money::dollar(7)->currency, $result->currency);
Является ли это следствием отсутствия способности по-настоящему приводить объекты к определенным типам? Я не изменил реализацию для подклассов, и предыдущие тесты все еще проходят:
$five = Money::dollar(5);
$this->assertEquals(new Money(10, "USD"), $five->times(2));
$this->assertEquals(new Money(15, "USD"), $five->times(3));
$this->assertEquals(get_class($five), "Dollar");
Два объекта разного типа никогда не могут быть равны. PHP не позволяет объекту самому решать, равен ли он другому объекту, как это делает Java с помощью метода equal.
http://php.net/manual/en/language.oop5.object-comparison.php
В ваших тестовых случаях вы можете вызвать метод eqauls самостоятельно.
$this->assertsTrue( $result->equals(Money::dollar(7)) );
Это скорее проблема архитектуры, чем проблема использования phpunit. Я дал вам 2 простых решения, но вы, конечно, должны улучшить свой код с помощью некоторых шаблонов проектирования.
1.
Какой смысл определять класс Dollar
если вы должны передать «USD» конструктору? Используйте класс Money
вместо.
// static factory method that returns Dollar
// (reduces dependence on subclasses)
static function dollar($amount) {
return new Money($amount, "USD");
}
static function franc($amount) {
return new Money($amount, "CHF");
}
public function times($multiplier) {
return new Money($this->amount * $multiplier, $this->currency);
}
2.
Это не логика, что сумма Dollar
вернуть Money
возможно с другой валютой (если у вас нет механизма конвертации). Вы можете изменить метод reduce
из Sum
как это:
public function reduce() {
$money = clone $this->augend;
$money->amount = $this->augend->amount + $this->addend->amount;
return $money;
}
—
С этими решениями ваш тест будет работать как положено:
$sum = new Sum(Money::dollar(3), Money::dollar(4));
$bank = new Bank();
$result = $bank->reduce($sum, "USD");
$this->assertEquals(Money::dollar(7), $result); // OK
Потому что это утверждение проверяет класс объектов и равенство свойств.