This article is the continuation of my first post about TDD in PyCharm with Python and Django. Here I will outline how to do behaviour driven development in PyCharm can be used in a scrum environment. However, scrum itself is another topic, but I will use these processes as a canvas.
I am not an affiliate of JetBrains neither any of their products. In fact, I am a little bit disappointed with the most recent release of YouTrack and the number of critical bugs that they still have after months of work. I am not saying that PyCharm or YouTrack or any other JetBrains products are flawless (clearly not) but these tools I found the best for my team and me.
Before getting to specs I need to commit requirements.txt file
Let’s start with defining our goals. Previous article defined requirements quite briefly so let’s write it a little
bit better. There is another blog post that
describes the way to describe the requirements which I will be using. For the time being, I will use
Gherkin Syntax to create a several features.
In the previous post we’ve built an API so it’s time to do a catalogue now.
Feature: products catalogue To provide access to products As Any User I want to browse products catalogue Scenario: products on homepage Given there are set of Products in Database | id | title | description | price | | 1 | Product 1 | description 2 | 10 | | 2 | Product 2 | description 3 | 20 | When I go to / Then the page includes "Product 1" And the page includes "Product 2" Scenario: product detail page Given there is a Product in database | id | title | description | price | | 1 | Product 1 | description 2 | 10 | When I go to /products/1/ Then the page includes "Product 1" And the page includes "description 2" And the page includes "10$"
This is a valid behave feature file which defines a catalogue feature and two scenarios of how can I use this feature. By the way, adding a file in PyCharm is Option ⌥ + Command ⌘ + A. Commit is Command ⌘ + K. Find commit here @850c187.
Good start, the suit can be ran with Control ⌃ + Option ⌥ + R in the PyCharm.
Of course, tests are failing which is good.
The error message is
Traceback (most recent call last): ... behave.configuration.ConfigError: No steps directory in "/Users/andrey/Projects/test_project/test_project/features"
Which means there is nothing to execute yet.
Making behave tests work
Ok, now let’s generate implementation steps for the Feature. First, let’s create
features/steps folder with
by navigating to highlighted expressions in feature file and press **Option ⌥** + **Enter**. PyCharm suggest you create a step for particular expression. ![Create step defenition](/images/bdd-with-python/Create step definition in PyCharm.png) For some reasons *Create all steps definition* does not work as expected. It creates some definitions for certain phrases but not all of them. Must be a bug or something. So I used *Create step definition* option. Now, we have blank steps defined for every step. Let's run them [@850c187d](https://git.anvileight.com/andrey.zarubin/tdd_example/commit/b2c4ee24de54b6fbd36124d911f41533eca3ffc0) ![Run empty steps](/images/bdd-with-python/Behave implementation steps defined.png) They are passing now. Which is expected but not very good. I don't know why PyCharm team decided to put ```pass``` as default implementation like this:This approach creates whole bunch of problems. Particularly, I don't like it because tests are passing now which makes it easy to forgot some of the implementation steps. I would say assert False would be much better as a default:
@step('the page includes "Product 2"') def step_impl(context): """ :type context: behave.runner.Context """ passI have modified steps that suppose to assert something to assert False. Now tests are failing. [@b2c4ee24](https://git.anvileight.com/andrey.zarubin/tdd_example/commit/b2c4ee24de54b6fbd36124d911f41533eca3ffc0) ![Behave tests are failing again](/images/bdd-with-python/Behave tests are failing again.png) ## Django context configuration It is time to configure behave. Let's run behave tests inside the Django context. To do so we have to add environment.py and several lines in it (@043ac7b)[https://git.anvileight.com/andrey.zarubin/tdd_example/commit/043ac7b76226dee74453f6cf923110bfeb5720f2]:
@step('the page includes "Product 2"') def step_impl(context): """ :type context: behave.runner.Context """ assert FalseAnother good thing would be to create setup and teardown methods just like typical test cases. In these methods, we will be creating test database each time we run tests and delete it when tests finished. There are many other things that could be done but let's keep it simple for now. In this example, we will use DiscoverRunner, but I think it might not work for older versions of Django:
import os import django os.environ['DJANGO_SETTINGS_MODULE'] = 'test_project.settings' def before_all(context): django.setup()We have database managed now [@6c471b3](https://git.anvileight.com/andrey.zarubin/tdd_example/commit/6c471b30a3253667a5c97ccb8dba63232e5cc69e) And one more thing that could be added is an ability to run scenarios in transactions and roll them back after each scenario. That's fairly simple too:
from django.test.runner import DiscoverRunner def before_all(context): django.setup() context.test_runner = DiscoverRunner() context.test_runner.setup_test_environment() context.old_db_config = context.test_runner.setup_databases() def after_all(context): context.test_runner.teardown_databases(context.old_db_config) context.test_runner.teardown_test_environment()I think we are pretty much done with a configuration so let's get into implementation of the steps. ## Behave scenarios implementation One nice thing about PyCharm is that it highlights patterns. I've modified my test for common case and PyCharm automatically recognised which parts of the statement are variables. Now I can re-use that ```@given``` statements for any model to create my test data. [@6058808c](https://git.anvileight.com/andrey.zarubin/tdd_example/commit/228114590f0f20aab295a53f9c9b83c8e4d773f7) ![Highlighting of the variables in PyCharm](/images/bdd-with-python/Highlighting of the variables in PyCharm.png) The next step will be implementing the navigation. One thing that is missing is the server we can send requests to. But since we have everything in place it is only a matter of changing a test case class in ```environment.py``` to ```LiveServerTestCase```. Sending requests to that server will be using ```urllib```. [@2281145](https://git.anvileight.com/andrey.zarubin/tdd_example/commit/6579060bc0914334fb177f0c1b8ecee51a2a1cee)
from django.test.testcases import TestCase def before_scenario(context, scenario): context.test = TestCase() context.test.setUpClass() def after_scenario(context, scenario): context.test.tearDownClass() del context.testLet's run it! ![Navigation implemented](/images/bdd-with-python/Navigation implemented.png) We've got an exception:
@when("I go to /") def step_impl(context): """ :type context: behave.runner.Context """ context.response = urllib.request.urlopen( context.test_case.live_server_url)
HTTPError: HTTP Error 404: Not Found ```
That is a good sign. This means we actually have a test server running and are able to request it. The last step is to assert response. Let’s do the same with the second scenario. I’ve refactored a little bit and added implementation for the second scenario @2281145
Generally speaking it is possible to write behave tests in such a way that allows you to reuse your code a lot. But that’s another subject.
Actual feature implementation
The actual implementation took maybe half of the hour in total. Since it is just a demo, my intent was to show BDD practices rather than coding of the features. Finally I have tests passing at my last commit @9cbdbf31 I have added actual API endpoints and corresponded implementation.