Se ha denunciado esta presentación.
Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.

How TDD helps me design - A case study

430 visualizaciones

Publicado el

In this talk we are going to show how TDD can help us in our daily work. Through a simple set of steps, we will see how it's possible to write a library without working before on any design. In this way, we will try to explain how are the tests who "suggests" us what to do in each state of development, how they make us focus to take baby steps in order to avoid drawbacks like paralysis by analysis.

Maybe you have done some programming katas (or maybe not) but you do not achieve to see what really TDD brings you. Or it is difficult to you see how to apply it in your daily basis. Whatever your level is, the goal of this talk is trying to show how it is possible to get a software emergent design by only applying some basic TDD rules with tiny unit tests.

--

En este evento vamos a mostrar cómo TDD puede ayudarnos en nuestro trabajo diario. Mediante una sencilla serie de pasos, veremos cómo se puede desarrollar una librería sin tener siquiera un prediseño pensado. De igual forma, intentaremos explicar cómo los tests nos van "sugiriendo" qué hacer en cada estado del desarrollo, cómo nos hacen enfocarnos para tomar pequeños pasos y evitar así, por ejemplo, la parálisis por análisis.

Tal vez hayas hecho algunas katas (o no) y no llegas a ver qué te aporta TDD, o no ves cómo aplicarlo a tu trabajo real, o te parece un poco pérdida de tiempo. Sea cual sea tu nivel, el objetivo es intentar demostrarte cómo practicando una técnica de TDD básica a base sólo de tests unitarios es posible conseguir un diseño emergente.

Publicado en: Software
  • Sé el primero en comentar

How TDD helps me design - A case study

  1. 1. Software Craftsmanship Alicante
  2. 2. Software Craftsmanship Alicante Who we are What we like Why do we care? You are a software developer You like agile, programming Make community. Join us!
  3. 3. 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
  4. 4. Enrique Barbeito García @enriquebarbeito How TDD helps me design A case study Software Craftsmanship Alicante
  5. 5. @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 nutshell Writing 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?
  6. 6. @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 nutshell Writing 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.
  7. 7. @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 nutshell Writing 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
  8. 8. @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 nutshell Writing mytripcar/rentway step by step tim-eric-mind-blown-gif Fer Elisabet Alberto
  9. 9. @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 nutshell Writing 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
  10. 10. @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
  11. 11. @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
  12. 12. @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^W y7Hm9.jpg
  13. 13. @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 nutshell Writing mytripcar/rentway step by step (1/27) HEAD is now at 745c932... First commit Stage 1 Focusing on how to start
  14. 14. @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 nutshell Writing 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
  15. 15. @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 nutshell Writing 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); }
  16. 16. @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 nutshell Writing 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); }
  17. 17. @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 nutshell Writing 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()
  18. 18. @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 nutshell Writing 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
  19. 19. @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 nutshell Writing 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
  20. 20. @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 nutshell Writing 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
  21. 21. @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 nutshell Writing 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)
  22. 22. @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 nutshell Writing mytripcar/rentway step by step (11/27) 7f9c723 Improve message class composition 90d3c98 Finish GetCountries request (de)serialization use MessagegetListCountries as Countries; test_input_message_getCountries_serialize() { request = new CountriesRequest( (new CountriesBody()).setGetCountries( new CountriesGetCountries('testCompanyCode', true) )); actual = this.serializer.serialize(request, 'xml'); expected = this.resourceGetContents('rq_getCountries.xml'); this.assertEquals(expected, actual); }
  23. 23. @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 nutshell Writing mytripcar/rentway step by step (12/27) f6c602f Add factory method to getListCountries request use MessagegetListCountries as Countries; test_input_message_getCountries_serialize() { request = new CountriesRequest( (new CountriesBody()).setGetCountries( new CountriesGetCountries('testCompanyCode', true) )); actual = this.serializer.serialize(request, 'xml'); expected = this.resourceGetContents('rq_getCountries.xml'); this.assertEquals(expected, actual); } use MessagegetListCountries as Countries; test_input_message_getCountries_serialize() { request = CountriesRequest::create('testCompanyCode', true); actual = this.serializer.serialize(request, 'xml'); expected = this.resourceGetContents('rq_getCountries.xml'); this.assertEquals(expected, actual); }
  24. 24. @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 nutshell Writing mytripcar/rentway step by step (13/27) 0cc3e9f Finish MultiplePrices request (de)serialization package MessageCommon package MessagegetMultiplePrices package MessagegetListCountries
  25. 25. @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 nutshell Writing mytripcar/rentway step by step (14/27) e6566b0 Refactor around Message test suite test_input_message_getCountries_serialize() { - request = CountriesRequest::create('testCompanyCode', true); - - actual = $this.serializer.serialize(request, 'xml'); - expected = $this.resourceGetContents('rq_getCountries.xml'); - - this.assertEquals(expected, actual); + this.runTestSerializeWith( + 'rq_getCountries.xml', + CountriesRequest::class, + 'testCompanyCode', + true); } test_input_message_getCountries_deserialize() { - data = $this->resourceGetContents('rq_getCountries.xml'); - - actual = $this.serializer.deserialize(data, CountriesRequest::class, 'xml'); - expected = CountriesRequest::create('testCompanyCode', true); - - this.assertEquals(expected, actual); + this.runTestDeserializeWith( + 'rq_getCountries.xml', + CountriesRequest::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); + }
  26. 26. @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 nutshell Writing 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 MessagegetListCountries as Countries; test_response_message_getListCountries_serialize() { # Option 1: Repetitive request/response test design response = CountriesResponse::create( new CountriesCountryItem(1,'testCountry','testIso','testTableId', 0) #, new CountriesCountryItem(...), 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', CountriesResponse::class, new CountriesCountryItem(1,'testCountry','testIso','testTableId',0) ); }
  27. 27. @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 nutshell Writing 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
  28. 28. @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 nutshell Writing mytripcar/rentway step by step (17/27) 1b2bce3 Inject http-client dependency in Client Stage 3 Focusing on Client
  29. 29. @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 nutshell Writing 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, CountriesResponse::class, 'xml'); actual = client.getListCountries(CountriesRequest::create(COMPANY_CODE, true)); this.assertEquals(expected, actual); } Design software ❏ Easily mockable ❏ Easily injectable ❏ Easily testable
  30. 30. @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 nutshell Writing 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 ConfigurationHttp(BASE_URL, BASIC_AUTH, TIMEOUT); expectedCredentials = new ConfigurationCredentials( 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()); }
  31. 31. @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 nutshell Writing 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, CountriesResponse::class, 'xml'); actual = client.getListCountries(CountriesRequest::create(COMPANY_CODE, true)); this.assertEquals(expected, actual); }
  32. 32. @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 nutshell Writing mytripcar/rentway step by step (21/27) 1b2bce3 Inject http-client dependency in Client Stage 4 Focusing on isolated Client results
  33. 33. @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 nutshell Writing 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(CountriesResponse::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); }
  34. 34. @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 nutshell Writing 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, CountriesResponse::class, 'xml'); actual = client.getListCountries(CountriesRequest::create(COMPANY_CODE, true)); this.assertEquals(expected, actual->toObject()); }
  35. 35. @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 nutshell Writing mytripcar/rentway step by step (24/27) 10c0925 Client refactored to isolated services Stage 5 Focusing on independent Client services
  36. 36. @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 nutshell Writing 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(MultiplePricesRequest::create( new CommonCheckpoint('testDate1', 'testStation1'), new CommonCheckpoint('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()); }
  37. 37. @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 nutshell Writing 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
  38. 38. @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 nutshell Writing mytripcar/rentway step by step (27/27) 10c0925 Now Client only works as a service dispatcher shia-magic.gif
  39. 39. Enrique Barbeito García @enriquebarbeito FIN. Thanks! Questions? @AlicanteSwCraft Please, follow! Please, Join us!

×