I was given a test to write a class that calculates a vehicle's average and expected mileage without using a database or framework.
The mileages were to be calculated given some different types of events, which I've decided to implement in a flat way, as this is how I would store them in the DB.
If you could let me know what you think about it, that would be nice.
Event.php
namespace App;
class Event {
protected $id = null;
protected $event_type = null;
protected $date = null;
protected $mileage = null;
protected $result = null;
protected $price = null;
protected $from_vrm = null;
protected $to_vrm = null;
private $eventTypes = ['ADVERTISED_FOR_SALE', 'MOT_TEST', 'VRM_CHANGE'];
public function setId($id) {
$this->id = $id;
}
public function setEventType($event_type) {
if(!in_array($event_type, $this->eventTypes)) {
throw new \Exception('Unrecognised event type');
}
else {
$this->event_type = $event_type;
}
}
public function setDate($date) {
$this->date = $date;
}
public function setMileage($mileage) {
$this->mileage = $mileage;
}
public function setResult($result) {
$this->result = $result;
}
public function setPrice($price) {
$this->price = $price;
}
public function setFromVrm($from_vrm) {
$this->from_vrm = $from_vrm;
}
public function setToVrm($to_vrm) {
$this->to_vrm = $to_vrm;
}
public function getId() {
return $this->id;
}
public function getDate() {
return $this->date;
}
public function getMilage() {
return $this->mileage;
}
public function getEventType() {
return $this->event_type;
}
}
EventTest.php
class EventTest extends TestCase {
public function testNamespacing() {
$event = new Cazana\Event();
$this->assertInstanceOf(Cazana\Event::class, $event);
}
public function testSetEventType() {
$event = new Cazana\Event();
$event->setEventType('ADVERTISED_FOR_SALE');
$this->assertEquals($event->getEventType(), 'ADVERTISED_FOR_SALE');
}
public function testSetInvalidEventType() {
$event = new Cazana\Event();
$this->setExpectedException(Exception::class);
$event->setEventType('INVALID_EVENT_TYPE');
}
}
Vehicle.php
namespace App;
class Vehicle {
protected $id = null;
protected $vrm = null;
protected $make = null;
protected $model = null;
protected $first_registration_date = null;
protected $events = [];
public function setId($id) {
$this->id = $id;
}
public function setVrm($vrm) {
$this->vrm = $vrm;
}
public function setMake($make) {
$this->make = $make;
}
public function setModel($model) {
$this->model = $model;
}
public function setFirstRegistrationDate($first_registration_date) {
$this->first_registration_date = $first_registration_date;
}
public function addEvent(Event $event) {
array_push($this->events, $event);
}
public function getId() {
return $this->id;
}
public function getVrm() {
return $this->vrm;
}
public function getMake() {
return $this->make;
}
public function getModel() {
return $this->model;
}
public function getFirstRegistrationDate() {
return $this->first_registration_date;
}
public function getEvents() {
return $this->events;
}
public function calculateAverageMilage() {
if($this->first_registration_date == null) {
throw new \Exception('No first registation date set, cannot calculate');
}
//calculate the average annual milage based off the last event that we have
$lastEvent = null;
foreach($this->events as $event) {
//we only need to look at these two event types, as the others do not store milage info
if(in_array($event->getEventType(), ['ADVERTISED_FOR_SALE', 'MOT_TEST'])) {
if($lastEvent == null || ($event->getDate() > $lastEvent->getDate())) {
$lastEvent = $event;
}
}
}
if($lastEvent == null) {
$average = 7900;
}
else {
//return the last milage divided by the age of the car (at that time)
$average = $lastEvent->getMilage() / ($lastEvent->getDate()->format('Y') - $this->first_registration_date->format('Y'));
}
return $average;
}
public function calculateCurrentMilage() {
$current = new \DateTime();
return $this->calculateAverageMilage() * ($current->format('Y') - $this->first_registration_date->format('Y'));
}
}
VehicleTest.php
class VehicleTest extends TestCase {
public function testNamespacing() {
$vehicle = new App\Vehicle();
$this->assertInstanceOf(App\Vehicle::class, $vehicle);
}
public function testAddEvent() {
$vehicle = new \App\Vehicle();
$event = new \App\Event();
$event->setId(1);
$event->setEventType('ADVERTISED_FOR_SALE');
$event->setDate(new DateTime('10-10-2010'));
$event->setMileage(10000);
$event->setPrice(10000.00);
$vehicle->addEvent($event);
$this->assertContainsOnly(App\Event::class, $vehicle->getEvents());
}
public function testAverageMilage() {
$vehicle = new \App\Vehicle();
$event = new \App\Event();
$event->setId(1);
$event->setEventType('ADVERTISED_FOR_SALE');
$event->setDate(new DateTime('10-01-2000'));
$event->setMileage(5000);
$event->setPrice(50000.00);
$event2 = new \App\Event();
$event2->setId(1);
$event2->setEventType('MOT_TEST');
$event2->setDate(new DateTime('10-05-2005'));
$event2->setMileage(10000);
$event->setResult(true);
$event3 = new \App\Event();
$event3->setId(3);
$event3->setEventType('VRM_CHANGE');
$event3->setDate(new DateTime('10-10-2010'));
$event3->setFromVrm('ABC-123');
$event3->setToVrm('DEF-456');
$event4 = new \App\Event();
$event4->setId(4);
$event4->setEventType('ADVERTISED_FOR_SALE');
$event4->setDate(new DateTime('10-01-2015'));
$event4->setMileage(20000);
$event4->setPrice(37000.00);
$vehicle->addEvent($event);
$vehicle->addEvent($event2);
$vehicle->addEvent($event3);
$vehicle->addEvent($event4);
$vehicle->setFirstRegistrationDate(new DateTime('01-01-1998'));
$this->assertGreaterThan(500, $vehicle->calculateAverageMilage());
}
public function testAverageMilageNoFirstRegistationDate() {
//should throw an exception
$vehicle = new \App\Vehicle();
$this->setExpectedException(Exception::class);
$vehicle->calculateAverageMilage();
}
public function testAverageMilageNoEvents() {
//testing the average milage when we have no events, should be 7,900
$vehicle = new \App\Vehicle();
$vehicle->setFirstRegistrationDate(new DateTime('01-01-1998'));
$this->assertEquals(7900, $vehicle->calculateAverageMilage());
}
public function testCurrentMilage() {
$vehicle = new \App\Vehicle();
$event = new \App\Event();
$event->setId(1);
$event->setEventType('ADVERTISED_FOR_SALE');
$event->setDate(new DateTime('10-01-2000'));
$event->setMileage(5000);
$event->setPrice(50000.00);
$event2 = new \App\Event();
$event2->setId(1);
$event2->setEventType('MOT_TEST');
$event2->setDate(new DateTime('10-05-2005'));
$event2->setMileage(10000);
$event->setResult(true);
$event3 = new \App\Event();
$event3->setId(3);
$event3->setEventType('VRM_CHANGE');
$event3->setDate(new DateTime('10-10-2010'));
$event3->setFromVrm('ABC-123');
$event3->setToVrm('DEF-456');
$event4 = new \App\Event();
$event4->setId(4);
$event4->setEventType('ADVERTISED_FOR_SALE');
$event4->setDate(new DateTime('10-01-2015'));
$event4->setMileage(20000);
$event4->setPrice(37000.00);
$vehicle->addEvent($event);
$vehicle->addEvent($event2);
$vehicle->addEvent($event3);
$vehicle->addEvent($event4);
$vehicle->setFirstRegistrationDate(new DateTime('01-01-1998'));
$this->assertGreaterThan(20000, $vehicle->calculateCurrentMilage());
}
public function testCurrentMilageNoEvents() {
//should return the result of the vehicles age * 7900
$vehicle = new \App\Vehicle();
$vehicle->setFirstRegistrationDate(new DateTime('01-01-1998'));
$this->assertEquals($vehicle->calculateCurrentMilage(), 150100);
}
public function testCurrentMilageNoFirstRegistationDate() {
//should throw an exception
$vehicle = new \App\Vehicle();
$this->setExpectedException(Exception::class);
$vehicle->calculateCurrentMilage();
}
}