使用lettuce测试flask项目

此为译文,原文链接:https://semaphoreci.com/community/tutorials/bdd-testing-a-restful-web-application-in-python?hmsr=toutiao.io&utm_campaign=9edebee457-Python_Weekly_Issue_219_November_26_2015&utm_medium=toutiao.io&utm_source=toutiao.io&utm_term=0_9e26887fc5-9edebee457-312739589

本文通过一个使用flask框架开发的web项目,介绍了python中行为驱动开发模式,包括BDD的语法,结构和目标。

简介

行为驱动开发模式,是指先对项目的行为进行描述,然后增加相应的测试和代码,使这个行为的测试通过。通过对不同场景中行为的清晰描述,你可以保证在项目最后交付时,能够满足产品的需求。BDD模式能够让能一块一块的编写你的项目,并且提供了你整个系统的living文档(living documentation,不知道怎么翻译),这个文档是你在保持测试通过是所自然维护的。

前提

在开始本教程之前,请确保你的系统已安装以下环境:
* Python2.7X
* Lettuce
* flask
* Nosetests
* 对于RESET 原则的基本理解

设置项目的基本结构

在本教程中,你将会构建一个简单的resetful项目,用来处理用户数据的存储和检索。首先,在你的系统中创建如下所示的目录结构,文件均为空,内容以后再添加:
目录
文件的描述如下:
* __init__.py: 使当前目录作为一个python包的存在。
* steps.py:将会被.feature文件执行的python代码
* user.feature:描述项目中用户行为的测试文件。
* application.py:Flask项目的创建入口以及服务开始的地方。
* views.py:处理视图注册的代码,以及定义了视图上对各种http请求的响应。

创建flask架构的项目

为了完成本教程,你需要使用flask框架,构建一个简单的web项目。打开application.py文件,添加以下代码:

from flask import Flask
app = Flask(__name__)

if __name__ == "__main__":
    app.run()

如果你已经安装好了必须的环境,打开系统命令行,输入如下命令:

python app/appliction.py

如果能看到以下输出,说明你的flask项目是正常运行的,然后你可以继续这个教程了。

$ python app/application.py
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

第一个BDD测试

我们将会依照BDD的开发模式,首先写测试,用来描述了我们想要在我们的应用程序中开发的初始功能。一旦测试写完,并且运行失败了,我们将会接着写项目代码,使测试通过。

写feature文件

编辑user.feature,添加如下代码:

Scenario: Retrieve a customers details
Given some users are in the system
When I retrieve the customer 'david01'
Then I should get a '200' response
And the following user details are returned:
| name |
| David Sale |

你会发现测试中的关键字使用了Gherkin(e.g. Given, When, Then, And)标准。
另一个重要的值得注意的是测试的风格和它如何读取。你想使你的场景尽可能容易阅读和重复使用,让任何人了解测试正在做什么,测试的功能和你期望它的行为。你应该尽可能的重用你的步骤,这样能保证你需要写的新代码的数量尽可能少,以及维持单元测试的高一致性。我们将在后面的教程中介绍一些可重用步骤,如将步骤中参数作为值。
如果你的系统中以及安装lettuce,现在就可以通过系统的命令行执行user.feature文件了。

lettuce test/features

你将会看到如下输出:

$ lettuce test/features/

Feature: Handle storing, retrieving and deleting customer details # test/features/user.feature:1

  Scenario: Retrieve a customers details                          # test/features/user.feature:3
    Given some users are in the system                            # test/features/user.feature:4
    When I retrieve the customer 'david01'                        # test/features/user.feature:5
    Then I should get a '200' response                            # test/features/user.feature:6
    And the following user details are returned:                  # test/features/user.feature:7
      | name       |
      | David Sale |

1 feature (0 passed)
1 scenario (0 passed)
4 steps (4 undefined, 0 passed)

You can implement step definitions for undefined steps with these snippets:

[ example snippets removed for readability ]

你会明显的发现测试没有通过当我们还没有写任何可被.feature文件所执行的代码时。这个被执行的代码被定义为steps。

定义steps

打开steps.py文件,添加如下代码:

from lettuce import step, world, before
from nose.tools import assert_equals
from app.application import app
from app.views import USERS

@before.all
def before_all():
    world.app = app.test_client()
    from app.views import USERS

@step(u'Given some users are in the system')
def given_some_users_are_in_the_system(step):
    USERS.update({'david01': {'name': 'David Sale'}})

@step(u'When I retrieve the customer \'(.*)\'')
def when_i_retrieve_the_customer_group1(step, username):
    world.response = world.app.get('/user/{}'.format(username))

@step(u'Then I should get a \'(.*)\' response')
def then_i_should_get_a_group1_response_group2(step, expected_status_code):
    assert_equals(world.response.status_code, int(expected_status_code))

@step(u'And the following user details are returned:')
def and_the_following_user_details(step):
    assert_equals(step.hashes, [json.loads(world.response.data)])    

@before.all这步,在lettuce中作为hook,即钩子的存在,是指在场景开始测试之前,所需要执行的代码。
接下来,定义了“Given some users are in the system”,当系统中存在一些用户时的操作。我们需要在view.py中添加如下代码:

USERS = {}

“When I retrieve the customer \’(.*)\’”这句,使用了正则表达式,匹配断言中的参数,并执行相应的操作。
“Then I should get a \’(.*)\’ response”这个步骤中,调用了nose.tools 的assert_equals函数,来判断上一步操作之后的返回值,与期望值是否相同。最后一句“And the following user details are returned:”原理类似。

执行场景

接下来,再运行一次测试:

lettuce test/features

可以看到如下输出:

$ lettuce test/features/

Feature: Handle storing, retrieving and deleting customer details # test/features/user.feature:1

  Scenario: Retrieve a customers details                          # test/features/user.feature:3
    Given some users are in the system                            # test/features/steps.py:17
    When I retrieve the customer 'david01'                        # test/features/steps.py:22
    Then I should get a '200' response                            # test/features/steps.py:27
    Traceback (most recent call last):
        [ SNIPPET REMOVED FOR READABILITY ]
        raise self.failureException(msg)
    AssertionError: 404 != 200
    And the following user details are returned:                  # test/features/steps.py:32
      | name       |
      | David Sale |

1 feature (0 passed)
1 scenario (0 passed)
4 steps (1 failed, 1 skipped, 2 passed)

List of failed scenarios:
  Scenario: Retrieve a customers details                          # test/features/user.feature:3

正如你所看到的那样,我们的项目返回“404 not found”的响应。那是因为我们还没有定义URL“/user/”的操作。在view.py中加入如下代码:

GET = 'GET'

@app.route("/user/<username>", methods=[GET])
def access_users(username):
    if request.method == GET:
        user_details = USERS.get(username)
        if user_details:
            return jsonify(user_details)
        else:
            return Response(status=404)

这段代码首先为你的flask项目注册了一个URL。然后定义了处理这个URL的方法。
再一次执行测试,你将会看到测试通过:

$ lettuce test/features/

Feature: Handle storing, retrieving and deleting customer details # test/features/user.feature:1

  Scenario: Retrieve a customers details                          # test/features/user.feature:3
    Given some users are in the system                            # test/features/steps.py:17
    When I retrieve the customer 'david01'                        # test/features/steps.py:22
    Then I should get a '200' response                            # test/features/steps.py:27
    And the following user details are returned:                  # test/features/steps.py:32
      | name       |
      | David Sale |

1 feature (1 passed)
1 scenario (1 passed)
4 steps (4 passed)
额外的任务

如果你喜欢这个教程,为什么不通过BDD的方法扩展你的flask项目,以满足如下要求:
* 支持POST操作在USERS中添加新的用户
* 支持PUT操作更新USERS中用户的信息
* 支持DELETE操作删除USERS中的用户

总结

行为驱动的开发是一个很好的过程,无论你是一个单独的开发人员工作在一个小项目,或是大型企业应用程序的开发人员。该过程确保你的代码符合前面设置的要求,在开始开发要交付的功能之前提供了正式的短暂的思考。BDD还有额外的好处,它可以自然的为你的代码提供“活着”的文档( “living” documentation),并且保持测试都是最新的,当更新新的功能时。

通过学习本教程,您希望获得编写这种样式的行为测试所需的核心技能,执行测试并传递使它们通过的代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值