Hello Pyramid [Part 1] – Test First

Posted by – December 21, 2012

I’ve looked for a alternative web framework similar like Ruby on Rails and based on Python, not much configuration but able to be full configuration, possible to manage a routing and includes testing tools. I found Django, web2py and Pyramid. I’ve already known Django because I worked on it for 3 years. Also Pyramid looks more lightweight than web2py so I chosed Pyramid.

After spending 2-3 weeks with Pyramid, I realized that it’s not too complicated and there is good documents. But there is something a bit hard if you’ve worked on Rails is choosing an ORM. I spent a bit long to search for the orm and I chosed SQLAlchemy. Actually, I don’t know any thing about SQLAlchemy. Just heard that it’s quite light, not too heavy like ActiveRecord. After an experiment, it’s not too bad. Just need to used to how to read the document. Need to know where what you’re looking for. As far as I’m a developer, I’ve never found any document starts with writing test. I’s quite impressive to me. I actually want BDD like RSpect but I’m a newbie. Still finding now.

I’ll create a simple todolist and write the test covers CRUD. My tests should be failed totally. Once I implemented, they would be passed all. In order to to start, let’s follow the installation then you’ll see 2 testing tools which are Unittest and Test coverage. Unittest is a Python builtin library(PyUnit) and test coverage is a combination of Coverage and nose to customize the testing files in the projects.

Before writing test cases, let’s see how the testing in Pyramid are… there are 3 kinds of tests.

  • Unit test is to test the smallest unit like functions or variables.
  • Integration test is a group of tests that tests the units as a component that the units work together.
  • Functional test is like the integration test which runs the application literally.

My test cases

What I want to test..

  • C – Create should post a request to a function and return back a dict that contains todo data.
  • R – Read(also Edit and list) should get the data that can be a dict of todo or a list of the dict of todos.
  • U – Update supposed to be put. I wanted to make it restfully but I’m a newbie. Let’s set it as the post request and return back the dict of updated todo.
  • D – Delete should post(delete in restful) the id and return back the deleted id

What I concern about these test cases is what the test retrieves in the provided functions. It actually is the real http response to be retrieved like http status is 200 or content type is json but this kind of concern is the functional test. There is another server library to mock the http request and posts to the full stack of the application if you want to test.

Set up and Tear down

A little more introduction about unittest in Pyramid. There are set up and tear down method for putting the code to be run when the test starts and run after the test ends. For example, testing.setUp() is the method to prepare the application environment for testing or the group of code to initial the data for testing in the set up method. Also after finish testing, remove that data in the tear down method.

class TestIntegrationTodolist(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
        from sqlalchemy import create_engine
        #engine = create_engine('sqlite:///todolist.sqlite')
        engine = create_engine('postgresql://dev:dev@localhost:5432/todolist_test')
 
        DBSession.configure(bind=engine)
        Base.metadata.create_all(engine)
        # the transaction commited after added so rolling back doesnt work on Postgres
        #with transaction.manager:
        instances = (
            Todo(task='Second task', priority=0),
            Todo(task='Thrid task', priority=5),
            Todo(task='First task', priority=10),
            Todo(task="Done task", priority=5, done_at=datetime.now())
        )
        DBSession.add_all(instances)
 
    def tearDown(self):
        DBSession.remove()
        testing.tearDown()

it starts with conneting to PosgreSQL then create the connection between Pyramid and SQLAlchemy. After that is the group of code to initial the data for test. In the end, just remove the data.

Read

To Read in my todolist, there is 1 task which is listing all of the todo. What I excpect from the test is after request, it should return a list of dict of todos.

    def test_index_pass(self):
        """ test_index_pass """
        from todolist.controllers.todos import index
        request = testing.DummyRequest()
        response = index(request)
        self.assertEqual(len(response['todos']), 3)
        self.assertEqual(len(response['done_todos']), 1)
        # test ordering
        self.assertEqual(response['todos'][0].task, "First task")
        self.assertEqual(response['todos'][2].task, "Second task")

There is an addiional done_todos which is a list of dict to contains the done todos. I intended to show them in another list in the same page. done_at is the key to specific which todos are done.

One facility method is testing.DummyRequest() to create a request as a parameter to send to the get function.

Create
    def test_create_pass(self):
        """ test_create_pass """
        from todolist.controllers.todos import create
        params = {'task':'New task', 'priority':1}
        request = testing.DummyRequest(params=params, post=params)
        response = create(request)
        self.assertTrue(response['id'])
        self.assertEqual(response['task'], params['task'])
        self.assertEqual(response['priority'], params['priority'])
 
    def test_create_fail(self):
        """ test_create_fail """
        from todolist.controllers.todos import create
        params = {'task':"", 'priority': "low"}
        request = testing.DummyRequest(params=params, post=params)
        response = create(request)
        self.assertTrue(response['errors'])
        self.assertEqual(len(response['errors']['priority']), 1)
        self.assertEqual(len(response['errors']['task']), 1)
        self.assertEqual(response['errors']['task'], ['Please enter a value'])
        self.assertEqual(response['errors']['priority'], ['Please enter an integer value'])

incase of create, I provided 2 cases, pass and fail. If fail then show the notified message.

To create the post request with DummyRequest is just put the post value as a parameter. If want to know what are the parameters, here is the doc. You’ll see how to create a session too.

Update
    def test_update_pass(self):
        """ test_update_pass """
        from todolist.controllers.todos import update
        params = {'task':'Updated task', 'priority':1}
        # provide the todo with id
        todo = DBSession.query(Todo).first()
        request = testing.DummyRequest(params=params, matchdict={'id':todo.id}, post=params)
        response = update(request)
        updated_todo = DBSession.query(Todo).filter_by(id=todo.id).one()
        self.assertEqual(response['task'], params['task'])
        self.assertEqual(updated_todo.task, params['task'])
 
    def test_update_fail(self):
        """ test_update_fail """
        from todolist.controllers.todos import update
        # test query not found
        request = testing.DummyRequest(params={}, matchdict={'id':1}, post={})
        response = update(request)
        self.assertEqual(response['errors'], "No todo id: 1")
 
        # test validation
        params = {'task':"", 'priority': "low"}
        todo = DBSession.query(Todo).first()
        request = testing.DummyRequest(params=params, matchdict={'id':todo.id}, post=params)
        response = update(request)
        self.assertTrue(response['errors'])
        self.assertEqual(len(response['errors']['priority']), 1)
        self.assertEqual(response['errors']['task'], ['Please enter a number'])

It almost similar to create. One differece is a validation. I expected that priority should be a number but I put a string instead. It should display the message to let you know it’s not a number

Delete
    def test_delete_pass(self):
        """ test_delete_pass """
        from todolist.controllers.todos import delete
        todo = DBSession.query(Todo).first()
        request = testing.DummyRequest(params={},matchdict={'id':todo.id}, post={})
        response = delete(request)
        todo_count = DBSession.query(Todo).count()
        self.assertIsNot(todo, DBSession.query(Todo).first())
        self.assertEqual(response['messages'], '%s has been deleted' % todo.task)
        self.assertEqual(todo_count, 3)
 
    def test_delete_fail(self):
        """ test_delete_fail """
        from todolist.controllers.todos import delete
        # test query not found
        request = testing.DummyRequest(params={}, matchdict={'id':1}, post={})
        response = delete(request)
        self.assertEqual(response['errors'], "No todo id: 1")

These are for 2 cases too. If fails then shows a message says it has been deleted. Once deleted, the total amount should be decresed.

Then run the all codes above with nosetest, I got this…

Failed nosetest

Failed nosetest

There is a few errors. It imported the packages but not found. So I needed to write these functions which are in the next post.
Another thing in the picture above is a result of code coverage.

0 Comments on Hello Pyramid [Part 1] – Test First

Respond

Respond

Comments

Comments