Flask RESTful Testing 101



Hi Everyone!

I'm Wicaksono, one of the hacker of allocateam development team.
Time to share about what I've learned like all of my mates right?

This time is about Backend Test Driven Development. We use Flask as the backend framework and the steps to test it is quite simple. Here is the overview:

Write the test - We create test first to break down our app requirements [RED].
Run the test - Obviously, the test should fail because we haven't code anything related to it.
Write the code - With an objective to make all the test pass.
Run the test (again) - If it passed, we can be sure that our code has met the requirements [GREEN].

That's all! Pretty easy for sure!

Because we think our application is not really that complicated to test, we won't use any Flask extension to perform the test, instead, we use unittest package that comes pre-installed with python.

The implementation is quite straightforward as the Flask documentation is well written, so you can read here for more in-depth flask testing knowledge and how-to.

So let's go back to our business on how we implement simple TDD our backend project. For today example, we want to replicate our way to make our RESTful hello world API.

First, let's create our test! Oops, but our test need to get a result or response from a method that we haven't create yet. At least we need the method name, what kind of input it needs, so we can construct our test right? That what stub is for. We can create an empty and unimplemented method called stub, so we can draw more picture about our requirements. We placed our restful API on resources/api that will be imported by our main app. Here is the code:

1
2
3
4
5
6
from flask_restful import Resource


class HelloWorld(Resource):
    def get(self):
        pass


Simple right? Just pass it! So what it's used for?
Because of this kind of thing, now we can be sure that our method:

  • Called Hello World
  • Has a route which is /api/v1/hello
  • Only use get request
  • No input

We still don't know the logic is because we haven't constructed what kind of output that we need, which we'll define on our test.

Now we can create our test easily! We put our tests into a file called tests.py. Here is the code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import unittest
import json
import mario


class TestApi(unittest.TestCase):

    __api_path = '/api/v1'
    def setUp(self):
        self.app = mario.app.test_client()
        self.app.testing = True
    def test_hello(self):
        response = self.app.get('{}/hello'.format(self.__api_path))
        self.assertEqual(
            json.loads(
                response.get_data().decode()), {'hello': 'world'}
            )

if __name__ == "__main__":
    unittest.main()

We create a single class for a specific purpose. For this example, we create a TestApi class to test all API calls on our backend service.

Our setUp() method is for, you know, set up. For now, we only create a new test client.

Our API needs to return something right? We have constructed the stub before so we can be sure which API and method that we'll use. Now, whatever that method will do, I want its input to match our expectation right? That's what tests are for!

Here We put our method result on a variable called 'result'. Our requirements said that our method needs to return a JSON string with key 'hello' and 'world' as the value. So we need to compare our method output with our expected output using assert and if it's the same, it shall pass.

Let's run the test!

BOOM! What's wrong?

The output doesn't match? Your test just failed? Have you realize (I know you have, I just want to make this post more dramatic), for all the time you have spent from the beginning, you only make the test. You haven't created the implementation. Now it's time for you to do the "work". All of the stubs and tests you have made will guide you though. Luckily our requirements only need us to return that JSON object literally without any processing. You know what to do. Here is the code:

1
2
3
4
5
6
from flask_restful import Resource


class HelloWorld(Resource):
    def get(self):
        return {'hello': 'world'}

Done, it's done. Run the test again. It should pass now. If it's not, don't be panic, take a breath and read again :)

Fun, right? See ya on the next post!

0 komentar:

Post a Comment