Friday, September 21, 2018

Outside-In TDD for the functional python server with Flask and SqlAlchemy




In the following video, you will see a graph and how in time we expand the code using the Outside-in TDD 

Requirements


We have an web API where we should be able to write and read articles and their comments.

Acceptance Test

We save an article:

POST /api/1/save/Article
{
"title":"New article",
"content":"The content"
}
and we receive the article + id
{
id: 13, - can be any value
title:"New article",
content:"The content"
}
We then add 2 new comments:
POST /api/1/save/Article
{
id: 13, - can be any value
comments:[
    {"comment":"This was awesome!"},
    {"comment":"I loved it as well!"},
]
}
response:
{
id: 13, - can be any value
title:"New article",
content:"The content"
}
and now reading the articles and the comments:
POST api/1/query/Article
{
    find: [id,title,content,{comments:[id,comment]}],
    where:{id:13}
}
should return
{"13":{

    id: 13, - can be any value
    comments:{
    "113":{"comment":"This was awesome!"},
    "114":{"comment":"I loved it as well!"},
    }
   }
   }

Outside-In TDD approach


Unlike classicist TDD, in Outside-In, you start the code from the outside, designing it along the way. You start with an acceptance test, then with the unit/integration tests and code needed until you make the acceptance test work:




So let's begin:

Step 1: Acceptance test






When we run it:




2. Step 2 moving in, 


From the outside, writing the first test for the service that will handle the url. A little bit of design, while writing the test: we consider, that we will use a function that will get the object from the database if we have an id in the json or will give us a brand new Article object if we have no id in the json.













Now if we properly do the code, the test will pass:








Step 3: moving furher in, database_services and get_database_object


However, we did not implement the get_database_object, which will work with the database. So for that we'll write the tests first:


or:



and






Now let's write the code to make it pass:







We write in fact 2 tests to cover both scenarios:  when editing or when adding a new article.


Step 4: now we move back and extend the api


We design it to read the data from json and transfer it to the database object. For that we'll use a function transfer_from_json





Once it passes, we move further:

Step 5: writing the transfer service













Now we make the test pass:





Step 6: back to extending the api

We now need to save the object with the data from the json into the db



For this, we extend the test:



and make it pass, then we move back to implementing using TDD the save_database_object

Step 7 extending the database_service to also save



Once we write the test, the code and make it pass:



we move back to the api

Step 7 extend the api





Step 8: TDD the extension:



Until we make it pass:




After a few more intermediary steps:

...

We will TDD all the necessary code to make the acceptance test pass:




Wednesday, August 29, 2018

Outside-In TDD the bank kata in Clojure/ClojureScript


Code: https://github.com/DanBunea/katas/tree/master/outside-in%20tdd%20bank%20kata 


In "London school" TDD or Outside-In we start from an acceptance test, then write unit tests. This is better explained as the double loop of TDD:




Some time ago, I found this great step by step explanation of Outside-In TDD, by Sandro Mancuso from Codurance:



However it was all Java and obviously OOP, so I wanted do the same thing but in Clojure, and fully functional: functions and not classes. So here we go:





Restrictions (adapted for functional):

1. Start with a module account that has the following functions:

(defn deposit [account amount] ...)
(defn withdraw [account amount] ...)
(defn print-statement [account] ...)

Threat the methods, as if they cannot return any values!

2. You're not allowed to add any new functions to the module
3. Strings and integeres are to be used for dates and amounts for simplicity
4. Don't worry about spacing in the statement printed at the console


Step 1: The acceptance test (the big loop)





we need to take this test, unit it fails for the right reason: the results of the console-print are not what was expected, so we write two modules:



and account.cljs:



and now the test:



Step 2: The first unit test - account module - the business layer (the small loop)


The very first module we should test is account, so we make account_tests. For account, we could test the deposit function. Considering we might have a module which will actually do the depositing/withdrawals in a database or something, that module will be separate and we'll call it transaction-repository. So we could test the interactions between modules like:



We expect that when we invoke deposit from the account module, the add-deposit from the transaction-repository will be invoked.



we make it pass,



and continue with withdraw and later with print-statement for which we'll do a separate module: statement-printer. The final test will be:



and:



and:




Step 3: the unit tests for the "data layer" (the small loop)

Like for the account, we start with a simple test, where we make a deposit and check if all-transactions will return it. We also need to make sure it's on a certain date. We make the test pass, and we move on to withdraw, ending up with:



and:



and:




Step 4: Unit testing the statement-printer (the small loop)


Now, we'll go straight to the results, even though I used TDD to get to it:

The tests:




The code obtained:



The tests result:



Let's not forget that our console-print function is still mocked, and the implementation is:



and we can now implement it:



Tests still pass.

Step 5: Going back to the acceptance test (the big loop)


We will modify the test, mocking the dates delivered, as well as console-print:




And when we run all the tests:




Conclusion


Starting from the outside, we could test-drive the design and the code.

acceptance-test -> 
      account-tests->account->
      transaction-respository-tests->transaction-repository->
      statement-printer-tests->statement-printer->console