Top Banner
Software Craftsmanship Alicante
39

How TDD helps me design - A case study

Feb 09, 2017

Download

Software

Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: How TDD helps me design - A case study

Software

Craftsmanship

Alicante

Page 2: How TDD helps me design - A case study

So

ftw

are

C

raftsm

an

sh

ip

Alican

te

Who we are

What we like

Why do we care?

You are a software developer

You like agile, programming

Make community. Join us!

Page 3: How TDD helps me design - A case study

Software

Craftsmanship

Alicante

Meetup http://meetup.com/Software-Craftsmanship-Alicante/

Twitter @AlicanteSwCraft

Slack http://softwarecraftsmanship.slack.com/messages/alicante/

Slides http://slideshare.net/AlicanteSwCraft

Github https://github.com/alicanteswcraft

Page 4: How TDD helps me design - A case study

Enrique Barbeito García

@enriquebarbeito

How TDD helps me designA case study

Software

Craftsmanship

Alicante

Page 5: How TDD helps me design - A case study

@enriquebarbeito

What is this talk about?

Software Craftsmanship Alicante

What is this talk about?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step

About the problem we need to solve

About Test-Driven Development

mytripcar/rentway step by step

What it is, how it works. Why?

TDD insights & rules I followed

How has it been designed?

Page 6: How TDD helps me design - A case study

@enriquebarbeito

What it is, how it works. Why?

Software Craftsmanship Alicante

What is this talk about?What it is, how it works. Why? (1/4)Test-Driven Development in a nutshellWriting mytripcar/rentway step by step

As an API consumer, I want to fetch Company car rental rates so that I can offer them along with my other rates.

❏ We sign an agreement to work with a Company

❏ The Company provides a SOAP webservice system

❏ Our API needs to handle, send, receive information

from↔to their system, parse and improve it, and

return it to the caller/consumer.

❏ Big requirement that may be splitted.

Page 7: How TDD helps me design - A case study

@enriquebarbeito

What it is, how it works. Why?

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why? (2/4)Test-Driven Development in a nutshellWriting mytripcar/rentway step by step

Divide and conquer

❏ XML exchange messages

❏ HTTP connections handling

❏ Object-oriented (de)serialization

❏ SOAP services as an independent services

❏ Webservice client dispatcher

❏ Rental manager, analyzer, orchestrator

❏ API controllers and responses

Divide-conquer.png

Page 8: How TDD helps me design - A case study

@enriquebarbeito

What it is, how it works. Why?

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why? (3/4)Test-Driven Development in a nutshellWriting mytripcar/rentway step by step

tim-eric-mind-blown-gif

Fer Elisabet

Alberto

Page 9: How TDD helps me design - A case study

@enriquebarbeito

What it is, how it works. Why?

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why? (4/4)Test-Driven Development in a nutshellWriting mytripcar/rentway step by step

Thinking bottom-up to make a component

❏ XML exchange messages

❏ HTTP connections handling

❏ Object-oriented (de)serialization

❏ SOAP services as an independent services

❏ Webservice client dispatcher

❏ Rental manager, analyzer, orchestrator

❏ API controllers and responses

mytripcar/rentway

Page 10: How TDD helps me design - A case study

@enriquebarbeito

Test-Driven Development in a nutshell

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshell (1/3)Writing mytripcar/rentway step by step

Key concepts

❏ XP practice stated in 2003.

❏ TDD = TFD + Refactoring

❏ TDD != testing, so it is most about software design

❏ Does not replace architecture or design

❏ It is also a development process with a repetitive cycle.

Repeat with me: red-green-refactor!All-Code-Is-Guilty.jpg

Page 11: How TDD helps me design - A case study

@enriquebarbeito

Test-Driven Development in a nutshell

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshell (2/3)Writing mytripcar/rentway step by step

Key rules when doing TDD

❏ Ensure isolated specs

❏ Arrange. Act. Assert

❏ One verification per test/spec

❏ Write only one spec at a time

❏ Do not lose the green during a refactor step

❏ No debugging (neither output logging) TDD cycle

Page 12: How TDD helps me design - A case study

@enriquebarbeito

Test-Driven Development in a nutshell

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshell (3/3)Writing mytripcar/rentway step by step

Why TDD matters?

❏ Validates (proof) your design

❏ Provides quick feedback (does it work? is it simple

to use? is it well structured? is loosely coupled? ...)

❏ Enables you to: baby steps, focused in-flow, KISS

design, …

❏ Avoids: analysis by analysis, over-engineering, …

❏ Requires more discipline

❏ Gives confidence. Ease the change. Faster refactors

❏ … ERROR 1406: Data too long for column at slide 12^Wy7Hm9.jpg

Page 13: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (1/27)

HEAD is now at 745c932... First commit

Stage 1

Focusing on how to start

Page 14: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (2/27)

HEAD is now at 745c932... First commit

❏ New blank shiny project

❏ It starts with the minimal boilerplate

❏ No src/ tests/ folders

❏ 0% test code. 0% production code.

745c932... First commit

Page 15: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (3/27)

8f9efd6 Add base Client class

❏ The million dollar questions: Where should I start? What should I test?

❏ Several starting points = Strategies of choice

❏ I chose the most basic: rentway is a (SOAP) client

❏ It was a good choice? Does it really matter?

test_client_object_should_be_created(){ assertInstanceOf(Client::class, new Client);}

Page 16: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (4/27)

8f9efd6 Add base Client class

❏ Next million dollar question: What

should I test next?

❏ Listen to feedback each step gives.

❏ Change, rollback, delete code.

test_client_should_list_countries_from_ws()

{

client = new Client('testCompanyCode');

actual = client.getListCountries(true);

expected = this.resourceGetContents('rs_getListCountries.xml');

this.assertEquals(expected, actual);

}

test_client_should_get_multiple_prices_as_xml()

{

client = new Client('testCompanyCode', 'testCustomerCode',

'testUsername', 'testPassword');

actual = client.getMultiplePrices('testPickupDateTime',

'testPickupRentalStation', 'testDropoffDatetime',

'testDropoffRentalStation');

expected = this.resourceGetContents('rs_getListCountries.xml');

this.assertEquals(expected, actual);

}

Page 17: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (5/27)

f7ec0c6 Add serializer dependency

❏ Client redesign: do not pass

credentials. Inject serializer indeed.

❏ How about credentials? Are ignored

for now.

❏ I delete code not related with tests.

test_client_object_should_be_created()

{

this.assertInstanceOf(Client::class, new Client(

new Serializer()

));

}

deleted:

- test_client_should_list_countries_from_ws()

- test_client_should_get_multiple_prices_as_xml()

Page 18: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (7/27)

15cd51e Add message definitions for generic envelopes

Stage 2

Ignore Client and start thinking bottom-up

Focusing on Message (de)serialization

Page 19: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (8/27)

15cd51e Add message definitions for generic envelopes

test_input_message_with_empty_body_serialize() {

actual = this.serializer.serialize(new Envelope(new Body), 'xml');

expected = this.resourceGetContents('rq_EmptyBody.xml');

this.assertEquals(expected, actual);

}

test_input_message_with_empty_body_deserialize() {

data = this.resourceGetContents('rq_EmptyBody.xml');

actual = this.serializer.deserialize(data, Envelope::class, 'xml');

expected = new Envelope(new Body);

this.assertEquals(expected, actual);

}

<?xml version="1.0" encoding="UTF-8"?>

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">

<soap:Body/>

</soap:Envelope>

tests/Resources/EmptyBody.xml

Page 20: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (9/27)

6a3c3d5 Add GetCountries message

test_input_message_getCountries_serialize() {

getCountries = (new Envelope())

->setBody((new Body())

->setGetCountries(new GetCountries('testCompanyCode', true))

);

actual = this.serializer.serialize(getCountries, 'xml');

expected = this.resourceGetContents('rq_getCountries.xml');

this.assertEquals(expected, actual);

}

<?xml version="1.0" encoding="UTF-8"?>

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">

<soap:Body>

<getCountries xmlns="http://⋯/⋯/getCountries">

<companyCode>testCompanyCode</companyCode>

<allCountries>true</allCountries>

</getCountries>

</soap:Body>

</soap:Envelope>

tests/Resources/rs_getCountries.xml

Page 21: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (10/27)

cfab7da Refactor adding a Valuable trait

Key rules when doing TDD (bis)

Ensure isolated specs

Arrange. Act. Assert

One verification per test/spec

Write only one spec at a time

☺ Do not lose the green during a refactor step

No debugging (neither output logging)

Page 22: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (11/27)

7f9c723 Improve message class composition

90d3c98 Finish GetCountries request (de)serialization

use Message\getListCountries as Countries;

test_input_message_getCountries_serialize()

{

request = new Countries\Request(

(new Countries\Body()).setGetCountries(

new Countries\GetCountries('testCompanyCode', true)

));

actual = this.serializer.serialize(request, 'xml');

expected = this.resourceGetContents('rq_getCountries.xml');

this.assertEquals(expected, actual);

}

Page 23: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (12/27)

f6c602f Add factory method to getListCountries request

use Message\getListCountries as Countries;

test_input_message_getCountries_serialize()

{

request = new Countries\Request(

(new Countries\Body()).setGetCountries(

new Countries\GetCountries('testCompanyCode', true)

));

actual = this.serializer.serialize(request, 'xml');

expected = this.resourceGetContents('rq_getCountries.xml');

this.assertEquals(expected, actual);

}

use Message\getListCountries as Countries;

test_input_message_getCountries_serialize()

{

request = Countries\Request::create('testCompanyCode', true);

actual = this.serializer.serialize(request, 'xml');

expected = this.resourceGetContents('rq_getCountries.xml');

this.assertEquals(expected, actual);

}

Page 24: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (13/27)

0cc3e9f Finish MultiplePrices request (de)serialization

package Message\Common

package Message\getMultiplePrices package Message\getListCountries

Page 25: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (14/27)

e6566b0 Refactor around Message test suite test_input_message_getCountries_serialize() {

- request = Countries\Request::create('testCompanyCode', true);

-

- actual = $this.serializer.serialize(request, 'xml');

- expected = $this.resourceGetContents('rq_getCountries.xml');

-

- this.assertEquals(expected, actual);

+ this.runTestSerializeWith(

+ 'rq_getCountries.xml',

+ Countries\Request::class,

+ 'testCompanyCode',

+ true);

}

test_input_message_getCountries_deserialize() {

- data = $this->resourceGetContents('rq_getCountries.xml');

-

- actual = $this.serializer.deserialize(data, Countries\Request::class, 'xml');

- expected = Countries\Request::create('testCompanyCode', true);

-

- this.assertEquals(expected, actual);

+ this.runTestDeserializeWith(

+ 'rq_getCountries.xml',

+ Countries\Request::class,

+ 'testCompanyCode',

+ true);

}

+ /**

+ * @param string filename

+ * @param string classname

+ * @param array ...params

+ */

+ runTestDeserializeWith(string filename, string classname, ...params)

+ {

+ actual = this.deserializeFrom(filename, classname);

+ expected = classname::create(...params);

+

+ this.assertEquals(expected, actual);

+ }

+

+ /**

+ * @param string filename

+ * @param string classname

+ * @param array ...params

+ */

+ runTestSerializeWith(string filename, string classname, ...params)

+ {

+ actual = $this.serializeFrom(classname, ...params);

+ expected = $this.resourceGetContents(filename);

+

+ $this->assertEquals(expected, actual);

+ }

Page 26: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (15/27)

29ef5bd Finish getListCountries response (de)serialization

<?xml version="1.0" encoding="utf-8"?>

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">

<soap:Body>

<getCountriesResponse xmlns="http://⋯/⋯/getCountries">

<getCountriesResult>

<countries>

<diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"

xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">

<Countries>

<Table diffgr:id="testTableId" msdata:rowOrder="0">

<countryID>1</countryID>

<country>testCountry</country>

<ISOCode>testIso</ISOCode>

</Table>

</Countries>

</diffgr:diffgram>

</countries>

</getCountriesResult>

</getCountriesResponse>

</soap:Body>

</soap:Envelope>

use Message\getListCountries as Countries;

test_response_message_getListCountries_serialize() {

# Option 1: Repetitive request/response test design

response = Countries\Response::create(

new Countries\CountryItem(1,'testCountry','testIso','testTableId', 0)

#, new Countries\CountryItem(...), new ...

);

actual = this.serializer.serialize(request, 'xml');

expected = this.resourceGetContents('rs_getCountries.xml');

this.assertEquals(expected, actual);

# Option 2: Using new test design

this.runTestSerializeWith(

'rs_getCountries.xml',

Countries\Response::class,

new Countries\CountryItem(1,'testCountry','testIso','testTableId',0)

);

}

Page 27: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (16/27)

0bdd3c4 Requests and responses refactor to interfaces

Remember the most important thing when refactor ...

☺ Do not lose the green during a refactor step

Page 28: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (17/27)

1b2bce3 Inject http-client dependency in Client

Stage 3

Focusing on Client

Page 29: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (18/27)

1b2bce3 Inject http-client dependency in Client

test_client_should_fetch_getListCountries_xml() {

data = this.resourceGetContents('rs_getCountries.xml');

httpClient = new HttpMockClient([data]);

client = new Client(httpClient, new Serializer);

expected = this.serializer->deserialize(data, Countries\Response::class, 'xml');

actual = client.getListCountries(Countries\Request::create(COMPANY_CODE, true));

this.assertEquals(expected, actual);

}

Design software

❏ Easily mockable

❏ Easily injectable

❏ Easily testable

Page 30: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (19/27)

a31520a Add Client parameters and Client::getListCountries

use Configuration;

use Parameters;

test_parameters_configuration_should_be_created()

{

expectedHttp = new Configuration\Http(BASE_URL, BASIC_AUTH, TIMEOUT);

expectedCredentials = new Configuration\Credentials(

COMPANY_CODE, CUSTOMER_CODE, USERNAME, PASSWORD);

actual = Parameters::create(

BASE_URL,

BASIC_AUTH,

TIMEOUT,

COMPANY_CODE,

CUSTOMER_CODE,

USERNAME,

PASSWORD

);

this.assertEquals(expectedHttp, actual.getHttp());

this.assertEquals(expectedCredentials, actual.getCredentials());

}

Page 31: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (20/27)

a31520a Add Client parameters and Client::getListCountries

setEachTest() {

this.parameters = Parameters::create(

BASE_URL, BASIC_AUTH, TIMEOUT, COMPANY_CODE,

CUSTOMER_CODE, USERNAME, PASSWORD

);

}

test_client_should_fetch_getListCountries_xml() {

data = this.resourceGetContents('rs_getCountries.xml');

httpHandler = new HttpClient();

serializer = new Serializer();

client = new Client(this.parameters, httpClient, serializer);

expected = this.serializer.deserialize(data, Countries\Response::class, 'xml');

actual = client.getListCountries(Countries\Request::create(COMPANY_CODE, true));

this.assertEquals(expected, actual);

}

Page 32: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (21/27)

1b2bce3 Inject http-client dependency in Client

Stage 4

Focusing on isolated Client results

Page 33: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (22/27)

ab251c1 Client methods now return ResultInterface objects

test_result_from_XML_content_should_return_toJson() {

result = this.resultOf(Countries\Response::class, 'rs_getListCountries.xml');

actual = result.toJson();

expected = this.resourceGetContents('rs_getListCountries.json');

this.assertEquals(expected, actual);

}

/**

* Returns a Result object of some ResponseInterface class with some data.

* @param string $classname

* @param string $filename

* @return Result

*/

private function resultOf(string classname, string filename): Result {

contents = this.resourceGetContents(filename);

format = extension_of(filename);

return new Result(classname, contents, format);

}

Page 34: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (23/27)

ab251c1 Client methods now return ResultInterface objects

test_client_should_fetch_getListCountries_xml() {

data = this.resourceGetContents('rs_getCountries.xml');

httpHandler = new HttpClient();

serializer = new Serializer();

client = new Client(this.parameters, httpClient, serializer);

expected = this.serializer.deserialize(data, Countries\Response::class, 'xml');

actual = client.getListCountries(Countries\Request::create(COMPANY_CODE, true));

this.assertEquals(expected, actual->toObject());

}

Page 35: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (24/27)

10c0925 Client refactored to isolated services

Stage 5

Focusing on independent Client services

Page 36: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (25/27)

10c0925 Client refactored to isolated services

test_client_should_access_to_getListCountries_service() {

actual = this->client.getListCountries();

this.assertInstanceOf(ServiceInterface::class, actual);

this.assertInstanceOf(GetListCountries::class, actual);

}

test_client_should_access_to_getMultiplePrices_service() {

actual = this.client.getMultiplePrices();

this.assertInstanceOf(ServiceInterface::class, actual);

this.assertInstanceOf(GetMultiplePrices::class, actual);

}

// ClientTest rewrite. Previous tests moved to ServiceTest

test_should_fetch_error_getMultiplePrices_result() {

data = this.resourceGetContents('rs_getMultiplePrices_ko.xml');

client = this.mockClientWith(data);

expected = data;

actual = client.getMultiplePrices().request(MultiplePrices\Request::create(

new Common\Checkpoint('testDate1', 'testStation1'),

new Common\Checkpoint('testDate2', 'testStation1'),

COMPANY_CODE

));

this.assertEquals(expected, actual.toXml());

}

/**

* Returns a mocked instance of Client.

* @param string $data

* @return Client

*/

mockClientWith(string data) {

$httpClient = new HttpMockClient([data]);

return new Client(this.getParameters(), httpClient, this.getSerializer());

}

Page 37: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (26/27)

cb53cdd Refactor all classes that implements ServiceInterface. Easier to add new ones

Remember the most important thing when refactor ...

☺ Do not lose the green during a refactor step

Page 38: How TDD helps me design - A case study

@enriquebarbeito

Writing mytripcar/rentway step by step

Software Craftsmanship Alicante

What, how, why?What it is, how it works. Why?Test-Driven Development in a nutshellWriting mytripcar/rentway step by step (27/27)

10c0925 Now Client only works as a service dispatcher

shia-magic.gif

Page 39: How TDD helps me design - A case study

Enrique Barbeito García

@enriquebarbeito

FIN. Thanks!Questions?

@AlicanteSwCraft

Please

, follow

!Please, Join us!