Why, What, and How: Testing in 2014 · Web Application » Presentation (Presentation Model, View,...

Post on 22-Aug-2020

1 views 0 download

transcript

Why, What, and How: Testing in 2014Sebastian Bergmann | June 3rd 2014

Sebastian BergmannDriven by his passion to help developers build better software.

sharing experience

Web Application» Presentation

(Presentation Model, View, Template)

» Application Logic(HTTP Abstraction, Routing, Controller)

» Domain Logic

» Persistence

<?phpuseuse Behat\Mink\Session;useuse Behat\Mink\Driver\GoutteDriver;

abstract classabstract class MinkTestCase extendsextends PHPUnit_Framework_TestCase{

privateprivate $session$session;

protected functionprotected function setUp(){

$this$this->session = newnew Session(newnew GoutteDriver);}

protected functionprotected function visit($url$url){

$this$this->session->visit($url$url);

returnreturn $this$this->session->getPage();}

}

<?phpclassclass ApplicationTest extendsextends MinkTestCase{

public functionpublic function testExamplePageContainsExampleText(){

$page$page = $this$this->visit('http://example.com/');

$this$this->assertContains('Example Domain',$page$page->getContent()

);}

}

$ phpunit ApplicationTestPHPUnit 4.1.1 by Sebastian Bergmann.

.

Time: 1.52 seconds, Memory: 6.75Mb

OK (1 test, 1 assertion)

$ phpunit --testdox ApplicationTestPHPUnit 4.1.1 by Sebastian Bergmann.

Application[x] Example page contains example text

# features/example.featureFeature: Example...

Scenario: Accessing an example web page worksGiven I am on "/"Then I should see "Example Domain"

$ behat features/example.featureFeature: Example...

Scenario: Accessing an example web page worksGiven I am on "/"Then I should see "Example Domain"

1 scenario (1 passed)2 steps (2 passed)0m1.789s

"[T]he Rails community has adopted the strategy of writing hoards of

cucumber tests that drive the application through the web server. This leads

to slow and fragile tests and breaks many of the rules of TDD which tries to

keep tests from coupling to unnecessary system components like the GUI."

— Robert C. Martin

<?phpnamespacenamespace Company\Project;

classclass Application{

public functionpublic function run(RequestInterface $request$request, ResponseInterface $response$response){

// ...}

}

<?phpnamespacenamespace Company\Project;

interfaceinterface ResponseInterface{

public functionpublic function setData($key$key, $value$value);public functionpublic function getData($key$key);public functionpublic function hasData($key$key);

// ...}

<?phpnamespacenamespace Company\Project\Tests;useuse Company\Project\Application;useuse Company\Project\Request;useuse Company\Project\Response;useuse PHPUnit_Framework_TestCase;

classclass HomepageTest extendsextends PHPUnit_Framework_TestCase{

protected functionprotected function setUp(){

$this$this->application = newnew Application;$this$this->request = newnew Request;$this$this->response = newnew Response;

}

public functionpublic function testHasNavigation(){

$this$this->application->run($this$this->request, $this$this->response);

$this$this->assertTrue($this$this->response->hasData('navigation'));}

}

<?phpnamespacenamespace Company\Project\Tests;useuse Company\Project\Application;useuse Company\Project\Request;useuse Company\Project\Response;

classclass HomepageTest extendsextends IntegrationTestCase{

protected functionprotected function setUp(){

$this$this->application = newnew Application;$this$this->request = newnew Request;$this$this->response = newnew Response;

}

public functionpublic function testHasNavigation(){

$this$this->application->run($this$this->request, $this$this->response);

$this$this->assertResponseHasData('navigation', $this$this->response);}

}

<?phpnamespacenamespace Company\Project\Tests;useuse Company\Project\ResponseInterface;useuse PHPUnit_Framework_TestCase;

abstract classabstract class IntegrationTestCase extendsextends PHPUnit_Framework_TestCase{

public functionpublic function assertResponseHasData($name$name, ResponseInterface $response$response){

$constraint$constraint = newnew DataExistsConstraint($response$response);$this$this->assertThat($name$name, $constraint$constraint);

}

public functionpublic function assertResponseNotHasData($name$name, ResponseInterface $response$response){

$constraint$constraint = newnew DataExistsConstraint($response$response);$this$this->assertThat($name$name, $this$this->logicalNot($constraint$constraint));

}}

<?phpnamespacenamespace Company\Project\Tests;useuse Company\Project\ResponseInterface;useuse PHPUnit_Framework_Constraint;

classclass DataExistsConstraint extendsextends PHPUnit_Framework_Constraint{

privateprivate $response$response;

public functionpublic function __construct(ResponseInterface $response$response){

$this$this->response = $response$response;}

protected functionprotected function matches($other$other){

returnreturn $this$this->response->hasData($other$other);}

public functionpublic function toString(){

returnreturn 'data exists';}

}

<?phpnamespacenamespace Company\Project;

classclass SampleWorkflow{

privateprivate $backend$backend;privateprivate $service$service;

public functionpublic function __construct(Backend $backend$backend, Service $service$service){

$this$this->backend = $backend$backend;$this$this->service = $service$service;

}

public functionpublic function execute(Request $request$request){

$this$this->service->doWork($this$this->backend->getObjectById($request$request->getValue('id'))

);}

}

<?phpnamespacenamespace Company\Project;useuse PHPUnit_Framework_TestCase;

classclass SampleWorkflowTest extendsextends PHPUnit_Framework_TestCase{

public functionpublic function testServiceCallUpdatesObject(){

$service$service = $this$this->getMockBuilder('Service')->enableProxyingToOriginalMethods()->getMock();

$service$service->expects($this$this->once())->method('doWork');

$backend$backend = newnew Backend;$workflow$workflow = newnew SampleWorkflow($backend$backend, $service$service);

$workflow$workflow->execute(newnew Request(arrayarray('id' => 2204)));}

}

Domain Logic"Responsible for representing concepts of the business, information about

the business situation, and business rules. State that reflects the business

situation is controlled and used here, even though the technical details of

storing it are delegated to the infrastructure. This layer is the heart of

business software."

— Eric Evans

Domain LogicWhen you cannot test your domain logic, the heart of your software, using

unit tests and in isolation from persistence and the web context ...

TDD is dead. Long live testing."I rarely unit test in the traditional sense of the word, where all

dependencies are mocked out, and thousands of tests can close in seconds.

It just hasn't been a useful way of dealing with the testing of Rails

applications. I test active record models directly, letting them hit the

database, and through the use of fixtures. Then layered on top is currently a

set of controller tests, but I'd much rather replace those with even higher

level system tests through Capybara or similar."

— David Heinemeier Hansson

Monogamous TDD"If you trust those integration tests so much that you are willing to deploy

when they pass; and if they execute so quickly that you can continuously

and effectively refactor and clean the code, then you aren't doing any better

than me. Do it."

— Robert C. Martin

Monogamous TDD"But (and this is a big "but"), it seems to me that integration tests have very

little chance of meeting my two predicates."

— Robert C. Martin

Effective Testing

— Rich Martin

» A high-fidelity test is one which is very sensitive to defects in the codeunder test

» A resilient test is one that only fails when a breaking change is made to thecode under test

» A high-precision test tells you exactly where the defect lies

External and Internal Quality"Running end-to-end tests tells us about the the external quality of our

system, and writing them tells us something about how well we [...]

understand the domain, but end-to-end tests don't tell us how well we've

written the code. Writing unit tests gives us a lot of feedback about the

quality of our code, and running them tell us that we haven't broken any

classes [...]"

— Steve Freeman and Nat Pryce

talks.thePHP.cc

sebastian@thePHP.cc

@s_bergmann

sharing experience