通过模块和功能的命名方式

安装

pip install pytest

简介

pytest可以轻松编写测试,支持扩展,并且有丰富的引用和库支持复杂的功能测试

一个简单的例子:

# content of test_sample.py
def inc(x): return x + 1 def test_answer(): assert inc(3) == 5 

执行结果

$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 1 item

test_sample.py F                                                     [100%]

================================= FAILURES =================================
_______________________________ test_answer ________________________________  def test_answer(): > assert inc(3) == 5 E assert 4 == 5 E + where 4 = inc(3) test_sample.py:6: AssertionError ========================= 1 failed in 0.12 seconds ========================= 

功能特点

  • 失败的语句有详尽的信息 (无需记住 self.assert* names);
  • 自动发现测试的模块和方法(通过模块和功能的命名方式);
  • 使用fixtures用于管理测试资源;
  • 可以兼容 unittest和nose测试组件;
  • Python 2.7, Python 3.4+, PyPy 2.3, Jython 2.5 (未经测试);
  • 丰富的插件资源, 超过315个外部插件和蓬勃发展的社区;
  • 支持参数化
  • 执行测试过程中可以将某些测试跳过,或者对某些预期失败的case标记成失败
  • 支持重复执行失败的case

fixtures

fixtures提供一个固定的基线,可以可靠地重复执行测试。pytest fixture比经典的xUnit的setup/teardown 功能提供了显着的改进:

  • fixtures具有明确的名称,并通过从测试功能,模块,类或整个项目中声明它们的使用来激活。
  • fixtures以模块化方式实现,因为每个fixtures名称触发fixtures方法,该fixtures方法本身可以使用其他fixtures。
  • fixtures管理从简单的单元扩展到复杂的功能测试,允许根据配置和组件选项对fixtures和测试进行参数化,或者在功能,类,模块或整个测试会话范围内重复使用fixtures。

此外,pytes也支持经典的 xunit风格 。您可以根据需要混合使用两种样式,逐步从经典样式移动到新样式。您也可以从现有的unittest.TestCase样式或基于nose的项目开始。

xunit风格:

  • setUp/tearDown;
  • setUpClass/tearDownClass;
  • setUpModule/tearDownModule;

拓展作用域:

  • 模块级(setup_module/teardown_module)开始于模块始末,全局的
  • 函数级(setup_function/teardown_function)只对函数用例生效(不在类中)
  • 类级(setup_class/teardown_class)只在类中前后运行一次(在类中)
  • 方法级(setup_method/teardown_method)开始于方法始末(在类中)
  • 类里面的(setup/teardown)运行在调用方法的前后

fixtures作为函数参数

让我们看一个简单的独立测试模块,它包含一个fixture和一个使用它的测试函数

# content of ./test_smtpsimple.py
import pytest

@pytest.fixture
def smtp_connection(): import smtplib return smtplib.SMTP("smtp.gmail.com", 587, timeout=5) def test_ehlo(smtp_connection): response, msg = smtp_connection.ehlo() assert response == 250 assert 0 # for demo purposes 

在这里,test_ehlo需要smtp_connectio的返回值。pytest发现并调用@pytest.fixture标记的smtp_connection fixture函数。运行测试如下所示:

$ pytest test_smtpsimple.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 1 item test_smtpsimple.py F [100%] ================================= FAILURES ================================= ________________________________ test_ehlo _________________________________ smtp_connection = <smtplib.SMTP object at 0xdeadbeef> def test_ehlo(smtp_connection): response, msg = smtp_connection.ehlo() assert response == 250 > assert 0 # for demo purposes E assert 0 test_smtpsimple.py:11: AssertionError ========================= 1 failed in 0.12 seconds ========================= 

pytest 调用执行过程如下:

  1. pytest 发现了test_ehlo函数,因为以test_前缀。test_ehlo需要一个名为smtp_connection的函数参数。通过查找名为标记fixture的函数来发现匹配smtp_connection函数
  2. smtp_connection() 被调用来创建一个实例
  3. test_ehlo(<smtp_connection instance>) 被调用

共享fixture功能

如果在实施测试期间您意识到要使用多个测试文件中的fixture功能,则可以将其移动到conftest.py文件中。您不需要导入要在测试中使用的夹具,它会自动被pytest发现。

下面这个示例将fixture函数放入单独的conftest.py文件中,以便来自目录中多个测试模块的测试可以访问fixture函数

# content of conftest.py
import pytest
import smtplib

@pytest.fixture(scope="module")
def smtp_connection(): return smtplib.SMTP("smtp.gmail.com", 587, timeout=5) 

这个fixture的名字是smtp_connection,可以在任何测试文件(conftest.py所在目录中下的)将名称列为输入参数来访问:

# content of test_module.py

def test_ehlo(smtp_connection): response, msg = smtp_connection.ehlo() assert response == 250 assert b"smtp.gmail.com" in msg assert 0 # for demo purposes def test_noop(smtp_connection): response, msg = smtp_connection.noop() assert response == 250 assert 0 # for demo purposes 

fixture作用域

@pytest.fixture(scope="module")使用scope控制fixture的作用域,function,class,module,package,session。

变量名称作用范围xunit风格对比
function开始于方法始末(在类中)默认setup_method
class只在类中前后运行一次,每一个类调用一次,一个类可以有多个方法setup_class
module开始于模块始末,全局的,每一个.py文件调用一次,该文件内又有多个function和classsetup_module
package在pytest 3.7中引入了package范围,目前还属于测试阶段 
session是多个文件调用一次,可以跨.py文件调用,每个.py文件就是module 

fixture的teardown 代码

用xunit风格的的代码setup和teardown都是成对出现的,pytest除了兼容这种模式外,pytest还支持fixture特定的终结代码的执行。通过使用yield语句而不是returnyield语句之后的所有代码都用作teardown代码:

# content of conftest.py

import smtplib
import pytest


@pytest.fixture(scope="module")
def smtp_connection(): smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5) yield smtp_connection # provide the fixture value print("teardown smtp") smtp_connection.close() 

当模块中的最后一次测试已经完成,无论测试的情况如何的语句smtp_connection.close()将被执行

让我们执行:

$ pytest -s -q --tb=no
FFteardown smtp

2 failed in 0.12 seconds

请注意,如果我们使用scope='function'夹具设置fixture修饰的方法,则每次单独测试都会进行清理

我们也可以使用yield语法with的语句

# content of test_yield2.py

import smtplib
import pytest


@pytest.fixture(scope="module")
def smtp_connection(): with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection: yield smtp_connection # provide the fixture value 

yield关键字是在python语法生成器使用,用来节省内存

参数化

fixture参数化

当fixture方法被多次调用,并且每次执行一组相同的测试,在这种情况下,可以对fixture方法进行参数化。

扩展前面的示例,我们可以通过标记fixture的方法创建两个 smtp_connection 实例。fixture函数通过request对象访问每个参数:

# content of conftest.py
import pytest
import smtplib

@pytest.fixture(scope="module",
                params=["smtp.gmail.com", "mail.python.org"]) def smtp_connection(request): smtp_connection = smtplib.SMTP(request.param, 587, timeout=5) yield smtp_connection print("finalizing %s" % smtp_connection) smtp_connection.close() 

运行测试:

$ pytest -q test_module.py
FFFF                                                                 [100%]
================================= FAILURES =================================
________________________ test_ehlo[smtp.gmail.com] _________________________

smtp_connection = <smtplib.SMTP object at 0xdeadbeef>

    def test_ehlo(smtp_connection): response, msg = smtp_connection.ehlo() assert response == 250 assert b"smtp.gmail.com" in msg > assert 0 # for demo purposes E assert 0 test_module.py:6: AssertionError ________________________ test_noop[smtp.gmail.com] _________________________ smtp_connection = <smtplib.SMTP object at 0xdeadbeef> def test_noop(smtp_connection): response, msg = smtp_connection.noop() assert response == 250 > assert 0 # for demo purposes E assert 0 test_module.py:11: AssertionError ________________________ test_ehlo[mail.python.org] ________________________ smtp_connection = <smtplib.SMTP object at 0xdeadbeef> def test_ehlo(smtp_connection): response, msg = smtp_connection.ehlo() assert response == 250 > assert b"smtp.gmail.com" in msg E AssertionError: assert b'smtp.gmail.com' in b'mail.python.org\nPIPELINING\nSIZE 51200000\nETRN\nSTARTTLS\nAUTH DIGEST-MD5 NTLM CRAM-MD5\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8\nCHUNKING' test_module.py:5: AssertionError -------------------------- Captured stdout setup --------------------------- finalizing <smtplib.SMTP object at 0xdeadbeef> ________________________ test_noop[mail.python.org] ________________________ smtp_connection = <smtplib.SMTP object at 0xdeadbeef> def test_noop(smtp_connection): response, msg = smtp_connection.noop() assert response == 250 > assert 0 # for demo purposes E assert 0 test_module.py:11: AssertionError ------------------------- Captured stdout teardown ------------------------- finalizing <smtplib.SMTP object at 0xdeadbeef> 4 failed in 0.12 seconds 

我们看到两个测试函数分别针对不同的smtp_connection实例运行了两次

测试函数参数

内置的pytest.mark.parametrize装饰器支持测试函数的参数的参数化。以下是测试函数的典型示例,该函数实现检查某个输入是否导致预期输出:

# content of test_expectation.py
import pytest


@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected): assert eval(test_input) == expected 

次使用它们运行三次:

$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 3 items test_expectation.py ..F [100%] ================================= FAILURES ================================= ____________________________ test_eval[6*9-42] _____________________________ test_input = '6*9', expected = 42  @pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)]) def test_eval(test_input, expected): > assert eval(test_input) == expected E AssertionError: assert 54 == 42 E + where 54 = eval('6*9') test_expectation.py:6: AssertionError ==================== 1 failed, 2 passed in 0.12 seconds ==================== 

调用fixture的方式

# content of conftest.py
import pytest
import smtplib

@pytest.fixture(scope="module")
def smtp_connection(): return smtplib.SMTP("smtp.gmail.com", 587, timeout=5) 

在方法中可以直接使用fixture标识的函数名调用:

# content of test_module.py

def test_ehlo(smtp_connection): response, msg = smtp_connection.ehlo() assert response == 250 assert b"smtp.gmail.com" in msg assert 0 # for demo purposes 

也可以用声明装饰器@pytest.mark.usefixtures调用

# content of test_module.py

@pytest.mark.usefixtures("smtp_connection")
def test_ehlo(): response, msg = smtp_connection.ehlo() assert response == 250 assert b"smtp.gmail.com" in msg assert 0 # for demo purposes

public interface IApplicationBuilder
{
IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
RequestDelegate Build();
}
  第二个功能是Build,如下所示:

public class ApplicationBuilder : IApplicationBuilder
{
private readonly List<Func<RequestDelegate, RequestDelegate>> _middlewares = new List<Func<RequestDelegate, RequestDelegate>>();
public RequestDelegate Build()
{
_middlewares.Reverse();
return httpContext =>
{
RequestDelegate next = _ => { _.Response.StatusCode = 404; return Task.CompletedTask; };
foreach (var middleware in _middlewares)
{
next = middleware(next);
}
return next(httpContext);
};
}


public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
_middlewares.Add(middleware);
return this;
}
}
  Build真正做的事情是循环组装中间件,最后把组装好的委托链进行返回。从_middlewares.Reverse();我们又可以知道,对于委托链来说,中间件的注册顺序和执行顺序是相反的,这里需要进行反转,然后才能保证先注册的中间件先执行。

5.Server
Server对象相对比较简单,我们看下他的接口定义:

public interface IServer
{
Task StartAsync(RequestDelegate handler);
}
  我们可以看到Server有个启动函数StartAsync,StartAsync内部封装了RequestDelegate中间件,同时内部也会new一个HttpContext(features),这样Server、RequestDelegate、HttpContext三者就全部聚齐了。
public class HttpListenerServer : IServer
{
private readonly HttpListener _httpListener;
private readonly string[] _urls;
public HttpListenerServer(params string[] urls)
{
_httpListener = new HttpListener();
//绑定默认监听地址(默认端口为5000)
_urls = urls.Any()?urls: new string[www.dell-case.com] { "http://www.yongshi123.cn localhost:5000/"};
}

public async Task StartAsync(RequestDelegate handler)
{
Array.ForEach(_urls, url => _httpListener.Prefixes.Add(url));
_httpListener.Start();
Console.WriteLine("Server started and is listening on: {0}", string.Join(www.gouyiflb.cn/';', _urls));
while (true)
{
//该方法将阻塞进程(这里使用了await),等待传入的请求,直到收到请求
var listenerContext = await _httpListener.GetContextAsync();
//打印状态行: 请求方法, URL, 协议版本
Console.WriteLine("{0} {1} HTTP/{2}",
listenerContext.Request.HttpMethod,
listenerContext.Request.www.cqyland.com RawUrl,
listenerContext.Request.ProtocolVersion);

// 获取抽象封装后的HttpListenerFeature
var feature = new HttpListenerFeature(www.baihuiyulegw.com listenerContext);

// 获取封装后的Feature集合
var features = new FeatureCollection()
.Set<IHttpRequestFeature>(feature)
.Set<IHttpResponseFeature>(feature);

// 创建HttpContext
var httpContext = new HttpContext(features);
Console.WriteLine(www.hnxinhe.cn"[Info]: Server process one HTTP request start.");

// 开始依次执行中间件
await handler(httpContext);
Console.WriteLine("[Info]: Server process one HTTP request end.");

// 关闭响应
listenerContext.Response.Close(www.yunyouuyL.com);
}
}
}
public static partial class Extensions
{
public static IWebHostBuilder www.365soke.com UseHttpListener(this IWebHostBuilder builder, params string[] urls)
=> builder.UseServer(new HttpListenerServer(urls))

当装饰器@pytest.mark.usefixtures作用于类的时,如果这个@pytest.fixture的scope=function,那么类中的每个测试方法都会调用这个fixture。

@pytest.fixture(scope="module", autouse=True),参数autouse, 默认设置为False。 当默认为False,就可以选择用上面两种方式来试用fixture。 当设置为True时,在一个session内的所有的test都会自动调用这个fixture。 所以用该功能时也要谨慎小心

pytest常用插件

pip install pytest-html #轻量级的测试报告
pytest '文件' --html=report.html
pip install pytest-sugar # 打印进度
pip install pytest-rerunfailures # 失败重试
pip install pytest-ordering # 执行顺序 pip install pytest-allure-adaptor #测试报告的升级版,功能完备,界面酷炫

转载于:https://www.cnblogs.com/qwangxiao/p/10843000.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值