KEMBAR78
PHPSpec BDD for PHP | KEY
PHPSpec
 BDD for PHP
Marcello Duarte
          @_md


PHPSpec Lead Developer
Head of Training @
Agile guy
What is PHPSpec?
What is PHPSpec?
 BDD Framework
What is PHPSpec?
 BDD Framework

 Created by Pádraic Brady
What is PHPSpec?
 BDD Framework

 Created by Pádraic Brady

 DSL based on RSpec
BDD?
Better way to explain TDD
TDD?
TDD?
Write a failing test
TDD?
       Write a failing test

Make it fail for the right reasons
TDD?
      Write a failing test
Make it fail for the right reasons

   Make it pass (just about)
TDD?
      Write a failing test
Make it fail for the right reasons
  Make it pass (just about)

            Refactor
Unit Testing
XP: test everything that can possibly break
Unit Testing
XP: test everything that can possibly break
       Unit: vague and structural 1:1
Unit Testing
XP: test everything that can possibly break
       Unit: vague and structural 1:1
       Focus on code not behaviour
It’s all in the mind
 It’s soo simple, it does not need tests
It’s all in the mind
 It’s soo simple, it does not need tests
      I’ve done this millions of times
It’s all in the mind
      It’s soo simple, it does not need tests
           I’ve done this millions of times
I will write my code rst and test if I have time
Focus on value
Focus on behaviour
Test
Test
Test


Specify
class CalculatorTest


      becomes
class DescribeCalculator
Test Case
Test Case
Test Case


Context
class CalculatorTest extends
   SomeTestFramework_TestCase


          becomes
class DescribeCalculator extends
        PHPSpecContext
Test Method
Test Method
Test Method


 Example
testAddWithNoArguments()


         becomes
itReturnsZeroWithNoArguments()
Assert
Assert
Assert


Expect
$this->assertEquals(0, $result);


         becomes
    $result->should->be(0);
Installing
PEAR (soon...)

# pear channel-discover pear.phpspec.net
Adding Channel "pear.phpspec.net" succeeded
Discovery of channel "pear.phpspec.net" succeeded
# pear config-set preferred_state beta
# pear install --alldeps phpspec/PHPSpec

GITHUB

$ git clone git://github.com/phpspec/phpspec.git
Set initial state
# StringCalculatorSpec.php
<?php


class DescribeStringCalculator extends PHPSpecContext
{
    function before()
    {
        $this->calculator = $this->spec(new StringCalculator);
    }
}
PHPSpec DSL

class DescribeStringCalculator extends PHPSpecContext
{
    ...

    function itReturnsZeroWithNoArguments()
    {
        $this->calculator->add()->should->be(0);
    }
}
Lets run our specs
$ phpspec StringCalculatorSpec.php -c
..

Finished in 0.055689 seconds

2 examples, 0 failures
Pending examples

function itReturnsTheBareNumber()
{
    $this->pending('Waiting to clarify the spec');
}
$ phpspec StringCalculatorSpec.php -c
..*

Pending:
  String Calculator returns the bare number
     # Waiting to clarify the spec
     # ./spec/StringCalculatorSpec.php:19

Finished in 0.056134 seconds
2 examples, 0 failures, 1 pending
$
Failing examples

public function itReturnsTheSumOfSpaceSeparatedString()
{
    $this->calculator->add('4 2')->should->be(6);
}

// this will fail because it’s not yet implemented
$ phpspec StringCalculatorSpec.php -c
.*.F

Pending:
  String Calculator returns the bare number
     # Waiting to clarify the spec
     # ./spec/StringCalculatorSpec.php:19

Failures:
  1) String Calculator returns the sum of space separate string
     expected 6, got NULL (using be())
     # .spec/StringCalculatorSpec.php:28

Finished in 0.056134 seconds
4 examples, 1 failure, 1 pending
$
$result->should->be(0)
$result->shouldNot->be(42)
$ phpspec StringCalculatorSpec.php -c
.*.F

Pending:
  String Calculator returns the bare number
     # Waiting to clarify the spec
     # ./spec/StringCalculatorSpec.php:19

Failures:
  1) String Calculator returns the sum of space separate string
     expected 42, not to be 24 (using be())
     # .spec/StringCalculatorSpec.php:28

Finished in 0.056134 seconds
4 examples, 1 failure, 1 pending
$
Deliberate fail

public function itReturnsTheBareNumber()
{
    $this->fail('Just because');
}
$ phpspec StringCalculatorSpec.php -c
.*.F

Pending:
  String Calculator returns the bare number
     # Waiting to clarify the spec
     # ./spec/StringCalculatorSpec.php:19

Failures:
  1) String Calculator returns the sum of space separate string
     expected 42, got 0 (using be())
     # .spec/StringCalculatorSpec.php:28

  2) StringCalculator returns the sum of any white space
separated string
     Failure/Error:
     Just because

Finished in 0.056134 seconds
4 examples, 1 failure, 1 pending
Matchers
be($match)
         equal($match)
       beEqualTo($match)
    beAnInstanceOf($match)
            beEmpty()
            beFalse()
     beGreaterThan($match)
beGreaterThanOrEqualTo($match)
And more matchers...
beInteger()
     beLessThan($match)
beLessThanOrEqualTo($match)
           beNull()
          beString()
           beTrue()

  throwException($match)
beInteger()
     beLessThan($match)
beLessThanOrEqualTo($match)
           beNull()
          beString()
           beTrue()

                           w
  throwException($match) ne
Predicate Matcher

           $cell = $this->spec(new Cell);
           $cell->should->beAlive();

class Cell
{
    protected $alive = true;

    public function isAlive() {
        return $this->alive;
    }
    ...
}
Predicate Matcher

           $newNode = $this->spec(new Node);
           $newNode->shouldNot->haveChildren();

class Node
{
    protected $children = array();

    public function hasChildren() {
        return count($this->children) > 0;
    }
    ...
}
User De ned Matcher
class DescribeRapture extends PHPSpecContext
{
   ...
    function itIsNotTheEndOfTheWorldAsWeKnowIt()
    {
        $today = new DateTime('2011-05-20');
        $this->rapture->setDate($today)
        $this->rapture->shouldNot->happenToday();
    }
}
$ phpspec RaptureSpec.php -c
E

Exceptions:
  1) Rapture is not the end of the world as we know it
     Failure/Error: $this->rapture->shouldNot->happenToday();
     PHPSpecException: unknown method call
     # ./RaptureSpec.php:19
     # /usr/share/pear/PHPSpec/Runner/Example.php:168

Finished in 0.056134 seconds
1 examples, 1 exception
$
PHPSpecMatcherdefine('happenToday', function() {
    return array (
        'match' => function($rapture) {
            return $rapture->happensToday();
        },
        'failure_message_for_should_not' =>
            function($rapture) {
                return sprintf("expected %s not to mean kaput",
                       $rapture->getDate());
            }
    );
});

class DescribeRapture extends PHPSpecContext
{   ...
    function itIsNotTheEndOfTheWorldAsWeKnowIt() {
        $today = new DateTime('2011-05-20');
        $this->rapture->setDate($today)
        $this->rapture->shouldNot->happenToday();
    }
}
http://farm5.static.flickr.com/4140/4926597784_4392361eb6_b_d.jpg
PHPSpecMatcherdefine('contain', function($course) {
    return array (
        'match' => function($repository) use ($course) {
            return $repository->has($course);
        },
        'failure_message_for_should' =>
            function($repository) use ($course) {
                return "expected $course to be in repository";
            }
    );
});

class DescribeCourseRepository extends PHPSpecContext
{   ...
    function itAddsNewCourses() {
        $course = new Course("BDD with PHPSpec");
        $this->courses->add($course);
        $this->courses->should->contain(course);
    }
}
$ phpspec CourseRepositorySpec.php -c
F

Failures:
  1) Course Repository adds new courses
     Failure/Error: $this->courses->should->contain(course);
     expected 'BDD with PHPSpec' to be in repository
     # ./CourseRepository.php:19

Finished in 0.056134 seconds
1 examples, 1 failure
$
Hooks


  before()
   after()
beforeAll()
 afterAll()
Mocks
Mocks


 mock()
 stub()
double()
Stub Example
$greeter = stub('Greeter');
$greeter->stub('greet')->andReturn('Hello Mocks!');

$hello = $this->spec(new HelloWorld($greeter));
$hello->sayHello()->should->equal('Hello Mocks!');
Partial Stub
$greeter = mock('Greeter');
$greeter ->stub('greet')
         ->shouldReceive('Chuck')
         ->andReturn('Hello Chuck!');

$hello = $this->spec(new HelloWorld($greeter));
$hello->sayHello()->should->equal('Hello Chuck!');
Shortcut
$greeter = stub('Greeter', array('greet' => 'Hello World!'));

$hello = $this->spec(new HelloWorld($greeter));
$hello->sayHello()->should->equal('Hello World!');
Mocks and hinted type
class HelloWorld
{
    public function __construct(Greeter $greeter) // <-- type
    {
        ...
    }
}

$greeter = mock('Greeter'); // <-- we are covered
Empty doubles
class HelloWorld
{
    public function __construct($greeter) // <-- no type
    {
        ...
    }
}

$greeter = mock(); // <-- no need for types
Counters
$greeter ->stub('greet')->andReturn('Hello Chuck!')
         ->exactly(42);

$greeter ->stub('greet')->andReturn('Hello Chuck!')
         ->never();
Coming soon (more counters)
$greeter ->stub('greet')->andReturn('Hello Chuck!')
         ->atLeast(2);

$greeter ->stub('greet')->andReturn('Hello Chuck!')
         ->atMost(5);

$greeter ->stub('greet')->andReturn('Hello Chuck!')
         ->between('2..5');
Coming soon (stub chain)
                        Instead of

$frontController = double();
$dispatcher       = double();
$route            = double();
$request          = double();
$request->stub('frontController')
         ->andReturn($frontController);
$frontController->stub('dispatcher')->andReturn($dispatcher);
$dispatcher->stub('route')->andReturn($route);
$route->stub('request')->andReturn($request);
Coming soon (stub chain)
                      You can have

$request->stubChain('frontController', 'dispatcher', 'route',
'request');
More to come

      CI friendly reports
      functional approach
           backtrace
             fail fast
            autorun
      run single examples
integration with ZF and Kohana
Links
 http://github.com/phpspec/phpspec
http://github/phpspec/phpspec-mocks
        http://www.phpspec.net

http://dannorth.net/introducing-bdd

       http://twitter.com/_md
     http://twitter.com/phpspec
Thank you!

       Marcello Duarte
           @_md

http://joind.in/talk/view/3469




   is hiring. Come talk to me.

PHPSpec BDD for PHP

  • 1.
  • 2.
    Marcello Duarte @_md PHPSpec Lead Developer Head of Training @ Agile guy
  • 3.
  • 4.
    What is PHPSpec? BDD Framework
  • 5.
    What is PHPSpec? BDD Framework Created by Pádraic Brady
  • 6.
    What is PHPSpec? BDD Framework Created by Pádraic Brady DSL based on RSpec
  • 7.
    BDD? Better way toexplain TDD
  • 8.
  • 9.
  • 10.
    TDD? Write a failing test Make it fail for the right reasons
  • 11.
    TDD? Write a failing test Make it fail for the right reasons Make it pass (just about)
  • 12.
    TDD? Write a failing test Make it fail for the right reasons Make it pass (just about) Refactor
  • 13.
    Unit Testing XP: testeverything that can possibly break
  • 14.
    Unit Testing XP: testeverything that can possibly break Unit: vague and structural 1:1
  • 15.
    Unit Testing XP: testeverything that can possibly break Unit: vague and structural 1:1 Focus on code not behaviour
  • 16.
    It’s all inthe mind It’s soo simple, it does not need tests
  • 17.
    It’s all inthe mind It’s soo simple, it does not need tests I’ve done this millions of times
  • 18.
    It’s all inthe mind It’s soo simple, it does not need tests I’ve done this millions of times I will write my code rst and test if I have time
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
    class CalculatorTest becomes class DescribeCalculator
  • 25.
  • 26.
  • 27.
  • 28.
    class CalculatorTest extends SomeTestFramework_TestCase becomes class DescribeCalculator extends PHPSpecContext
  • 29.
  • 30.
  • 31.
  • 32.
    testAddWithNoArguments() becomes itReturnsZeroWithNoArguments()
  • 33.
  • 34.
  • 35.
  • 36.
    $this->assertEquals(0, $result); becomes $result->should->be(0);
  • 37.
    Installing PEAR (soon...) # pearchannel-discover pear.phpspec.net Adding Channel "pear.phpspec.net" succeeded Discovery of channel "pear.phpspec.net" succeeded # pear config-set preferred_state beta # pear install --alldeps phpspec/PHPSpec GITHUB $ git clone git://github.com/phpspec/phpspec.git
  • 38.
    Set initial state #StringCalculatorSpec.php <?php class DescribeStringCalculator extends PHPSpecContext { function before() { $this->calculator = $this->spec(new StringCalculator); } }
  • 39.
    PHPSpec DSL class DescribeStringCalculatorextends PHPSpecContext { ... function itReturnsZeroWithNoArguments() { $this->calculator->add()->should->be(0); } }
  • 40.
    Lets run ourspecs $ phpspec StringCalculatorSpec.php -c .. Finished in 0.055689 seconds 2 examples, 0 failures
  • 41.
    Pending examples function itReturnsTheBareNumber() { $this->pending('Waiting to clarify the spec'); }
  • 42.
    $ phpspec StringCalculatorSpec.php-c ..* Pending: String Calculator returns the bare number # Waiting to clarify the spec # ./spec/StringCalculatorSpec.php:19 Finished in 0.056134 seconds 2 examples, 0 failures, 1 pending $
  • 43.
    Failing examples public functionitReturnsTheSumOfSpaceSeparatedString() { $this->calculator->add('4 2')->should->be(6); } // this will fail because it’s not yet implemented
  • 44.
    $ phpspec StringCalculatorSpec.php-c .*.F Pending: String Calculator returns the bare number # Waiting to clarify the spec # ./spec/StringCalculatorSpec.php:19 Failures: 1) String Calculator returns the sum of space separate string expected 6, got NULL (using be()) # .spec/StringCalculatorSpec.php:28 Finished in 0.056134 seconds 4 examples, 1 failure, 1 pending $
  • 45.
  • 46.
    $ phpspec StringCalculatorSpec.php-c .*.F Pending: String Calculator returns the bare number # Waiting to clarify the spec # ./spec/StringCalculatorSpec.php:19 Failures: 1) String Calculator returns the sum of space separate string expected 42, not to be 24 (using be()) # .spec/StringCalculatorSpec.php:28 Finished in 0.056134 seconds 4 examples, 1 failure, 1 pending $
  • 47.
    Deliberate fail public functionitReturnsTheBareNumber() { $this->fail('Just because'); }
  • 48.
    $ phpspec StringCalculatorSpec.php-c .*.F Pending: String Calculator returns the bare number # Waiting to clarify the spec # ./spec/StringCalculatorSpec.php:19 Failures: 1) String Calculator returns the sum of space separate string expected 42, got 0 (using be()) # .spec/StringCalculatorSpec.php:28 2) StringCalculator returns the sum of any white space separated string Failure/Error: Just because Finished in 0.056134 seconds 4 examples, 1 failure, 1 pending
  • 49.
  • 50.
    be($match) equal($match) beEqualTo($match) beAnInstanceOf($match) beEmpty() beFalse() beGreaterThan($match) beGreaterThanOrEqualTo($match)
  • 51.
  • 52.
    beInteger() beLessThan($match) beLessThanOrEqualTo($match) beNull() beString() beTrue() throwException($match)
  • 53.
    beInteger() beLessThan($match) beLessThanOrEqualTo($match) beNull() beString() beTrue() w throwException($match) ne
  • 54.
    Predicate Matcher $cell = $this->spec(new Cell); $cell->should->beAlive(); class Cell { protected $alive = true; public function isAlive() { return $this->alive; } ... }
  • 55.
    Predicate Matcher $newNode = $this->spec(new Node); $newNode->shouldNot->haveChildren(); class Node { protected $children = array(); public function hasChildren() { return count($this->children) > 0; } ... }
  • 56.
    User De nedMatcher class DescribeRapture extends PHPSpecContext { ... function itIsNotTheEndOfTheWorldAsWeKnowIt() { $today = new DateTime('2011-05-20'); $this->rapture->setDate($today) $this->rapture->shouldNot->happenToday(); } }
  • 57.
    $ phpspec RaptureSpec.php-c E Exceptions: 1) Rapture is not the end of the world as we know it Failure/Error: $this->rapture->shouldNot->happenToday(); PHPSpecException: unknown method call # ./RaptureSpec.php:19 # /usr/share/pear/PHPSpec/Runner/Example.php:168 Finished in 0.056134 seconds 1 examples, 1 exception $
  • 58.
    PHPSpecMatcherdefine('happenToday', function() { return array ( 'match' => function($rapture) { return $rapture->happensToday(); }, 'failure_message_for_should_not' => function($rapture) { return sprintf("expected %s not to mean kaput", $rapture->getDate()); } ); }); class DescribeRapture extends PHPSpecContext { ... function itIsNotTheEndOfTheWorldAsWeKnowIt() { $today = new DateTime('2011-05-20'); $this->rapture->setDate($today) $this->rapture->shouldNot->happenToday(); } }
  • 59.
  • 60.
    PHPSpecMatcherdefine('contain', function($course) { return array ( 'match' => function($repository) use ($course) { return $repository->has($course); }, 'failure_message_for_should' => function($repository) use ($course) { return "expected $course to be in repository"; } ); }); class DescribeCourseRepository extends PHPSpecContext { ... function itAddsNewCourses() { $course = new Course("BDD with PHPSpec"); $this->courses->add($course); $this->courses->should->contain(course); } }
  • 61.
    $ phpspec CourseRepositorySpec.php-c F Failures: 1) Course Repository adds new courses Failure/Error: $this->courses->should->contain(course); expected 'BDD with PHPSpec' to be in repository # ./CourseRepository.php:19 Finished in 0.056134 seconds 1 examples, 1 failure $
  • 62.
    Hooks before() after() beforeAll() afterAll()
  • 63.
  • 64.
  • 65.
    Stub Example $greeter =stub('Greeter'); $greeter->stub('greet')->andReturn('Hello Mocks!'); $hello = $this->spec(new HelloWorld($greeter)); $hello->sayHello()->should->equal('Hello Mocks!');
  • 66.
    Partial Stub $greeter =mock('Greeter'); $greeter ->stub('greet') ->shouldReceive('Chuck') ->andReturn('Hello Chuck!'); $hello = $this->spec(new HelloWorld($greeter)); $hello->sayHello()->should->equal('Hello Chuck!');
  • 67.
    Shortcut $greeter = stub('Greeter',array('greet' => 'Hello World!')); $hello = $this->spec(new HelloWorld($greeter)); $hello->sayHello()->should->equal('Hello World!');
  • 68.
    Mocks and hintedtype class HelloWorld { public function __construct(Greeter $greeter) // <-- type { ... } } $greeter = mock('Greeter'); // <-- we are covered
  • 69.
    Empty doubles class HelloWorld { public function __construct($greeter) // <-- no type { ... } } $greeter = mock(); // <-- no need for types
  • 70.
    Counters $greeter ->stub('greet')->andReturn('Hello Chuck!') ->exactly(42); $greeter ->stub('greet')->andReturn('Hello Chuck!') ->never();
  • 71.
    Coming soon (morecounters) $greeter ->stub('greet')->andReturn('Hello Chuck!') ->atLeast(2); $greeter ->stub('greet')->andReturn('Hello Chuck!') ->atMost(5); $greeter ->stub('greet')->andReturn('Hello Chuck!') ->between('2..5');
  • 72.
    Coming soon (stubchain) Instead of $frontController = double(); $dispatcher = double(); $route = double(); $request = double(); $request->stub('frontController') ->andReturn($frontController); $frontController->stub('dispatcher')->andReturn($dispatcher); $dispatcher->stub('route')->andReturn($route); $route->stub('request')->andReturn($request);
  • 73.
    Coming soon (stubchain) You can have $request->stubChain('frontController', 'dispatcher', 'route', 'request');
  • 74.
    More to come CI friendly reports functional approach backtrace fail fast autorun run single examples integration with ZF and Kohana
  • 75.
    Links http://github.com/phpspec/phpspec http://github/phpspec/phpspec-mocks http://www.phpspec.net http://dannorth.net/introducing-bdd http://twitter.com/_md http://twitter.com/phpspec
  • 76.
    Thank you! Marcello Duarte @_md http://joind.in/talk/view/3469 is hiring. Come talk to me.

Editor's Notes

  • #2 \n
  • #3 \n
  • #4 Differs from normal practice in terms of effort and awareness\n
  • #5 Differs from normal practice in terms of effort and awareness\n
  • #6 Differs from normal practice in terms of effort and awareness\n
  • #7 tester hat\n
  • #8 Differs from normal practice in terms of effort and awareness\n
  • #9 Differs from normal practice in terms of effort and awareness\n
  • #10 Differs from normal practice in terms of effort and awareness\n
  • #11 Differs from normal practice in terms of effort and awareness\n
  • #12 Differs from normal practice in terms of effort and awareness\n
  • #13 Differs from normal practice in terms of effort and awareness\n
  • #14 Differs from normal practice in terms of effort and awareness\n
  • #15 Differs from normal practice in terms of effort and awareness\n
  • #16 Differs from normal practice in terms of effort and awareness\n
  • #17 Differs from normal practice in terms of effort and awareness\n
  • #18 Differs from normal practice in terms of effort and awareness\n
  • #19 Differs from normal practice in terms of effort and awareness\n
  • #20 Differs from normal practice in terms of effort and awareness\n
  • #21 Differs from normal practice in terms of effort and awareness\n
  • #22 Differs from normal practice in terms of effort and awareness\n
  • #23 Differs from normal practice in terms of effort and awareness\n
  • #24 Differs from normal practice in terms of effort and awareness\n
  • #25 Differs from normal practice in terms of effort and awareness\n
  • #26 Differs from normal practice in terms of effort and awareness\n
  • #27 Differs from normal practice in terms of effort and awareness\n
  • #28 Differs from normal practice in terms of effort and awareness\n
  • #29 Differs from normal practice in terms of effort and awareness\n
  • #30 Differs from normal practice in terms of effort and awareness\n
  • #31 Differs from normal practice in terms of effort and awareness\n
  • #32 Differs from normal practice in terms of effort and awareness\n
  • #33 Differs from normal practice in terms of effort and awareness\n
  • #34 Differs from normal practice in terms of effort and awareness\n
  • #35 Differs from normal practice in terms of effort and awareness\n
  • #36 Differs from normal practice in terms of effort and awareness\n
  • #37 Differs from normal practice in terms of effort and awareness\n
  • #38 Differs from normal practice in terms of effort and awareness\n
  • #39 Differs from normal practice in terms of effort and awareness\n
  • #40 Differs from normal practice in terms of effort and awareness\n
  • #41 Differs from normal practice in terms of effort and awareness\n
  • #42 Differs from normal practice in terms of effort and awareness\n
  • #43 Differs from normal practice in terms of effort and awareness\n
  • #44 Differs from normal practice in terms of effort and awareness\n
  • #45 Differs from normal practice in terms of effort and awareness\n
  • #46 Differs from normal practice in terms of effort and awareness\n
  • #47 Differs from normal practice in terms of effort and awareness\n
  • #48 Differs from normal practice in terms of effort and awareness\n
  • #49 Differs from normal practice in terms of effort and awareness\n
  • #50 Differs from normal practice in terms of effort and awareness\n
  • #51 Differs from normal practice in terms of effort and awareness\n
  • #52 Differs from normal practice in terms of effort and awareness\n
  • #53 Differs from normal practice in terms of effort and awareness\n
  • #54 Differs from normal practice in terms of effort and awareness\n
  • #55 http://farm5.static.flickr.com/4140/4926597784_4392361eb6_b_d.jpg\n
  • #56 Differs from normal practice in terms of effort and awareness\n
  • #57 Differs from normal practice in terms of effort and awareness\n
  • #58 Differs from normal practice in terms of effort and awareness\n
  • #59 Differs from normal practice in terms of effort and awareness\n
  • #60 Differs from normal practice in terms of effort and awareness\n
  • #61 Differs from normal practice in terms of effort and awareness\n
  • #62 Differs from normal practice in terms of effort and awareness\n
  • #63 Differs from normal practice in terms of effort and awareness\n
  • #64 Differs from normal practice in terms of effort and awareness\n
  • #65 Differs from normal practice in terms of effort and awareness\n
  • #66 Differs from normal practice in terms of effort and awareness\n
  • #67 Differs from normal practice in terms of effort and awareness\n
  • #68 Differs from normal practice in terms of effort and awareness\n
  • #69 Differs from normal practice in terms of effort and awareness\n
  • #70 Differs from normal practice in terms of effort and awareness\n
  • #71 Differs from normal practice in terms of effort and awareness\n
  • #72 Differs from normal practice in terms of effort and awareness\n