current position:Home>Untested Python code is not far from crashing

Untested Python code is not far from crashing

2022-01-31 07:51:16 Nuggets translation program

My first mentor was incredibly . He showed me the code 、 logging 、 Best practices and benefits of documentation . But there's one thing he couldn't instill in me , That's the test . His way of testing code is complicated , Including writing the test program first , And then coding implementation ! His way is opposite to my coding style , It makes me feel :" If I write the test before I write the function , Then I might as well not write a test .”…… It makes me feel much better .

But the problem is : Your code needs to be tested . Because all the code , Even good code , Both with bug There is only a thin line between them . For starters :bug Is an unexpected function or error in the code . You may know your code and its limitations very well , But new teammates ? perhaps , In a year , You want to add a feature to a project you've largely forgotten , What should I do ? The test is like a bumper on a bowling alley , So that you can be confident that the submitted code will get full marks every time .

This article will reuse my Python In the learning series The first 3 part Code for , And use me in here To introduce the Makefile. If you are Python Novice , Why don't you take a look first The first 1 part and The first 2 part ? Besides , If you don't have your own Python The work environment , Please be there. here Check out the tutorials you need .

Topics discussed :

  • unit testing
  • Inherit
  • Mocking and patch
  • Makefile
  • When to test ?

Because this requires some code , I've created a Github Project To help us start this topic . The easiest way to get it is through Github Desktop Clone it , Or download it as ZIP file . The folder contains、 One Makefile And a Pipfile, One more Solutions Folder , But let's ignore it for the time being .

Create a file called tests Of Python package . So how to create ? It's a little complicated —— First, create a folder , Create a file named Empty file . Yes , That's it . And then in the new tests In the folder , Create another one called The file of . Now we can start . Be careful :unittest( and pytest) According to “test” The beginning of the file determines the test code , So avoid this when naming non test files !

What is the test ?

In short , The test answered “ Whether the execution results of the program meet our expectations ?” This problem . To answer this question , We can run a function by using preselected input and check whether the output meets our expectations . You can run a function and verify the output , Make sure it doesn't throw errors , Or make sure it exactly Throw an error , To ensure that the code has been fully tested . A good set of tests should include normal use cases 、 Edge use cases and creative use cases . You don't just have to make sure your code runs as it is , And make sure your The test will capture any stupid behavior you or others will do in the future .


Unittest yes Python Built in test framework , So we'll start here . Put this code into your test file :

import unittest
import order_up

class TestOrderUp(unittest.TestCase):
    def test_get_order_one_item(self):
        order = ["fries"]
        result = order_up.get_order(order)
        self.assertEqual(order, result)
 Copy code 

First , We import unittest, It is a tool for testing code Python Built in bag , And then we import file ( Notice that we have omitted .py Extension ).

notes : If you're using PyCharm And in order_up See the red underline below , This means that the package cannot be found . You can go back to Github Open the project under the root directory of the project or right-click the project folder and select “Mark Directory as” -> “Sources Root” To solve this problem .

Next , Let's create a name TestOrderUp Class , Its name matches our file name , This makes it easier to find failed tests . Oh , But there's something in parentheses ,unittest.TestCase, This means that our class inherits TestCase class .


Inheritance means that a class receives functions and variables from its parent class . In our case , We from TestCase It inherits rich functions to facilitate our testing work . What functions and variables are inherited ? We'll discuss this later .

Create a test

Under our class is a class named test_output_order_one_item Function of , It should roughly explain what we do in the test . We will use it to test get_order() Function and check whether the output meets our expectations . Let's run it , See what happens ! You can execute... In the terminal python -m unittest, Or click PyCharm The green arrow next to the function in . You can also choose to perform make unit-test, Let the code run in a virtual environment ( We will mention later Makefile). Look at the results :

 beautiful , You successfully performed your first test !

Assertion (assert)

We from unittest.TestCase Functions inherited in include assertions , It ensures that the result of the function is within our expectations . stay Pycharm in , Input self.assert, The code completion function will display all the different options . There are a lot of them , But I mainly use self.assertEqual, It checks whether the two objects are the same , as well as self.assertTrue/self.assertFalse, The function is self-evident .

Now? ,order_up The main function of is to get orders , Delete items that are not on the menu , And allow duplicate items . therefore , Let's add tests to make sure we keep these functions in our code .

#  Make sure these functions are indented in the class .
def test_get_order_duplicate_in_list(self):
    order = ["fries", "fries", "fries", "burger"]
    result = order_up.get_order(order)
    self.assertEqual(order, result)

def test_get_order_not_on_menu(self):
    order = ["banana", "cereal", "cookie"]
    expected_result = ["cookie"]
    result = order_up.get_order(order)
    self.assertEqual(expected_result, result)
 Copy code 

Now we are checking whether our function can handle duplicate items and items not on the menu . Run these tests and make sure they pass ! sidenote : It's best to write a test with a line between the executed code and the verified code . such , You and your teammates can easily tell which is which .


I have to admit : I did a little cheating . If you will The first 3 part The code in is the same as the current Compare , You will notice that I added a function to accommodate a new variable :test_order. With this new variable , We can bypass the introduction of input(), In this way, we won't let the program ask the user for input every time we run the test . But now we have mastered the basic knowledge of testing , We can start trying to use mock.Mock Can imitate and create functions / object , So that our tests can focus on logic . under these circumstances , We will “ Mend ” input() function , Or rewrite it temporarily , To simply return the output we want . have a look :

@patch("builtins.input", return_value="yes")
def test_is_order_complete_yes(self, input_patch):
    self.assertEqual(builtins.input, input_patch)
    result = order_up.is_order_complete()
 Copy code 

First , Add... At the beginning of the test file from unittest.mock import patch. In limine , We are mending builtins.input() Function and tell it to return “yes”. then , We execute assertions to check whether the parameters obtained from the patch are consistent with input It's exactly the same ! be aware builtins.input Are there no parentheses ? We can reference the signature of the function to verify , Instead of executing functions . after , Let's go back to the normal test protocol : Operation function , To get the results , And assert the result . under these circumstances , Because of our input() The return value is “yes”, We expect is_order_complete() return False. Add it to your test class , Click Run , Get red OK Or green check mark , Let's move on !

Side Effect

Now we've learned patch, We can solve get_output() The input problem in ! Um. , almost . First , We need to understand side_effect, When we need to provide different return values for the same function , It is our Savior . stay get_output() in , adopt input(), We were asked “ What do you want ?” and “ Have you finished? ?”. therefore , We need to get input() Not just one but multiple outputs are returned to accommodate each situation . have a look :

@patch("builtins.input", side_effect=["banana", "cookie", "yes", "fries", "no"])
def test_get_order_valid(self, input_patch):
    self.assertEqual(builtins.input, input_patch)
    expected_result = ["cookie", "fries"]

    result = order_up.get_order()

    self.assertEqual(expected_result, result)
 Copy code 

So , We don't know return_value, It's for side_effect Assign a list .

remarks : You can also assign values in the test function side_effect or return_value.

side_effect Will get each item in the list , And in every call patch Function is provided separately . Add the code and click the test button / command ! The last thing : stay “banana” and “cookie” None of them is / no , Because if MENU The item does not exist in ,get_order() Don't ask “ Would you like to order more ?”. If you want to play with this list yourself , Please remember this thing .


Having finished the basics of testing , Let's take a look Makefile. I won't copy / Paste the code here , Because you can see it in the project . The main method is unit-test and run.unit-test need venv To execute , According to our Pipfile Configure and start a virtual environment . Pay attention to unit-test At the end of , We did python3 -m pipenv run python3 -m unittest;, This is where testing magic takes place , Even if you forget how to run tests , You can also find it there !

When to write tests ?

So when to write a test ? It doesn't matter . The point is that the test written can cover most of the code and the potential use cases it may encounter . If you can't test your code correctly or need to 8 A different test to cover a function , Then you probably need to refactor your code . This doesn't make you a bad programmer , It's just a programming process / Part of the experience .

Test-driven development (TDD)

Let me talk about test driven development (TDD) The question of .TDD Is a development practice , Write the failed test program first, and then write the function to pass it . Story time : I joined a startup , The startup will Robert C. Martin(《 Clean code 》 And the authors of other books ) Of TDD And the concept of negative patterns , Or bad coding practices to avoid , As a faith . There is a , We held a meeting on TDD And its benefits of meetings to encourage the team to think more “ It works ” The way to code . Unfortunately , Spend most of your time arguing TDD On the definition and correct usage of . The organizer of the meeting , A senior engineer , Think we “ Coding too fast ”, Not by writing " smart " Tests or functions that exceed the functions tested to correctly implement TDD Principles .

I left the meeting with an idea : Let your philosophical debate from my work disappear .

The point of this article is : Find a suitable way to include testing in the project . I didn't give a specific way to implement them or when , As long as they can prevent your code from entering the drain after the next submission . bye !

If there are errors in the translation or other areas that need to be improved , Welcome to Nuggets translation plan Revise the translation and PR, You can also get bonus points . At the beginning of the article Permanent link to this article That's what this article is about GitHub Upper MarkDown link .

Nuggets translation plan It is a community that translates high-quality Internet technology articles , The source of the article is Nuggets Share articles in English on . Content coverage AndroidiOS front end Back end Blockchain product Design Artificial intelligence Other fields , If you want to see more excellent translations, please keep your eyes on Nuggets translation plan Official micro-blog Know about columns .

copyright notice
author[Nuggets translation program],Please bring the original link to reprint, thank you.

Random recommended