Wednesday, January 22, 2014

Using Behave with Pyramid

A guest post by Wayne Witzel III who resides in Florida and is currently working for Slashdot Media as a Senior Software Engineer building backend systems using Python, Pylons, mongodb, and solr. He can be reached at .



The Pyramid web framework provides a great testing module (pyramid.testing) that gives you a lot of convenient methods for setting up and tearing down your application. The module also provides you with easy access to mock request and resource objects that you can use in your unit and functional tests.




behave is a behavior-driven development library for Python. Behavior-driven development (or BDD) is a software development technique that encourages collaboration between developers, QA, and non-technical or business participants in a software project. It does this by using a natural language called Gherkin. This language allows you to create human readable tests that you can also use and run as feature tests against your application.



This post will show you how to get started using Pyramid and behave together. I recommend you download and setup the basic project skeleton (pyramidbehave) that I have created for this post. I will show you how to do this in the "Our First Feature" section.



See: repository.



QUICK BEHAVE PRIMER



behave tests are written in .feature files. You create a high-level feature, like arithmetic.feature, and inside that feature file you implement scenarios using the Gherkin language I mentioned before. Here is a trivial example:



Feature: Basic arithmetic In order to produce the correct answer I want to call the correct method the users requests Scenario: Adding two numbers Given the user selects addition When the user provides the numbers 3 and 4 Then the result should equal 7



After you have done that, you implement the actual Python steps to execute the scenario and make any assertions:



@given(u'the user selects addition') def givenaddition(context): context.method = sum @when(u'the user provides the numbers 3 and 4') def whennumbers(context): context.result = context.method([3,4]) @then(u'the result should equal 7') def thenequal(context): assert context.result == 7



You use the behave decorators (given, when, then) to connect your scenario text to the step methods. Each method must accept a context. The Context object is generated by behave and allows you to store any arbitrary data you need for your steps. We will be working with the context in the examples that follow.



Now that you have a features file and a set of steps, you assemble them in a directory as basic.feature and steps/basic.py and run the behave command:



pyramidbehave/tests/features/basic.feature . 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined Took 0m0.000s



OUR FIRST FEATURE



To get started you should checkout the pyramidbehave git repository and get everything installed.



git clone https://github.com/wwitzel3/pyramidbehave.git cd pyramidbehave pip install -e .



This will install all of the required packages for you and allow you to easily follow along. For our first feature you will want to be on step1 in the repository:



git checkout step1



We will create behave.ini at the root of the project. We do this to control how the test runner will format the output and point behave to our features directory. In our case it looks like this:



[behave] defaultformat=plain paths=pyramidbehave/tests/features loggingclearhandlers=yes



Now we will create our first feature. We will do this by creating the pyramidbehave/tests/features/todo.feature. This will define the feature for adding new items to our todo list.



Feature: Create todo In order to have a todo in my todo list As a user I will create a new todo Scenario: create a new todo Given my task is "new todo task" When I create a new todo entry Then I should find new todo in todo table



We can save that file and run the behave command. We will see the output for the failing feature and a message. The message informs us that we are missing steps and will generate some helpful code for us to make it easy to begin the process of implementing each step for this feature's scenario. We will do that by creating pyramidbehave/tests/features/steps/todo.py.



First, we will implement the given step. This step is really here to allow us to setup the context we need for the next step. In our case we need to setup some database access and provide access to that for each step. Use the given step as a place for putting the system in to a known state, so things like database setup or earlier actions like logging in can go here:



@given(u'my task is "new todo task"') def giventask(context): configuri = 'development.ini' settings = getappsettings(configuri) engine = enginefromconfig(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.createall(engine) context.db = DBSession



Now we move on to our when step. This step will take our context.text data "new todo task" and actually create our new task. We will then assign it to the context so the then step can perform its assertions. Consider the when step as a place where the key action takes place, or things like interacting with a view or web page (we do that later).



@when(u'I create a new todo entry') def whencreatetodo(context): todo = Todo(task=context.text, completed=False) context.db.add(todo) context.db.flush() context.todo = todo



Finally we are in our then step. This step takes the todo entry we assigned to the context, pulls a listing or all of the todos, and asserts that the context todo is in the listing. The then step is for observing outcomes and verifying that the data we expect is present and correct.



@then(u'I should find new todo in todo table') def thenlookuptodo(context): todo = context.db.query(Todo).get(context.todo.id) assert todo



You can now checkout step1.complete and run behave to see the successful results and review the code including imports:



git checkout step1.complete behave



A BETTER WAY



Now that you've created your first feature, right away you can see there will be some problems. Mainly the setUp and tearDown methods you normally have are not present and there is no clear way for how we actually test views. Fortunately there is a better way to setup behave for testing with Pyramid and that is by using a custom environment.py file. This file is a behave convention that allows us to have setUp and tearDown steps like registering a testing database and bootstrapping our application.



Since behave supports many different steps, there are a few more options we have access to in our environment.py over what you traditionally see with other testing libraries.



Lets get started by checking out step2 in the pyramidbehave repository.



git checkout step2



You will now have a test.ini in the root directory. We are using this to define a database that is safe to tear down and rebuild after each scenario. We can now begin the process of defining our environment for behave.



First, we will setup the beforeall. Code contained within this method will run only once for the entire set of features:



def beforeall(context): context.settings = getappsettings('test.ini') context.engine = enginefromconfig(context.settings, 'sqlalchemy.')



This creates our settings, using our test.ini, which uses an in-memory database, and sets up the sqlalchemy engine. Now we can setup the beforescenario method. This, as you might assume, is called before each scenario is run. Think of this as your traditional setUp method. We will create our database here:



def beforescenario(context, scenario): context.configurator = testing.setUp(request=testing.DummyRequest(), settings=context.settings) Base.metadata.createall(context.engine) DBSession.configure(bind=context.engine) context.db = DBSession



Finally, we can create our afterscenario. You can think of this like you would tearDown. We will clean up our database session and run the Pyramid testing tearDown here:



def afterscenario(context, scenario): testing.tearDown() context.db.remove()



Now that we have our newly setup environment.py, we can go ahead and refactor the existing tests to take advantage of it and after that, we will write a feature that tests our Pyramid view.



REFACTORING



We can make our old test take advantage of the new environment.py file by simply deleting some lines of code from the current test. We will also make our Given a little more flexible by dynamically passing in the todo text. These changes look like this:



@given(u'my todo is {task}') def giventodo(context, task): context.todo = Todo(task=task, completed=False) @when(u'I create a new todo entry') def whencreatetodo(context): context.db.add(context.todo) context.db.flush() @then(u'I should find todo in todo table') def thenlookuptodo(context): todo = context.db.query(Todo).get(context.todo.id) assert todo



As you can see, we have a nice clean set of tests around creating a new todo task in the database and we've removed all of the boiler plate setup out in to the environment.py.



VIEW TESTING



Now that we have our environment setup for Pyramid testing, we can actually test our view. Let's create a very simple view in views.py called todo that just fetches out a given todo from the database using the id.



@viewconfig(routename='todo', renderer='todo.mako') def todo(request): todo = DBSession.query(Todo).filter(Todo.id == request.matchdict['id']).one() return {'todo':todo}



We will also add a simple route to init.py:



config.addroute('todo', '/{id:d+}')



Now in our todo.feature we can create a scenario that will represent using this view.



Scenario: view a todo task Given my todo is "another todo task" When I create a new todo entry And I visit the URL /1 Then I should see my todo



Next we take advantage of behave, allowing us to parameterize our match text, so we can easily fetch the ID from the feature, instead of hard coding it in to the test. Then we use the same familiar testing tools from Pyramid to implement our new when step.



@when(u'I visit the URL /{id}') def whenvisiturl(context,id): from pyramidbehave.views import todo context.configurator.addroute('/','/{id:d+}') request = testing.DummyRequest() request.matchdict['id'] = int(id) context.result = todo(request)



Then finally we implement our new then step which in this case, just ensures that the view returned a dictionary with a todo item in it.



@then(u'I should see my todo') def thenviewtodo(context): assert context.result['todo']



You can now checkout step2.complete and run behave to see the tests pass.



git checkout step2.completed behave



WRAPPING UP



You should now be comfortable enough to get started with using behave for your own Pyramid projects. I encourage you to reference the repository as well as the and documentation.



For more details about Python, see the Safari Books Online resources referenced below.



Not a subscriber? Sign up for a .



SAFARI BOOKS ONLINE HAS THE CONTENT YOU NEED



will help you quickly write efficient, high-quality code with Python. It's an ideal way to begin, whether you're new to programming or a professional developer versed in other languages.



fully covers the building blocks of Python programming and gives you a gentle introduction to more advanced topics such as object-oriented programming, functional programming, network programming, and program design. New (or nearly new) programmers will learn most of what they need to know to start using Python immediately.



shows you how to move from a theoretical understanding of offensive computing concepts to a practical implementation. This book demonstrates how to write Python scripts to automate large-scale network attacks, extract metadata, and investigate forensic artifacts. It also shows how to write code to intercept and analyze network traffic using Python, craft and spoof wireless frames to attack wireless and Bluetooth devices, and how to data-mine popular social media websites and evade modern anti-virus.
Full Post

No comments:

Post a Comment