Getting started with behavior-driven development using Python, python-driven development
Behavior-Driven Development (BDD) is an excellent Development model. It can help developers develop good habits of day-to-day settlement, so as to avoid or even eliminate the "Last Minute" situation, so it is of great benefit to Improve the Quality of code. The test structure and design form combined with the Gherkin syntax makes it extremely easy for all members of the team, including non-technical personnel.
All code must be tested, which means that the system flaws are minimized or even zero when going online. This needs to be matched with the complete test suite, from the overall control of software behavior, so that detection and maintenance can be carried out in an orderly manner. This is the charm of BDD. Isn't it exciting?
What is BDD?
The concept and theory of BDD are derived from TDD (test-driven development). Similar to TDD, the theoretical point is to write a test before coding. The difference is that in addition to the unit test for fine-grained testing, the acceptance test (acceptance tests) is also used throughout the beginning and end of the program. Next, we will introduce the Lettuce testing framework.
The BDD process can be summarized as follows:
- Compile a defect Acceptance Test
- Compile a defect unit test
- Pass Unit Test
- Reconstruction
- Pass Acceptance Test
In each function, repeat the preceding steps if necessary.
BDD in Agile Development
In Agile development, BDD is even better.
If the new features and new requirements of the project change every one or two weeks, the team needs to work with fast-paced testing and coding. Acceptance and unit testing in Python can help achieve this goal.
The acceptance test is well known for the use of an English "feature" description file containing tests and individual tests. The advantage of this is that the entire project team is involved, in addition to developers, non-technical members such as managers and business analysts who are not involved in the actual test process.
The compilation of feature files follows the rules that are fully readable, so that both technical and non-technical members can clearly understand and receive them. If only unit tests are included, the requirement analysis may be incomplete or consensus cannot be reached. The biggest advantage of acceptance testing is its strong applicability, which can be used freely regardless of the project size.
Gherkin syntax
Generally, Gherkin is used to write for acceptance testing. Gherkin is from the Cucumber framework and written in the Ruby language. The Gherkin syntax is very simple. In Lettuce Python, the following eight points are used to define features and tests:
- Given hypothesis
- When time
- Then next step
- And
- Feature features:
- Background:
- Outline of Scenario Outline:
Install
You can install the Lettuce package using the pip install statement commonly used in Python:
$ pip install lettuce
$ Lettuce/path/to/example. feature is used to run the test. You can run only one test file at a time, or submit a directory name to run all files in the directory.
To make writing and using the test easier, we recommend that you install nosetests:
$ pip install nose
Feature File
The feature file is written in English and the content is the scope of the program covered by the test. In addition, you can create a test task. In other words, in addition to writing tests, you must standardize yourself to write good documents for all aspects of the program. The advantage of doing so is to make yourself aware of the code and what to do next. As the scale of the project expands, the importance of the document will gradually become apparent. For example, review a function or trace back an API call.
Next, we will create a feature file with an instance in TDD. This example is a simple calculator written in Python. It also demonstrates the basic method of testing. We recommend that you create two folders: one is app, which is used to place code files such as calculator. py, and the other is tests, which is used to place feature folders.
calculator.py: class Calculator(object): def add(self, x, y): number_types = (int, long, float, complex) if isinstance(x, number_types) and isinstance(y, number_types): return x + y else: raise ValueError
Calculator. feature, feature file under the tests/features directory
Feature: As a writer for NetTuts I wish to demonstrate How easy writing Acceptance Tests In Python really is. Background: Given I am using the calculator Scenario: Calculate 2 plus 2 on our calculator Given I input "2" add "2" Then I should see "4"
From this example, it is not difficult to see that the description of the feature file is very straightforward and can be understood by all members.
Three key points of the feature file:
- Feature block: Describes the program content covered by the test group. No code is executed here, but the reader can understand what kind of feature testing is going on.
- Background block: runs in each Scenario (Scenario) block in the feature file. This is similar to the SetUp () method used to compile the creation code, such as compiling conditions and locations.
- Scenario block: This is used to define the test. The first line is used as a further description of the document, followed by the specific content of the test. Isn't it easy to write a test in this style?
Step (Steps) File
In addition to feature files, STEP Files are also required. This is a time to witness a miracle ". Obviously, the feature file itself does not produce any results; it requires STEP Files to map one-to-one with the Python Execution Code in order to have the final result output. Here the regular expression is applied.
Regular Expression? Isn't it too complicated? In fact, in the BDD world, regular expressions are often used to capture the entire string or capture variables from a row. Therefore, practice makes perfect.
Regular Expression? Isn't it too complicated to use in testing? In Lettuce, it won't be, but it is very simple.
Follow these steps to compile the file:
from lettuce import *from nose.tools import assert_equalsfrom app.calculator import Calculator @step(u'I am using the calculator')def select_calc(step): print ('Attempting to use calculator...') world.calc = Calculator() @step(u'I input "([^"]*)" add "([^"]*)"')def given_i_input_group1_add_group1(step, x, y): world.result = world.calc.add(int(x), int(y)) @step(u'I should see "([^"]+)"')def result(step, expected_result): actual_result = world.result assert_equals(int(expected_result), actual_result)
The first part of the file is the standard import method. For example, the access to Calculator and the import of Lettuce tools, as well as the import of assert_equals determination methods in the nosetest package. Next, you can start to define the steps for each row in the feature file. As mentioned above, regular expressions are often used to extract the entire string, except for sometimes accessing variables in certain rows.
In this example, @ step plays the role of decoding and extraction; the meaning of u letters is the unicode encoding method for expression execution. Then, use a regular expression to match the referenced content. Here is the number to be added.
Then, input the variable to the Python method. The variable name can be defined at will. Here, x and y are used as the input variable name of the calculator add method.
In addition, we need to introduce the use of the world variable. World is a global container that allows variables to be used in different steps in the same scenario. Otherwise, all variables are available only for the method in which they are located. For example, the Operation Result of the add method is stored in a step, and the result is determined by another step.
Feature execution
After the feature file and step file are completed, you can run the test to check whether the test can be passed. The Lettuce execution method of the built-in test run machine is very simple, for example
lettuce test/features/calculator.feature: $ lettuce tests/features/calculator.feature Feature: As a writer for NetTuts # tests/features/calculator.feature:1 I wish to demonstrate # tests/features/calculator.feature:2 How easy writing Acceptance Tests # tests/features/calculator.feature:3 In Python really is. # tests/features/calculator.feature:4 Background: Given I am using the calculator # tests/features/steps.py:6 Given I am using the calculator # tests/features/steps.py:6 Scenario: Calculate 2 plus 2 on our calculator # tests/features/calculator.feature:9 Given I input "2" add "2" # tests/features/steps.py:11 Then I should see "4" # tests/features/steps.py:16 1 feature (1 passed)1 scenario (1 passed)2 steps (2 passed)
The output of Lettuce is very neat. It clearly shows which feature file code is executed, and then highlights the successfully executed rows in green. In addition, the running feature file and row number are displayed, which is helpful for finding the defect lines in the feature file when the test fails. The output end is a summary of the features, scenarios, number of steps executed, and number of passed results. In this example, all tests have passed. But what will Lettuce do if an error occurs?
First, you must modify the calculator. py code and change the add method to two subtraction values:
class Calculator(object): def add(self, x, y): number_types = (int, long, float, complex) if isinstance(x, number_types) and isinstance(y, number_types): return x - y else: raise ValueError
Run it again to see how Lettuce describes the error:
$ lettuce tests/features/calculator.feature Feature: As a writer for NetTuts # tests/features/calculator.feature:1 I wish to demonstrate # tests/features/calculator.feature:2 How easy writing Acceptance Tests # tests/features/calculator.feature:3 In Python really is. # tests/features/calculator.feature:4 Background: Given I am using the calculator # tests/features/steps.py:6 Given I am using the calculator # tests/features/steps.py:6 Scenario: Calculate 2 plus 2 on our calculator # tests/features/calculator.feature:9 Given I input "2" add "2" # tests/features/steps.py:11 Then I should see "4" # tests/features/steps.py:16 Traceback (most recent call last): File "/Users/user/.virtualenvs/bdd-in-python/lib/python2.7/site-packages/lettuce/core.py", line 144, in __call__ ret = self.function(self.step, *args, **kw) File "/Users/user/Documents/Articles - NetTuts/BDD_in_Python/tests/features/steps.py", line 18, in result assert_equals(int(expected_result), actual_result) File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/case.py", line 515, in assertEqual assertion_func(first, second, msg=msg) File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/case.py", line 508, in _baseAssertEqual raise self.failureException(msg) AssertionError: 4 != 0 1 feature (0 passed)1 scenario (0 passed)2 steps (1 failed, 1 passed) List of failed scenarios: Scenario: Calculate 2 plus 2 on our calculator # tests/features/calculator.feature:9
Obviously, the actual result 0 is inconsistent with expected result 4. Lettuce clearly shows the problem, and the next step is to debug the error until it passes.
Other tools
Many different tools are also provided in Python for similar tests, which are basically derived from Cucumber. For example:
- Behave: This is a Cucumber interface. Documentation is complete, updated, and many supporting tools are available.
- Freshen: Another Cucumber interface, supporting websites with complete tutorials and instances. installation methods are simple pip methods.
No matter what tool is used, as long as you are familiar with a tool, other tools can be integrated. Familiar reading of the tutorial documentation is the first step to success.
Advantages
Self-confidence in code refactoring
The advantage of using a complete test suite is obvious. Finding a powerful test suite will make code refactoring work more efficient and confident.
As the scale of the project continues to expand and the lack of effective tools makes it difficult to trace back and reconstruct the project. If you have a complete set of acceptance tests that correspond to each feature one by one, the changes will be conducted in an orderly manner without disrupting the existing functional modules.
All members can participate in the acceptance test, which will greatly improve the team's combat capability and move forward to the same goal from the very beginning. Programmers can focus on precise goals to avoid out-of-control requirements. Testers can review the feature files one by one to maximize the test process. Finally, a virtuous circle is formed, so that every feature of the program can be delivered perfectly.
Summary
Combined with the above processes and tools, we have achieved good results in teams that have worked in the past. BDD development allows the entire team to stay focused, confident, dynamic, and minimize potential errors.