使用Selenium WebDriver测试Flask应用-第2部分

This is the second and final part of a tutorial on how to test a Python/Flask web app with Selenium webdriver. We are testing Project Dream Team, an existing CRUD web app. Part One introduced Selenium WebDriver as a web browser automation tool for browser-based tests. By the end of Part One, we had written tests for registration, login, performing CRUD operations on departments and roles, as well as assigning departments and roles to employees.

这是有关如何使用Selenium webdriver测试Python / Flask Web应用程序的教程的第二部分,也是最后一部分。 我们正在测试Project Dream Team ,这是一个现有的CRUD Web应用程序。 第一部分介绍了Selenium WebDriver,它是用于基于浏览器的测试的Web浏览器自动化工具。 到第一部分结束时,我们已经编写了有关注册,登录,对部门和角色进行CRUD操作以及将部门和角色分配给员工的书面测试。

In Part Two, we will write tests to ensure that protected pages can only be accessed by authorised users. We will also integrate our app with CircleCI, a continuous integration and delivery platform. I have included a demo video showing all the tests running, so be sure to check it out!

在第二部分中,我们将编写测试以确保受保护的页面只能由授权用户访问。 我们还将把我们的应用程序与CircleCI(一个持续集成和交付平台)集成。 我提供了一个演示视频,其中显示了所有正在运行的测试,因此请务必检查一下!

权限测试 ( Permissions Tests )

Recall that in the Dream Team app, there are two kinds of users: regular users, who can only register and login as employees, and admin users, who can access departments and roles and assign them to employees. Non-admin users should not be able to access the departments, roles, and employees pages. We will therefore write tests to ensure that this is the case.

回想一下,在Dream Team应用程序中,有两种用户:普通用户(只能注册和登录为员工)和管理员用户(可以访问部门和角色并将其分配给员工)。 非管理员用户不应访问部门,角色和员工页面。 因此,我们将编写测试以确保确实如此。

In your tests/test_front_end.py file, add the following code:

在您的tests/test_front_end.py文件中,添加以下代码:

# tests/test_front_end.py

class TestPermissions(CreateObjects, TestBase):

    def test_permissions_admin_dashboard(self):
        """
        Test that non-admin users cannot access the admin dashboard
        """
        # Login as non-admin user
        self.login_test_user()

        # Navigate to admin dashboard
        target_url = self.get_server_url() + url_for('home.admin_dashboard')
        self.driver.get(target_url)

        # Assert 403 error page is shown
        error_title = self.driver.find_element_by_css_selector("h1").text
        self.assertEqual("403 Error", error_title)
        error_text = self.driver.find_element_by_css_selector("h3").text
        assert "You do not have sufficient permissions" in error_text

    def test_permissions_list_departments_page(self):
        """
        Test that non-admin users cannot access the list departments page
        """
        # Login as non-admin user
        self.login_test_user()

        # Navigate to admin dashboard
        target_url = self.get_server_url() + url_for('admin.list_departments')
        self.driver.get(target_url)

        # Assert 403 error page is shown
        error_title = self.driver.find_element_by_css_selector("h1").text
        self.assertEqual("403 Error", error_title)
        error_text = self.driver.find_element_by_css_selector("h3").text
        assert "You do not have sufficient permissions" in error_text

    def test_permissions_add_department_page(self):
        """
        Test that non-admin users cannot access the add department page
        """
        # Login as non-admin user
        self.login_test_user()

        # Navigate to admin dashboard
        target_url = self.get_server_url() + url_for('admin.add_department')
        self.driver.get(target_url)

        # Assert 403 error page is shown
        error_title = self.driver.find_element_by_css_selector("h1").text
        self.assertEqual("403 Error", error_title)
        error_text = self.driver.find_element_by_css_selector("h3").text
        assert "You do not have sufficient permissions" in error_text

    def test_permissions_list_roles_page(self):
        """
        Test that non-admin users cannot access the list roles page
        """
        # Login as non-admin user
        self.login_test_user()

        # Navigate to admin dashboard
        target_url = self.get_server_url() + url_for('admin.list_roles')
        self.driver.get(target_url)

        # Assert 403 error page is shown
        error_title = self.driver.find_element_by_css_selector("h1").text
        self.assertEqual("403 Error", error_title)
        error_text = self.driver.find_element_by_css_selector("h3").text
        assert "You do not have sufficient permissions" in error_text

    def test_permissions_add_role_page(self):
        """
        Test that non-admin users cannot access the add role page
        """
        # Login as non-admin user
        self.login_test_user()

        # Navigate to admin dashboard
        target_url = self.get_server_url() + url_for('admin.add_role')
        self.driver.get(target_url)

        # Assert 403 error page is shown
        error_title = self.driver.find_element_by_css_selector("h1").text
        self.assertEqual("403 Error", error_title)
        error_text = self.driver.find_element_by_css_selector("h3").text
        assert "You do not have sufficient permissions" in error_text

    def test_permissions_list_employees_page(self):
        """
        Test that non-admin users cannot access the list employees page
        """
        # Login as non-admin user
        self.login_test_user()

        # Navigate to admin dashboard
        target_url = self.get_server_url() + url_for('admin.list_employees')
        self.driver.get(target_url)

        # Assert 403 error page is shown
        error_title = self.driver.find_element_by_css_selector("h1").text
        self.assertEqual("403 Error", error_title)
        error_text = self.driver.find_element_by_css_selector("h3").text
        assert "You do not have sufficient permissions" in error_text

    def test_permissions_assign_employee_page(self):
        """
        Test that non-admin users cannot access the assign employee page
        """
        # Login as non-admin user
        self.login_test_user()

        # Navigate to admin dashboard
        target_url = self.get_server_url() + url_for('admin.assign_employee', id=1)
        self.driver.get(target_url)

        # Assert 403 error page is shown
        error_title = self.driver.find_element_by_css_selector("h1").text
        self.assertEqual("403 Error", error_title)
        error_text = self.driver.find_element_by_css_selector("h3").text
        assert "You do not have sufficient permissions" in error_text

We begin by creating a class TestPermissions, which inherits from the CreateObjects and TestBase classes that we wrote in Part One. In each of the test methods inside the class, we login as a non-admin user, and then attempt to access a protected page. First, we test the departments pages (list and add), then the roles pages (list and add), and finally the employees pages (list and assign). In each method, we test that the 403 error page is shown by asserting that the appropriate page title ("403 Error") and text ("You do not have sufficient permissions to access this page") are shown on the page.

我们首先创建一个类TestPermissions ,该类继承于我们在第一部分中编写的CreateObjectsTestBase类。 在类中的每个测试方法中,我们以非管理员用户身份登录,然后尝试访问受保护的页面。 首先,我们测试部门页面(列表和添加),然后测试角色页面(列表和添加),最后测试员工页面(列表和分配)。 在每种方法中,我们通过断言页面上显示了适当的页面标题(“ 403错误”)和文本(“您没有足够的权限来访问此页面”)来测试是否显示了403错误页面。

Take note of the difference between the assertEqual method and the assert ... in statement. The former checks that two things are exactly the same, whereas the latter checks that the first thing is contained in the second. In the case of our tests, "403 Error" and the error page title are exactly the same, so we can use assertEqual. For the second assertion, we are merely checking that the words "You do not have sufficient permissions" are contained in the error page text. The assert ... in statement is ideal when you don't want to check for identicalness, but rather that a certain important word or phrase is contained in the element in question.

注意assertEqual方法和assert ... in语句之间的区别。 前者检查两项是否完全相同,而后者则检查第一项是否包含在第二项中。 在我们的测试中,“ 403错误”和错误页面标题完全相同,因此我们可以使用assertEqual 。 对于第二个断言,我们仅检查错误页面文本中是否包含单词“您没有足够的权限”。 当您不想检查一致性时, assert ... in语句是理想的选择,而是要在相关元素中包含某些重要的单词或短语。

Let's run our tests now:

现在让我们运行测试:

$ nose2......................................
----------------------------------------------------------------------
Ran 38 tests in 168.981s

OK

持续集成和持续交付 ( Continuous Integration and Continuous Delivery )

You may have heard of continuous integration (CI), but you may not be very clear on what exactly it is or how to implement it in your development workflow. Well, CI refers to a software development pratice of integrating project code into a shared repository frequently, typically multiple times a day. CI usually goes hand-in-hand with automated builds and automated testing, such that each time code is pushed into the shared repo, the code is run and tested automatically to ensure it has no errors.

您可能听说过持续集成 (CI),但是可能不清楚它到底是什么还是如何在开发工作流程中实现它。 好吧,CI是指将项目代码频繁(通常一天多次)集成到共享存储库中的软件开发实践。 CI通常与自动化构建和自动化测试紧密结合,因此每次将代码推送到共享存储库中时,都会自动运行和测试代码以确保没有错误。

The idea is that small changes in the code are integrated to the main repo frequently, which makes it easier to catch errors should they occur and troubleshoot them. This is in contrast to a scenario where integration is done less often and with more code, making it more difficult to detect which change was responsible if an error was to occur.

这样的想法是,代码中的细微更改会频繁地集成到主存储库中,这使得在发生错误时更容易发现错误并进行故障排除。 与之相反的情况是,这种情况较少地进行集成并且使用更多的代码,从而使得在发生错误的情况下更难以检测出哪个更改是负责任的。

Martin Fowler, Chief Scientist at ThoughtWorks, put it well when he said:

Martin Fowler的首席科学家在ThoughtWorks说得好时,他说

Continuous Integration doesn’t get rid of bugs, but it does make them dramatically easier to find and remove.

持续集成并不能消除错误,但确实可以使查找和删除错误变得更加容易。

Continuous delivery entails building and handling your code in such a way that it can be released into production at any time. Practising continuous delivery means not having any code in your main repo that you wouldn't want to deploy. Sometimes, this even means that any code that is pushed to the main repo is automatically put in production if the build is successful and all tests pass. This is called continuous deployment.

持续交付需要以可以随时将其发布到生产中的方式来构建和处理代码。 实行连续交付意味着在主仓库中没有您不想部署的任何代码。 有时,这甚至意味着如果构建成功并且所有测试通过,则推送到主存储库的所有代码都会自动投入生产。 这称为连续部署

介绍CircleCI ( Introducing CircleCI )

Now that you're up to speed with continuous integration and continuous delivery, let's get familiar with one of the most popular continuous integration and delivery platforms today: CircleCI. CircleCI is quick and easy to set up. It automates software builds and testing, and also supports pushing code to many popular hosts such as Heroku and Google Cloud Platform.

现在,您可以快速进行持续集成和持续交付,让我们熟悉当今最流行的持续集成和交付平台之一: CircleCI 。 CircleCI设置快速简便。 它可以自动进行软件构建和测试,还支持将代码推送到许多受欢迎的主机,例如Heroku和Google Cloud Platform。

To start using CircleCI, sign up by authenticating your GitHub or Bitbucket account. Once you login, navigate to the Projects page where you can add your project repository. Select Build Project next to your repository name, and CircleCI will start the build.

要开始使用CircleCI, 注册通过身份验证您的GitHub或到位桶帐户。 登录后,导航到“ 项目”页面,您可以在其中添加项目存储库。 选择您的存储库名称旁边的“ Build Project ,CircleCI将开始构建。

Uh oh! The first build fails. You'll notice the disconcerting red colour all over the page, the multiple error messages, and even the disheartening red favicon in your browser, all of which denote failure. First of all, congratulations on your first failed build! :) Secondly, don't worry; we haven't configured CircleCI or our app yet, so it's no wonder the build failed! Let's get to work setting things up to turn the red to green.

哦! 第一次构建失败。 您会在整个页面上看到令人不安的红色,出现多个错误消息,甚至在浏览器中令人沮丧的红色图标都显示失败,所有这些都表示失败。 首先,恭喜您第一次构建失败! :)其次,不用担心; 我们尚未配置CircleCI或我们的应用程序,因此也难怪构建失败了! 让我们开始进行设置,将红色变为绿色。

环境变量 (Environment Variables)

We'll start by adding some important environment variables to CircleCI. Because we won't be reading from the instance/config.py file, we'll need to add those variables to CircleCI. On the top right of the build page on CircleCI, click the cog icon to access the Project Settings. In the menu on the left under Build Settings, click on Environment Variables. You can now go ahead and add the following variables:

我们将从向CircleCI添加一些重要的环境变量开始。 因为我们不会从instance/config.py文件中读取数据,所以我们需要将这些变量添加到CircleCI。 在CircleCI上构建页面的右上角,单击齿轮图标以访问项目设置。 在“构建设置”下左侧菜单中,单击“环境变量”。 现在,您可以继续添加以下变量:

  1. SECRET_KEY. You can copy this from your instance/config.py file.

    SECRET_KEY 。 您可以从instance/config.py文件中复制它。

  1. SQLALCHEMY_DATABASE_URI. We will use CircleCI's default circle_test database and ubuntu user, so our SQLALCHEMY_DATABASE_URI will be mysql://ubuntu@localhost/circle_test.

    SQLALCHEMY_DATABASE_URI 。 我们将使用CircleCI的默认circle_test数据库和ubuntu用户,因此我们的SQLALCHEMY_DATABASE_URImysql://ubuntu@localhost/circle_test

You should now have all two environment variables:

您现在应该拥有所有两个环境变量:

circle.yml文件 (The circle.yml File)

Next, create a circle.yml file in your root folder and in it, add the following:

接下来,在您的根文件夹中创建一个circle.yml文件,并在其中添加以下内容:

machine:
  python:
    version: 2.7.10
test:
  override:
    - nose2 

We begin by indicating the Python version for our project, 2.7.10. We then tell CircleCI to run our tests using the nose2 command. Note that we don't need to explicitly tell CircleCI to install the software dependencies because it automatically detects the requirements.txt file in Python projects and installs the requirements.

首先,为我们的项目指定2.7.10的Python版本。 然后,我们告诉CircleCI使用nose2命令运行测试。 请注意,我们不需要明确告诉CircleCI安装软件依赖项,因为它会自动检测Python项目中的requirements.txt文件并安装需求。

create_app方法 (The create_app Method)

Next, edit the create_app method in the app/__init__.py file as follows:

接下来,如下所示在app/__init__.py文件中编辑create_app方法:

# app/__init__.py

def create_app(config_name):
    # modify the if statement to include the CIRCLECI environment variable
    if os.getenv('FLASK_CONFIG') == "production":
        app = Flask(__name__)
        app.config.update(
            SECRET_KEY=os.getenv('SECRET_KEY'),
            SQLALCHEMY_DATABASE_URI=os.getenv('SQLALCHEMY_DATABASE_URI')
        )
    elif os.getenv('CIRCLECI'):
        app = Flask(__name__)
        app.config.update(
            SECRET_KEY=os.getenv('SECRET_KEY')
        )
    else:
        app = Flask(__name__, instance_relative_config=True)
        app.config.from_object(app_config[config_name])
        app.config.from_pyfile('config.py')

This checks for CircleCI's built-in CIRCLECI environment variable, which returns True when on CircleCI. This way, when running the tests on CircleCI, Flask will not load from the instance/config.py file, and will instead get the value of the SECRET_KEY configuration variable from the environment variable we set earlier.

这将检查CircleCI的内置CIRCLECI环境变量,该变量在CIRCLECI上返回True 。 这样,在CircleCI上运行测试时,Flask将不会从instance/config.py文件中加载,而是从我们之前设置的环境变量中获取SECRET_KEY配置变量的值。

测试文件 (The Test Files)

Now edit the create_app method in the tests/test_front_end.py file as follows:

现在,按如下所示在tests/test_front_end.py文件中编辑create_app方法:

# tests/test_front_end.py

# update imports
import os

class TestBase(LiveServerTestCase):

    def create_app(self):
        config_name = 'testing'
        app = create_app(config_name)
        if os.getenv('CIRCLECI'):
            database_uri = os.getenv('SQLALCHEMY_DATABASE_URI')
        else:
            database_uri = 'mysql://dt_admin:dt2016@localhost/dreamteam_test',
        app.config.update(
            # Specify the test database
            SQLALCHEMY_DATABASE_URI=database_uri,
            # Change the port that the liveserver listens on
            LIVESERVER_PORT=8943
        )
        return app

This ensures that when the tests are running on CircleCI, Flask will get the SQLALCHEMY_DATABASE_URI from the environment variable we set earlier rather than using the test database we have locally.

这样可以确保在CircleCI上运行测试时,Flask将从我们之前设置的环境变量中获取SQLALCHEMY_DATABASE_URI ,而不是使用本地的测试数据库。

Finally, do the same for the create_app method in the tests/test_back_end.py file:

最后,对tests/test_back_end.py文件中的create_app方法执行相同的操作:

# tests/test_back_end.py

# update imports
import os

class TestBase(TestCase):

    def create_app(self):
        config_name = 'testing'
        app = create_app(config_name)
        if os.getenv('CIRCLECI'):
            database_uri = os.getenv('SQLALCHEMY_DATABASE_URI')
        else:
            database_uri = 'mysql://dt_admin:dt2016@localhost/dreamteam_test',
        app.config.update(
            # Specify the test database
            SQLALCHEMY_DATABASE_URI=database_uri
        )
        return app

Push your changes to your repository. You'll notice that as soon as you push your code, CircleCI will automatically rebuild the project. It'll take a few minutes, but the build should be successful this time. Good job!

将更改推送到存储库。 您会注意到,一旦您推送代码,CircleCI将自动重建项目。 这将花费几分钟,但是这次构建应该会成功。 做得好!

状态徽章 (Status Badge)

CircleCI porvides a status badge for use on your project repository or website to display your build status. To get your badge, click on the Status Badges link in the menu on the left under Notifications. You can get the status badge in a variety of formats, including image and MarkDown.

CircleCI提供了一个状态标志,供您在项目存储库或网站上使用以显示您的构建状态。 要获取徽章,请单击“通知”下方左侧菜单中的“ 状态徽章”链接。 您可以获得多种格式的状态徽章,包括图像和MarkDown。

结论 ( Conclusion )

You are now able to write a variety of front-end tests for a Flask application with Selenium WebDriver. You also have a good understanding of continuous integration and continuous delivery, and can set up a project on CircleCI. I hope you've enjoyed this tutorial! I look forwrad to hearing your feedback and experiences in the comment section below.

现在,您可以使用Selenium WebDriver为Flask应用程序编写各种前端测试。 您还对持续集成和持续交付有很好的了解,并且可以在CircleCI上建立一个项目。 希望您喜欢本教程! 我希望您能在下面的评论部分中听到您的反馈和经验。

For more information on continuous integration in Python with CircleCI, you may refer to this Scotch tutorial by Elizabeth Mabishi.

有关将Python与CircleCI进行持续集成的更多信息,您可以参考Elizabeth Mabishi撰写的Scotch教程

翻译自: https://scotch.io/tutorials/test-a-flask-app-with-selenium-webdriver-part-2

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值