博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
python cli_测试Python命令行(CLI)应用程序的4种技术
阅读量:2518 次
发布时间:2019-05-11

本文共 33941 字,大约阅读时间需要 113 分钟。

python cli

You’ve just finished building your first Python command-line app. Or maybe your second or third. You’ve been learning Python for a while, and now you’re ready to build something bigger and more complex, but still runnable on a command-line. Or you are used to building and testing web applications or desktop apps with a GUI, but now are starting to build CLI applications.

您刚刚完成了第一个Python命令行应用程序的构建。 或者您的第二或第三名。 您已经学习Python已有一段时间了,现在您可以构建更大,更复杂的东西,但是仍然可以在命令行上运行。 或者,您习惯于使用GUI构建和测试Web应用程序或桌面应用程序,但是现在开始构建CLI应用程序。

In all these situations and more, you will need to learn and get comfortable with the various methods for testing a Python CLI application.

在所有这些情况下以及更多情况下,您将需要学习并熟悉测试Python CLI应用程序的各种方法。

While the tooling choices can be intimidating, the main thing to keep in mind is that you’re just comparing the outputs your code generates to the outputs you expect. Everything follows from that.

尽管工具的选择可能令人生畏,但要记住的主要事情是,您只是将代码生成的输出与期望的输出进行比较。 一切都从此开始。

In this tutorial you’ll learn four hands-on techniques for testing Python command-line apps:

在本教程中,您将学习四种测试Python命令行应用程序的动手技术:

  • “Lo-Fi” debugging with print()
  • Using a visual Python debugger
  • Unit testing with pytest and mocks
  • Integration testing
  • 使用print() “ Lo-Fi”调试
  • 使用可视化Python调试器
  • 使用pytest和模拟进行单元测试
  • 整合测试

Free Bonus: that summarizes the techniques demonstrated in this tutorial.

免费奖金: ,该总结了本教程中演示的技术。

Everything will be structured around a basic Python CLI app that passes data in the form of a multi-level dictionary to two functions that transform it in some way, then prints it to the user.

一切将围绕一个基本的Python CLI应用程序进行结构化,该应用程序将数据以多级字典的形式传递给两个函数,这些函数以某种方式对其进行转换,然后将其打印给用户。

We will use the code below to examine a few of the different methods that will aid you in testing. And while certainly not exhaustive, I hope this tutorial will give you enough breadth to get you confident in creating effective tests in the major testing domains.

我们将使用下面的代码来检查一些有助于您进行测试的不同方法。 尽管当然不能穷举,但我希望本教程能给您足够的广度,以使您有信心在主要测试领域中创建有效的测试。

I’ve sprinkled in a few bugs in this initial code, which we will expose with our testing methods.

我在初始代码中散布了一些错误,我们将通过测试方法来揭示这些错误。

Note: For simplicity’s sake, this code does not include some basic best practices, such as verifying the existence of keys in a dictionary.

注意 :为了简单起见,此代码不包括一些基本的最佳实践,例如,验证字典中键的存在。

As a first step, let’s think about our objects at every stage of this application. We start with a structure that describes John Q. Public:

首先,让我们考虑一下该应用程序每个阶段的对象。 我们从描述John Q. Public的结构开始。

JOHN_DATA JOHN_DATA = = {
{
'name''name' : : 'John Q. Public''John Q. Public' , , 'street''street' : : '123 Main St.''123 Main St.' , , 'city''city' : : 'Anytown''Anytown' , , 'state''state' : : 'FL''FL' , , 'zip''zip' : : 9999999999 , , 'relationships''relationships' : : {
{
'siblings''siblings' : : [[ 'Michael R. Public''Michael R. Public' , , 'Suzy Q. Public''Suzy Q. Public' ], ], 'parents''parents' : : [[ 'John Q. Public Sr.''John Q. Public Sr.' , , 'Mary S. Public''Mary S. Public' ], ], }}}}

We then flatten the other dictionaries, expecting this after calling our first transform function, initial_transform:

然后,我们将其他字典拼合,在调用我们的第一个转换函数initial_transform后期望这样做:

Then we build all the address information into a single address entry with the function final_transform:

然后,我们使用final_transform函数将所有地址信息构建到单个地址条目中:

JOHN_DATA JOHN_DATA = = {
{
'name''name' : : 'John Q. Public''John Q. Public' , , 'address''address' : : '123 Main St. '123 Main St. nn Anytown, FL 99999' Anytown, FL 99999' 'siblings''siblings' : : [[ 'Michael R. Public''Michael R. Public' , , 'Suzy Q. Public''Suzy Q. Public' ], ], 'parents''parents' : : [[ 'John Q. Public Sr.''John Q. Public Sr.' , , 'Mary S. Public''Mary S. Public' ],],}}

And the call to print_person will write this to the console:

调用print_person会将其写入控制台:

testapp.py:

testapp.py:

def def initial_transforminitial_transform (( datadata ):    ):    """"""    Flatten nested dicts    Flatten nested dicts    """        """    for for item item in in listlist (( datadata ):        ):        if if typetype (( itemitem ) ) is is dictdict :            :            for for key key in in itemitem :                :                datadata [[ keykey ] ] = = itemitem [[ keykey ]    ]    return return datadatadef def final_transformfinal_transform (( transformed_datatransformed_data ):    ):    """"""    Transform address structures into a single structure    Transform address structures into a single structure    """        """    transformed_datatransformed_data [[ 'address''address' ] ] = = strstr .. formatformat (        (        "{0}"{0} nn {1}, {2} {3}"{1}, {2} {3}" , , transformed_datatransformed_data [[ 'street''street' ],         ],         transformed_datatransformed_data [[ 'state''state' ], ], transformed_datatransformed_data [[ 'city''city' ],         ],         transformed_datatransformed_data [[ 'zip''zip' ])    ])    return return transformed_datatransformed_datadef def print_personprint_person (( person_dataperson_data ):    ):    parents parents = = "and""and" .. joinjoin (( person_dataperson_data [[ 'parents''parents' ])    ])    siblings siblings = = "and""and" .. joinjoin (( person_dataperson_data [[ 'siblings''siblings' ])    ])    person_string person_string = = strstr .. formatformat (        (        "Hello, my name is {0}, my siblings are {1}, "        "Hello, my name is {0}, my siblings are {1}, "        "my parents are {2}, and my mailing"        "my parents are {2}, and my mailing"        "address is: "address is:  nn {3}"{3}" , , person_dataperson_data [[ 'name''name' ],         ],         parentsparents , , siblingssiblings , , person_dataperson_data [[ 'address''address' ])    ])    printprint (( person_stringperson_string ))john_data john_data = = {
{
'name''name' : : 'John Q. Public''John Q. Public' , , 'street''street' : : '123 Main St.''123 Main St.' , , 'city''city' : : 'Anytown''Anytown' , , 'state''state' : : 'FL''FL' , , 'zip''zip' : : 9999999999 , , 'relationships''relationships' : : {
{
'siblings''siblings' : : [[ 'Michael R. Public''Michael R. Public' , , 'Suzy Q. Public''Suzy Q. Public' ], ], 'parents''parents' : : [[ 'John Q. Public Sr.''John Q. Public Sr.' , , 'Mary S. Public''Mary S. Public' ], ], }}}}suzy_data suzy_data = = {
{
'name''name' : : 'Suzy Q. Public''Suzy Q. Public' , , 'street''street' : : '456 Broadway''456 Broadway' , , 'apt''apt' : : '333''333' , , 'city''city' : : 'Miami''Miami' , , 'state''state' : : 'FL''FL' , , 'zip''zip' : : 3333333333 , , 'relationships''relationships' : : {
{
'siblings''siblings' : : [[ 'John Q. Public''John Q. Public' , , 'Michael R. Public''Michael R. Public' , , 'Thomas Z. Public''Thomas Z. Public' ], ], 'parents''parents' : : [[ 'John Q. Public Sr.''John Q. Public Sr.' , , 'Mary S. Public''Mary S. Public' ], ], }}}}inputs inputs = = [[ john_datajohn_data , , suzy_datasuzy_data ]]for for input_structure input_structure in in inputsinputs : : initial_transformed initial_transformed = = initial_transforminitial_transform (( input_structureinput_structure ) ) final_transformed final_transformed = = final_transformfinal_transform (( initial_transformedinitial_transformed ) ) print_personprint_person (( final_transformedfinal_transformed ))

Right now, the code doesn’t actually meet those expectations, so we will investigate using the four techniques while we are learning about them. By doing this, you will get practical experience in using these techniques, expand your comfort zone to them, and begin to learn for which problems they are most suited.

目前,该代码实际上并不能满足这些期望,因此我们将在学习这四种技术的同时使用这四种技术进行调查。 这样,您将获得使用这些技术的实践经验,将舒适区域扩展到这些技术上,并开始学习它们最适合哪些问题。

使用打印进行“ Lo-Fi”调试 (“Lo-Fi” Debugging With Print)

This is one of the simplest ways to test. All you have to do here is print a variable or object that you’re interested in–before a function call, after a function call, or within a function.

这是最简单的测试方法之一。 您要做的就是在函数调用之前,函数调用之后或函数内print您感兴趣的变量或对象。

Respectively, these allow you to verify a function’s input, a function’s output, and a function’s logic.

这些分别使您可以验证函数的输入,函数的输出和函数的逻辑。

If you save the code above as testapp.py and try to run it with python testapp.py you’ll see an error like so:

如果您将上面的代码另存为testapp.py并尝试使用python testapp.py运行它,则会看到类似以下的错误:

There is a missing key in person_data that is passed into print_person. The first step would be to check the input to print_person and see why our expected output (a printed message) isn’t being generated. We will just add a print function call before the call to print_person:

person_data中缺少传递给print_person 。 第一步是检查对print_person的输入,并查看为什么未生成我们的预期输出(打印的消息)。 我们只会在调用print_person之前添加一个print函数调用:

final_transformed final_transformed = = final_transformfinal_transform (( initial_transformedinitial_transformed ))printprint (( final_transformedfinal_transformed ))print_personprint_person (( final_transformedfinal_transformed ))

The print function does the job here, showing in its output that we don’t have the top-level parents key—nor the siblings key—but in the interest of our sanity, I’ll show you pprint, which prints multi-level objects in a more readable manner. To use it, add from pprint import pprint to the top of your script.

print函数在这里完成了工作,在输出中显示我们没有顶级的parents键,也没有siblings键,但是pprint理智的考虑,我将向您展示pprint ,它可以打印多级以更易读的方式显示对象。 要使用它,请from pprint import pprint添加到脚本顶部。

Instead of print(final_transformed), we call pprint(final_transformed) to inspect our object:

代替print(final_transformed) ,我们调用pprint(final_transformed)检查我们的对象:

Compare this with the expected final form above.

将此与上面预期的最终形式进行比较。

Because we know final_transform doesn’t touch the relationships dictionary, it’s time to see what is going on in initial_transform. Normally, I’d use a traditional debugger to step through this, but I want to show you another use of print debugging.

因为我们知道final_transform不会涉及到relationships字典,所以现在该看看initial_transform发生了什么。 通常,我会使用传统的调试器来逐步解决此问题,但我想向您展示打印调试的另一种用法。

We can print the state of objects in code, but we aren’t limited to that. We can print whatever we want, so we can also print markers to see which logic branches are executed and when.

我们可以在代码中打印对象的状态,但不仅限于此。 我们可以打印所需的任何内容,因此我们也可以打印标记以查看执行哪些逻辑分支以及何时执行。

Because initial_transform is primarily a few loops, and because internal dictionaries are supposed to be handled by the inner for loop, we should check out what’s happening in there, if anything:

因为initial_transform主要是几个循环,而且内部字典应该由内部for循环处理,所以我们应该检查其中发生了什么(如果有的话):

def def initial_transforminitial_transform (( datadata ):    ):    """"""    Flatten nested dicts    Flatten nested dicts    """        """    for for item item in in listlist (( datadata ):        ):        if if typetype (( itemitem ) ) is is dictdict :            :            print print "item is dict!"            "item is dict!"            pprintpprint (( itemitem )            )            for for key key in in itemitem :                :                datadata [[ keykey ] ] = = itemitem [[ keykey ]    ]    return return datadata

If we come across a dictionary within our input data, then we will be alerted in the console and then we will see what the item looks like.

如果我们在输入data遇到字典,那么将在控制台中提示我们,然后我们将看到该项目的外观。

After running, our console output hasn’t changed. This is good evidence that our if statement isn’t working as expected. While we can continue printing to find the bug, this is a great way to demonstrate the strengths of using a debugger.

运行后,控制台输出未更改。 这很好地证明了我们的if语句未按预期运行。 尽管我们可以继续打印以查找错误,但是这是演示使用调试器的强大方法。

As an exercise, though, I recommend bug hunting this code using only print debugging. It’s good practice and will force you to think of all the ways to use the console to alert you about different things happening in the code.

不过,作为练习,我建议仅使用打印调试来查找此代码。 这是一个好习惯,它将迫使您考虑使用控制台的所有方式,以提醒您代码中发生的不同情况。

结语 (Wrapup)

When to use print debugging:

何时使用打印调试:

  • Simple objects
  • Shorter scripts
  • Seemingly simple bugs
  • Quick inspections
  • 简单对象
  • 较短的脚本
  • 看似简单的错误
  • 快速检查

Dive deeper:

深入了解:

  • – prettify printed objects
  • –美化打印对象

Pros:

优点:

  • Rapid testing
  • Easy to use
  • 快速测试
  • 易于使用

Cons:

缺点:

  • Most cases you have to run the whole program, otherwise:
  • You need to add extra code to manually control flow
  • You can accidentally leave test code when done, especially in complex code
  • 大多数情况下,您必须运行整个程序,否则:
  • 您需要添加额外的代码来手动控制流程
  • 完成后,您可能会意外留下测试代码,尤其是在复杂代码中

使用调试器 (Using a Debugger)

Debuggers are great for when you want to step through the code one line at a time and inspect the entire application state. They help when you know roughly where errors are happening but can’t figure out why, and they give you a nice top-down view of everything happening inside your application at once.

调试器非常适合您一次要单步执行代码并检查整个应用程序状态的情况。 当您大致了解错误发生在哪里但无法弄清楚原因时,它们会提供帮助,并且您可以轻松自上而下地查看一下应用程序内部发生的所有事情。

There are many debuggers out there, and often they come with IDEs. Python also has a module called pdb that can be used in the REPL to debug code. Rather than get into implementation-specific details of all available debuggers, in this section I’ll show you how to use debuggers with common functions, such as setting breakpoints and watches.

那里有很多调试器,并且通常带有IDE。 Python还有一个名为pdb的模块,可以在REPL中使用它来调试代码。 在本节中,我将向您展示如何将调试器与常用功能一起使用,例如设置断点和监视,而不是进入所有可用调试器的特定于实现的细节。

Breakpoints are markers on your code that tell your debugger where to pause execution for you to inspect your application state. Watches are expressions that you can add during a debugging session to watch the value of a variable (and more) and are persisted through your app’s execution.

断点是代码上的标记,它们告诉调试器在哪里暂停执行以供您检查应用程序状态。 手表是表达式,你可以在调试会话过程中添加观看变量(及以上)的值,并通过您的应用程序的执行被持久化。

But let’s jump back to breakpoints. These will be added where you want to start or continue a debugging session. Since we are debugging the initial_transform method, we will want to put one there. I will denote the breakpoint with a (*):

但是,让我们跳回到断点。 这些将添加到您要启动或继续调试会话的位置。 由于我们正在调试initial_transform方法,因此我们要在其中放置一个。 我将用(*)表示断点:

Now when we start debugging, execution will pause on that line and you’ll be able to see variables and their types at that particular point in the program’s execution. We have a few options to navigate our code: step over, step in, and step out are the most common.

现在,当我们开始调试时,执行将在该行上暂停,您将能够在程序执行的特定点看到变量及其类型。 我们有几种方法可以浏览我们的代码:最常见的是进入,进入和退出。

Step over is the one you’ll use most often–this simply jumps to the next line of code.

跳过是您最常使用的代码-只需跳转到下一行代码即可。

Step in, attempts to go deeper into the code. You’ll use this when you come across a function call you want to investigate more deeply–you’ll be taken directly to that function’s code and be able to examine state there. You also use it often when confusing it for step over. Luckily step out can rescue us, this brings us back out to the caller.

介入,尝试更深入地研究代码。 当您遇到想要更深入研究的函数调用时,将使用此方法-您将直接进入该函数的代码并能够在那里检查状态。 当您混淆它以进行跨步时,也会经常使用它。 幸运的是,一步可以挽救我们,这使我们回到了呼叫者手中。

We can also set a watch here, something like type(item) is dict, which you can do in most IDEs via an ‘add watch’ button during a debugging session. This will now show True or False no matter where you are in the code.

我们还可以在此处设置监视,例如type(item) is dict ,您可以在大多数IDE中在调试会话期间通过“添加监视”按钮来执行监视。 无论您在代码中的什么位置,现在都将显示TrueFalse

Set the watch, and now step over so that you are now paused on the if type(item) is dict: line. You should now be able to see the status of the watch, the new variable item, and the object data.

设置好手表,然后走过去,以便您现在停在if type(item) is dict: line上。 现在,您应该能够看到手表的状态,新的变量item和对象data

Even without the watch, we can see the issue: rather than type looking at what item points to, it’s looking at the type of item itself, which is a string. Computers do exactly what we tell them, after all. Thanks to the debugger, we see the error of our ways and fix our code like so:

即使没有手表,我们也可以看到问题所在:与其查看一个item指向的type ,不如查看item本身的类型,它是一个字符串。 毕竟,计算机完全按照我们所说的去做。 多亏了调试器,我们才能看到方法的错误并按如下方式修复代码:

def def initial_transforminitial_transform (( datadata ):    ):    """"""    Flatten nested dicts    Flatten nested dicts    """        """    for for item item in in listlist (( datadata ):        ):        if if typetype (( datadata [[ itemitem ]) ]) is is dictdict :            :            for for key key in in datadata [[ itemitem ]:                ]:                datadata [[ keykey ] ] = = itemitem [[ keykey ]    ]    return return datadata

We should run it through the debugger again, and just make sure the code is going where we expect it to. And we are not, the structure now looks like this:

我们应该再次通过调试器运行它,只需确保代码能够到达我们期望的位置即可。 而且我们不是,该结构现在看起来像这样:

Now that we’ve looked at how a visual debugger is used, let’s go deeper and put our new knowledge to the test by completing the exercise below.

现在,我们已经研究了如何使用可视调试器,让我们更深入地完成下面的练习,将我们的新知识用于测试。

I want you to fix the code so that the output of initial_transform looks more like this, using only the debugger:

我希望您修复代码,以便仅使用调试initial_transform的输出看起来像这样:

john_data john_data = = {
{
'name''name' : : 'John Q. Public''John Q. Public' , , 'street''street' : : '123 Main St.''123 Main St.' , , 'city''city' : : 'Anytown''Anytown' , , 'state''state' : : 'FL''FL' , , 'zip''zip' : : 9999999999 , , 'relationships''relationships' : : {
{
'siblings''siblings' : : [[ 'Michael R. Public''Michael R. Public' , , 'Suzy Q. Public''Suzy Q. Public' ], ], 'parents''parents' : : [[ 'John Q. Public Sr.''John Q. Public Sr.' , , 'Mary S. Public''Mary S. Public' ], ], }, }, 'parents''parents' : : [[ 'John Q. Public Sr.''John Q. Public Sr.' , , 'Mary S. Public''Mary S. Public' ], ], 'siblings''siblings' : : [[ 'Michael R. Public''Michael R. Public' , , 'Suzy Q. Public''Suzy Q. Public' ],],}}

We’ve talked about the visual debugger. We’ve used the visual debugger. We love the visual debugger. There are still pros and cons to this technique, though, and you can review them in the section below.

我们已经讨论了可视调试器。 我们使用了可视调试器。 我们喜欢视觉调试器。 但是,此技术仍存在优缺点,您可以在下面的部分中对其进行回顾。

结语 (Wrapup)

When to use a Python debugger:

何时使用Python调试器:

  • More complex projects
  • Difficult to detect bugs
  • You need to inspect more than one object
  • You have a rough idea of where an error is occurring, but need to zero in on it
  • 更复杂的项目
  • 难以发现错误
  • 您需要检查多个物体
  • 您对发生错误的位置有一个大概的了解,但需要将其归零

Dive deeper:

深入了解:

  • Conditional breakpoints
  • Evaluating expressions while debugging
  • 条件断点
  • 调试时求值表达式

Pros:

优点:

  • Control over flow of program
  • Bird’s-eye view of application state
  • No need to know exactly where the bug is occurring
  • 控制程序流程
  • 应用程序状态的鸟瞰图
  • 无需确切知道错误发生的位置

Cons:

缺点:

  • Difficult to manually watch very large objects
  • Long-running code will take very long to debug
  • 难以手动观看非常大的物体
  • 长时间运行的代码将花费很长时间进行调试

Pytest和Mocks的单元测试 (Unit Testing with Pytest and Mocks)

The previous techniques are tedious and can require code changes if you want to exhaustively test input-output combinations, ensuring you hit every branch of your code–especially as your app grows. In our example, the output of initial_transform still doesn’t look quite right.

以前的技术很繁琐,并且如果您想详尽地测试输入输出组合,可能会需要更改代码,以确保击中代码的每个分支,尤其是随着应用程序的增长。 在我们的示例中, initial_transform的输出看起来仍然不太正确。

While the logic in our code is fairly simple, it can easily grow in size and complexity, or become the responsibility of a whole team. How do we test an application in a more structured, detailed, and automated way?

尽管我们代码中的逻辑相当简单,但它的大小和复杂性很容易增加,或者成为整个团队的责任。 我们如何以更加结构化,详细和自动化的方式测试应用程序?

Enter unit tests.

输入单元测试。

Unit testing is a testing technique that breaks down source code into recognizable units (usually methods or functions) and tests them individually.

单元测试是一种测试技术,可以将源代码分解为可识别的单元(通常是方法或函数),并分别进行测试。

You will essentially be writing a script or group of scripts that test each method with different inputs to ensure every logic branch within each method is tested–this is referred to as code coverage, and usually you want to aim for 100% code coverage. This isn’t always necessary or practical, but we can save that for another article (or a textbook).

本质上,您将编写一个脚本或一组脚本,以使用不同的输入来测试每个方法,以确保测试每个方法中的每个逻辑分支–这称为代码覆盖率,通常您希望达到100%的代码覆盖率。 这并非总是必要或不切实际的,但是我们可以将其保存为另一篇文章(或一本教科书)。

Each test treats the method being tested in isolation: outside calls are overridden with a technique called mocking to give reliable return values and any object set up before the test is removed after the test. These techniques and others are done to assure the independence and isolation of the unit under test.

每个测试都单独对待被测试的方法:外部调用被称为嘲笑的技术覆盖,以提供可靠的返回值,以及在测试之后删除测试之前设置的任何对象。 完成这些技术和其他技术是为了确保被测单元的独立性和隔离性。

Repeatability and isolation are key to these kinds of tests, even though we are still continuing with our theme of comparing expected outputs to actual outputs. Now that you have an understanding of unit testing overall, you can take a quick detour and see how to unit test Flask applications with the .

可重复性和隔离性是此类测试的关键,即使我们仍继续将预期输出与实际输出进行比较这一主题。 既然您已经全面了解了单元测试,那么可以快速绕道而行,看看如何使用对Flask应用程序进行单元测试。

pytest (Pytest)

So now that we’ve gone probably a bit too deep into the theory, let’s look at how this works in practice. Python comes with a built-in unittest module, but I believe pytest does a great job of building on what unittest provides. Either way, I’ll just be showing the basics of unit testing as unit testing alone can take up multiple long articles.

因此,既然我们对理论的了解可能太深了,让我们看一下它在实践中是如何工作的。 Python带有内置的unittest模块,但是我相信pytest在基于unittest提供的pytest做得很好。 无论哪种方式,我都将展示单元测试的基础知识,因为仅单元测试会占用多篇长篇文章。

A common convention is to put all your tests in a test directory within your project. Because this is a small script, a file test_testapp.py at the same level as testapp.py is sufficient.

常见的约定是将所有测试放在项目中的test目录中。 因为这是一个小脚本,所以与test_testapp.py处于同一级别的文件testapp.py就足够了。

We will write a unit test for initial_transform to show how to set up a set of expected inputs and outputs and make sure they match up. The basic pattern I use with pytest is to set up a that will take some parameters and use those to generate the test inputs and expected outputs that I want.

我们将为initial_transform编写一个单元测试,以显示如何设置一组预期的输入和输出并确保它们匹配。 我与pytest一起使用的基本模式是设置一个包含一些参数的 ,并使用这些参数生成我想要的测试输入和预期输出。

First I’ll show the fixture setup, and while you’re looking at the code, think about the test cases that you will need in order to hit all possible branches of initial_transform:

首先,我将展示夹具的设置,并且在您查看代码时,请考虑一下为了击中initial_transform所有可能分支所需要的测试用例:

import import pytestpytestimport import testapp testapp as as appapp@pytest.fixture@pytest.fixture (( paramsparams == [[ 'nodict''nodict' , , 'dict''dict' ])])def def generate_initial_transform_parametersgenerate_initial_transform_parameters (( requestrequest ):):

Before we generate inputs, let’s look at what’s going on here, because it can get confusing.

在生成输入之前,让我们看一下这里发生的事情,因为它可能会造成混乱。

First, we use the @pytest.fixture decorator to declare the following function definition a fixture. We also use a named parameter params to use with generate_initial_transform_parameters.

首先,我们使用@pytest.fixture装饰器将以下函数定义声明为一个夹具。 我们还使用了一个命名参数params来与generate_initial_transform_parameters一起使用。

The neat feature with this is that whenever the decorated function is used, it’ll be used with every parameter, so just calling generate_initial_transform_parameters will call it twice, once with nodict as a parameter and once with dict.

这样做的nodict是,每当使用修饰后的函数时,它将与每个参数一起使用,因此只需调用generate_initial_transform_parameters便会对其调用两次,一次以nodict为参数,一次以dict

To access these parameters, we add the pytest special object request to our function signature.

要访问这些参数,我们将pytest特殊对象request添加到函数签名中。

Now let’s build our inputs and expected outputs:

现在,让我们构建输入和预期输出:

Nothing too surprising here, we set up the input and expected output, and if we have the 'dict' parameter, then we modify the input and expected ouput, allowing us to test the if block.

这里没有什么奇怪的,我们设置了输入和期望的输出,如果我们有'dict'参数,那么我们修改输入和期望的输出,从而可以测试if块。

Then we write the test. In the test, we have to pass the fixture to the test function as a parameter to have access to it:

然后我们编写测试。 在测试中,我们必须将夹具作为参数传递给测试功能才能访问它:

def def test_initial_transformtest_initial_transform (( generate_initial_transform_parametersgenerate_initial_transform_parameters ):    ):    test_input test_input = = generate_initial_transform_parametersgenerate_initial_transform_parameters [[ 00 ]    ]    expected_output expected_output = = generate_initial_transform_parametersgenerate_initial_transform_parameters [[ 11 ]    ]    assert assert appapp .. initial_transforminitial_transform (( test_inputtest_input ) ) == == expected_outputexpected_output

Test functions should be prepended with test_ and should be based on . Here we are asserting that the output we get from passing our input to our real function is equal to our expected output. When you run this either in your IDE with a test configuration or with pytest in the CLI you’ll get…errors! Our output isn’t quite right yet. Let’s fix it using the following exercise–the practical experience is invaluable, and putting what you read into practice will make it easier to recall in the future.

测试函数应以test_并应基于 。 这里我们断言,通过将输入传递给实函数而获得的输出等于我们的预期输出。 当您在IDE中使用测试配置或在CLI中使用pytest运行此命令时,您会得到……错误! 我们的输出还不太正确。 让我们通过以下练习进行修复-实际经验非常宝贵,将您所阅读的内容付诸实践将使将来更容易回忆起。

I want you to use the unit tests to help you fix the function so it comes out as we expect it. Use only the unit test output to make these changes, and don’t change the unit test.

我希望您使用单元测试来帮助您修复该功能,以便它按我们期望的那样出现。 仅使用单元测试输出进行这些更改,而不更改单元测试。

cks (Mocks)

Mocks are another important part of unit testing. Because we are only testing a single unit of code, we don’t really care about what other function calls do. We just want to have a reliable return from them.

模拟是单元测试的另一个重要部分。 因为我们仅测试单个代码单元,所以我们实际上并不关心其他函数调用的作用。 我们只想从他们那里得到可靠的回报。

Let’s add an outside function call to initial_transform:

让我们向initial_transform添加一个外部函数调用:

def def initial_transforminitial_transform (( datadata ):    ):    """"""    Flatten nested dicts    Flatten nested dicts    """        """    for for item item in in listlist (( datadata ):        ):        if if typetype (( datadata [[ itemitem ]) ]) is is dictdict :            :            for for key key in in datadata [[ itemitem ]:                ]:                datadata [[ keykey ] ] = = datadata [[ itemitem ][][ keykey ]            ]            datadata .. poppop (( itemitem )    )    outside_moduleoutside_module .. do_somethingdo_something ()    ()    return return datadata

We don’t want to make live calls to do_something() so instead we’ll make a mock in our test script. The mock will catch this call and return whatever you set the mock to return. I like setting up the mocks in fixtures, since it’s a part of test setup and we can keep all that setup code together:

我们不想对do_something()进行实时调用,因此我们将在测试脚本中进行模拟。 模拟程序将捕获此调用并返回您设置的模拟返回值。 我喜欢在夹具中设置模拟,因为它是测试设置的一部分,我们可以将所有设置代码保存在一起:

Now every time you call initial_transform, the do_something call will be intercepted and return 1. You can also take advantage of fixture parameters to determine what your mock returns–this is important when a code branch is determined by the result of the outside call.

现在,每次调用initial_transformdo_something调用都会被拦截并返回1。您还可以利用Fixture参数来确定模拟返回的内容–当代码分支由外部调用的结果确定时,这很重要。

One last neat trick is to use side_effect. Among other things, this allows you to mock different returns for successive calls to the same function:

最后一个巧妙的技巧是使用side_effect 。 除其他外,这使您可以为连续调用同一函数模拟不同的返回值:

def def initial_transforminitial_transform (( datadata ):    ):    """"""    Flatten nested dicts    Flatten nested dicts    """        """    for for item item in in listlist (( datadata ):        ):        if if typetype (( datadata [[ itemitem ]) ]) is is dictdict :            :            for for key key in in datadata [[ itemitem ]:                ]:                datadata [[ keykey ] ] = = datadata [[ itemitem ][][ keykey ]            ]            datadata .. poppop (( itemitem )    )    outside_moduleoutside_module .. do_somethingdo_something ()    ()    outside_moduleoutside_module .. do_somethingdo_something ()    ()    return return datadata

We’d set up our mock like so, with a list of outputs (for each successive call) passed to side_effect:

我们将像这样设置模拟,将输出列表(每个后续调用)传递给side_effect

Mocking is very powerful, so powerful that you can even and I again encourage you to do a deeper dive on your own into mocking with mocker.

功能非常强大,功能如此强大,您甚至可以 ,我再次鼓励您自己更深入地研究使用mocker

结语 (Wrapup)

When to use Python unit testing frameworks:

何时使用Python单元测试框架:

  • Large, complex projects
  • OSS projects
  • 大型复杂项目
  • OSS项目

Helpful tools:

有用的工具:

  • for comparing complex objects
  • Mocker
  • 用于比较复杂的对象
  • 嘲笑者

Pros:

优点:

  • Automates running tests
  • Can catch many types of bugs
  • Simple setup and modification for teams
  • 自动运行测试
  • 可以捕获许多类型的错误
  • 团队的简单设置和修改

Cons:

缺点:

  • Tedious to write
  • Has to be updated with most code changes
  • Won’t replicate true application running
  • 繁琐的写作
  • 必须通过大多数代码更改进行更新
  • 不会复制真实的应用程序运行

整合测试 (Integration Testing)

Integration testing is one of the simpler testing methods here, but arguably one of the most important. This entails actually running your app end-to-end with real data in a production-like environment.

集成测试是这里较为简单的测试方法之一,但可以说是最重要的测试方法之一。 这实际上需要在类似生产的环境中使用真实数据端到端地运行您的应用程序。

Whether this is your home machine, a test server that duplicates a production server, or just changing a connection to a test database from a production one, this lets you know that your changes will work when deployed.

无论这是您的家用计算机,复制生产服务器的测试服务器,还是仅更改生产服务器与测试数据库的连接,这都使您知道所做的更改在部署时将起作用。

Like in all the other methods, you’re checking that your application generates the expected outputs given some inputs–except this time you’re using actual external modules (unlike in unit testing, where they are mocked), perhaps writing to actual databases or files, and, in larger applications, ensuring that your code integrates well with the overall system.

像在所有其他方法中一样,您正在检查是否在给定某些输入的情况下应用程序会生成预期的输出-这次除了使用实际的外部模块(不同于在单元测试中模拟它们的地方)之外,可能正在写入实际的数据库或文件,并在较大的应用程序中,确保您的代码与整个系统很好地集成。

How you do this is highly dependent on your application, for example, our test app can be run on its own with python testapp.py. However, let’s pretend our code is a segment of a large distributed application, like an ETL pipeline–in that case you would have to run the entire system on test servers with your code swapped in, run data through it, and make sure it made it through the whole system in the correct form. Outside of the command-line application world, tools like .

您的操作方式很大程度上取决于您的应用程序,例如,我们的测试应用程序可以使用python testapp.py单独运行。 但是,让我们假设我们的代码是大型分布式应用程序的一部分,例如ETL管道,在这种情况下,您将不得不在交换代码的情况下在测试服务器上运行整个系统,并通过它运行数据,并确保它它以正确的形式通过整个系统。 在命令行应用程序世界之外,诸如工具 。

This is an open-ended exercise. I’ve left a few bugs in the code, run the code a few times and compare your outputs to our expected outputs in the beginning of this tutorial. Use this method and the others you’ve learned to find and fix any remaining bugs.

这是一个开放式练习。 我在代码中留下了一些错误,运行了几次代码,并在本教程的开始部分将您的输出与我们的预期输出进行了比较。 使用此方法以及您已学会的其他方法来查找和修复所有剩余的错误。

结语 (Wrapup)

When to use integration testing in Python:

何时在Python中使用集成测试:

  • Always 😉
  • Generally after other test methods, if they’re employed.
  • 总是😉
  • 通常采用其他测试方法(如果使用)。

Helpful tools:

有用的工具:

  • environment and test automation management
  • 环境和测试自动化管理

Pros:

优点:

  • See how your application runs in real-world conditions
  • 查看您的应用程序如何在实际条件下运行

Cons:

缺点:

  • Larger applications can be difficult to accurately track data flow through
  • Have to have test environments that are very close to production environments
  • 大型应用程序可能难以准确地跟踪数据流
  • 必须拥有与生产环境非常接近的测试环境

放在一起 (Putting It All Together)

In conclusion—all CLI testing is a matter of comparing your expected outputs to your actual outputs, given some set of inputs. The methods I’ve discussed above are all ways of doing just that, and in many ways are complementary. These will be important tools for you to understand as you continue building command-line applications in Python, but this tutorial is just a starting point.

总而言之,所有CLI测试都是在给定一些输入的情况下将预期输出与实际输出进行比较的问题。 我上面讨论的方法都是实现此目的的所有方法,并且在许多方面都是互补的。 在继续使用Python构建命令行应用程序时,这些将是供您理解的重要工具,但是本教程只是一个起点。

Python has a very rich ecosystem, and that extends to testing tools and methodologies, so branch out from this and investigate more–you may find a tool or technique that I didn’t mention here that you absolutely love. If so, I’d love to hear about it in the comments!

Python具有非常丰富的生态系统,并且扩展到测试工具和方法论,因此可以从中扩展出来并进行更多研究-您可能会发现我在这里没有提到您绝对喜欢的工具或技术。 如果是这样,我很乐意在评论中听到!

As a quick recap, here are the techniques we learned about today and how they’re employed:

快速回顾一下,这是我们今天了解的技术以及如何使用它们:

  • Print debugging – print out variables and other markers in code to see how execution flows
  • Debuggers – controlling program execution to get a bird’s-eye view of application state and program flow
  • Unit testing – breaking an application into individually testable units and testing all logic branches within that unit
  • Integration testing – Testing your code changes in the context of the wider application
  • 打印调试–在代码中打印出变量和其他标记,以查看执行流程
  • 调试器–控制程序执行以获得应用程序状态和程序流的鸟瞰图
  • 单元测试–将应用程序划分为可单独测试的单元,并测试该单元中的所有逻辑分支
  • 集成测试–在更广泛的应用程序上下文中测试您的代码更改

Now go forth and test! As you work with these techniques, be sure to let me know in the comments how you’ve employed them and which are your favorites.

现在继续测试! 当您使用这些技术时,请确保在评论中让我知道您是如何使用它们的,哪些是您的最爱。

To get a Python testing cheat sheet that summarizes the techniques demonstrated in this tutorial, click the link below:

要获取一个Python测试备忘单,其中总结了本教程中演示的技术,请单击下面的链接:

Free Bonus: that summarizes the techniques demonstrated in this tutorial.

免费奖金: ,该总结了本教程中演示的技术。

翻译自:

python cli

转载地址:http://erqwd.baihongyu.com/

你可能感兴趣的文章
产品经理 - 登录 注册
查看>>
小白的python进阶历程------05.占位符
查看>>
CF414BMashmokh and ACMDP
查看>>
Notepad++ 通过g++编译
查看>>
JAVA基础2——类初始化相关执行顺序
查看>>
转:Zend Framework 重定向方法(render, forward, redirect)
查看>>
Linux下查看磁盘与目录的容量——df、du
查看>>
关于日记app的思考
查看>>
使用sencha的cmd创建项目时提示找不到\Sencha\Cmd\repo\.sencha\codegen.json
查看>>
如何快速启动一个Java Web编程框架
查看>>
MSP430单片机存储器结构总结
查看>>
文本框过滤特殊符号
查看>>
教育行业安全无线网络解决方案
查看>>
7个杀手级的开源监测工具
查看>>
软件架构学习小结
查看>>
C语言实现UrlEncode编码/UrlDecode解码
查看>>
返回用户提交的图像工具类
查看>>
树链剖分 BZOJ3589 动态树
查看>>
挑战程序设计竞赛 P131 区间DP
查看>>
【例9.9】最长公共子序列
查看>>