在Python中引入测试。 第二部分

大家好!

我们将继续介绍有关Python测试的文章 ,这是我们在Python开发人员课程中为您准备的。

测试Django和Flask Web框架

如果您使用一种流行的框架(例如Django或Flask)编写针对Web应用程序的测试,则值得记住编写和运行此类测试的重要区别。

它们与其他应用程序有何不同

考虑一下要在Web应用程序中测试的代码。 所有路线,视图和模型都需要大量导入,并且需要有关所用框架的知识。
这类似于测试汽车,在本教程的第一部分中进行了讨论:在进行简单的测试(例如检查前灯)之前,您需要打开汽车中的计算机。

Django和Flask简化了此任务,并提供了基于单元测试的测试框架。 您可以继续以通常的方式编写测试,但运行方式有所不同。



如何使用Django Test Executor

Django startapp模板在您的应用程序目录中创建tests.py文件。 如果尚不存在,请使用以下内容创建它:

from django.test import TestCase class MyTestCase(TestCase): # Your test methods 

与先前示例的主要区别是您需要继承django.test.TestCase ,而不是unittest.TestCase 。 这些类的API相同,但是Django TestCase类设置了用于测试的所有内容。

要运行测试套件,请在命令行上使用manage.py test而不是unittest:

 $ python manage.py test 

如果需要多个测试文件,请用tests文件夹替换tests.py,在其中放入一个名为__init__.py的空文件,然后创建test_*.py文件。 Django将检测到它们并执行。

有关更多信息,请访问Django文档站点

如何使用单元测试和烧瓶

要使用Flask,必须将应用程序导入并置于测试模式。 您可以创建一个测试客户端,并将其用于将请求发送到应用程序中的任何路由。

在测试用例的setUp方法中实例化了测试客户端。 在以下示例中,my_app是应用程序的名称。 如果您不知道setUp的功能,请不要担心。 我们将详细介绍“更多高级测试脚本”部分。
测试文件中的代码如下所示:

 import my_app import unittest class MyTestCase(unittest.TestCase): def setUp(self): my_app.app.testing = True self.app = my_app.app.test_client() def test_home(self): result = self.app.get('/') # Make your assertions 

然后,您可以使用python -m unittest discover.运行测试用例python -m unittest discover.

在Flask文档站点上可以找到更多信息。

更高级的测试脚本

在开始为应用程序创建测试之前,请记住任何测试的三个主要步骤:

  1. 创建输入参数;
  2. 代码执行,接收输出数据;
  3. 输出数据与预期结果的比较;

这比为源数据(例如字符串或数字)创建静态值要复杂得多。 有时,您的应用程序需要类或上下文的实例。 在这种情况下该怎么办?

您创建为源的数据称为夹具。 创建和重用夹具是一种常见的做法。

在预期相同结果的情况下,以不同的值运行几次相同的测试称为参数化。

处理预期的故障

早先,当我们编译用于测试sum()的脚本列表时,出现了一个问题:当我们提供错误的值(例如,单个整数或字符串)时会发生什么?

在这种情况下,预计sum()将引发错误。 如果发生错误,则测试将失败。

有一种处理预期错误的特定方法。 您可以使用.assertRaises()作为上下文管理器,然后在with块内执行测试步骤:

 import unittest from my_sum import sum class TestSum(unittest.TestCase): def test_list_int(self): """ ,       """ data = [1, 2, 3] result = sum(data) self.assertEqual(result, 6) def test_list_fraction(self): """ ,       """ data = [Fraction(1, 4), Fraction(1, 4), Fraction(2, 5)] result = sum(data) self.assertEqual(result, 1) def test_bad_type(self): data = "banana" with self.assertRaises(TypeError): result = sum(data) if __name__ == '__main__': unittest.main() 

仅当sum(data)抛出TypeError时,才会通过此测试用例。 您可以将TypeError替换为任何其他类型的异常。

应用行为隔离

在本教程的最后一部分,我们讨论了副作用。 它们使单元测试复杂化,因为每次测试运行都会产生不同的结果甚至更糟-一个测试会影响整个应用程序的状态并导致另一个测试失败!

有一些简单的技术可以对应用程序的各个部分进行测试,这些副作用很多:

  • 根据单一责任原则重构代码;
  • 模拟所有方法和函数调用以消除副作用;
  • 针对该应用程序片段,使用集成测试代替单元测试。
  • 如果您不熟悉Moking,请查看一些很棒的Python CLI Testing示例

编写集成测试

到目前为止,我们更加关注单元测试。 单元测试是创建可预测且稳定的代码的好方法。 但是最后,您的应用程序应在启动时运行!

集成测试对于验证多个应用程序组件的协作是必需的。 此类测试可能需要履行买方或用户的角色:

  • 调用HTTP REST API;
  • Python API调用;
  • Web服务呼叫;
  • 运行命令行。

遵循输入参数,执行和批准模板,可以使用与单元测试相同的方式编写所有这些类型的集成测试。 最大的不同是集成测试同时测试了更多的组件,这意味着与单元测试相比,它们将带来更多的副作用。 另外,集成测试需要更多的固定装置,例如数据库,网络套接字或配置文件。

因此,建议将单元测试和集成测试分开。 为集成工具(例如,测试数据库或测试用例本身)创建固定装置比执行单元测试要花费更多的时间,因此您应该在投入生产之前执行集成测试,而不是每次提交时都运行它们。

分离单元测试和集成测试的最简单方法是将它们放在不同的文件夹中。

project/

├── my_app/
│ └── __init__.py

└── tests/
|
├── unit/
| ├── __init__.py
| └── test_sum.py
|
└── integration/
├── __init__.py
└── test_integration.py


您可以用不同的方式运行一组特定的测试。 可以使用包含测试的路径将用于指定源目录-s的标志添加到unittest discover中:

 $ python -m unittest discover -s tests/integration 

unittest将在测试/集成目录中显示所有结果。

测试面向数据的应用程序

许多集成测试需要后端数据,例如具有特定值的数据库。 假设您需要进行测试,以验证数据库中有100多个客户的应用程序是否正确运行,或者即使所有商品名称均为日语,也需要验证订单页面显示的正确性。

这些类型的集成测试将取决于各种测试装置,以确保其可重复性和可预测性。

测试数据应存储在集成测试目录内的装置文件夹中,以强调其“可测试性”。 然后,在测试中,您可以加载数据并运行测试。

这是一个由JSON文件组成的数据结构示例:

project/

├── my_app/
│ └── __init__.py

└── tests/
|
└── unit/
| ├── __init__.py
| └── test_sum.py
|
└── integration/
|
├── fixtures/
| ├── test_basic.json
| └── test_complex.json
|
├── __init__.py
└── test_integration.py


在测试案例中,您可以使用.setUp()方法以已知方式从固定文件中加载测试数据,并使用该数据运行多个测试。 请记住,您可以将多个测试用例存储在一个Python文件中,unittest会找到并执行它们。 每个测试数据集可以有一个测试用例:

 import unittest class TestBasic(unittest.TestCase): def setUp(self): # Load test data self.app = App(database='fixtures/test_basic.json') def test_customer_count(self): self.assertEqual(len(self.app.customers), 100) def test_existence_of_customer(self): customer = self.app.get_customer(id=10) self.assertEqual(customer.name, "Org XYZ") self.assertEqual(customer.address, "10 Red Road, Reading") class TestComplexData(unittest.TestCase): def setUp(self): # load test data self.app = App(database='fixtures/test_complex.json') def test_customer_count(self): self.assertEqual(len(self.app.customers), 10000) def test_existence_of_customer(self): customer = self.app.get_customer(id=9999) self.assertEqual(customer.name, u"バナナ") self.assertEqual(customer.address, "10 Red Road, Akihabara, Tokyo") if __name__ == '__main__': unittest.main() 

如果您的应用程序依赖于来自远程位置(例如,远程API)的数据,请确保测试是可重复的。 由于在禁用API和通讯问题时测试失败,因此开发可能会延迟。 在这种情况下,最好将远程设备存储在本地以进行回调并发送到应用程序。

requests库具有免费的响应包,可用于创建响应固定装置并将其保存在测试文件夹中。 在其GitHub页面上找到更多信息

下一部分将涉及在几种环境中进行测试和自动化测试。

结束

评论/问题总是受欢迎的。 在开放日在这里或去Stas

Source: https://habr.com/ru/post/zh-CN433572/


All Articles