Introduction to Python (26) Test (1)

1 Overview

When you write a function or class, you can also write tests for it. Testing ensures that the code works as expected for various inputs. Testing gives us confidence that your program will work correctly even if more people use it. As you add new code to your program, you can also test it to make sure you don't break the program's existing behavior. Programmers make mistakes, so every programmer must test their code frequently to catch problems before users notice them.

So we will learn how to use the tools in the Python module unittest to test code, and we will also learn to write test cases that verify that a series of inputs will produce the expected output. We'll see what it's like to pass a test, what it's like to fail a test, and how failing a test can help improve the code. Learn how to test functions and classes, and know how many tests to write for your project.

2. Test function

To learn to test, you must have code to test. Here's a simple function that takes a first and last name and returns the tidy name:

def get_formatted_name(first, last):
    """生成整洁的姓名。"""
    full_name = f"{
      
      first} {
      
      last}"
    return full_name.title()

The function get_formatted_name() combines the first and last names into a name: put a space between the first and last names and capitalize the first letter, and return the result. To verify that get_formatted_name() works as expected, let's write a program that uses this function. The program names.py lets the user enter a first and last name, and displays the names neatly:

from name_function import get_formatted_name

print("Enter 'q' at any time to quit.")
while True:
    first = input("\nPlease give me a first name: ")
    if first == 'q':
        break
    last = input("Please give me a last name: ")
    if last == 'q':
        break

    formatted_name = get_formatted_name(first, last)
    print(f"\tNeatly formatted name: {
      
      formatted_name}.")

This program imports get_formatted_name() from name_function.py. Users can enter a series of first and last names and see the names neatly formatted:

Enter 'q' at any time to quit.

Please give me a first name: janis
Please give me a last name: joplin
        Neatly formatted name: Janis Joplin.

Please give me a first name: bob
Please give me a last name: dylan
        Neatly formatted name: Bob Dylan.

Please give me a first name: q

From the above output, we can see that the merged name is correct. Now suppose you want to modify get_formatted_name() to also handle middle names. When doing this, make sure you don't break the way this function handles names with only first and last names. To do this, test after each modification of get_formatted_name(): run the program names.py and enter a name like Janis Joplin. But this is too cumbersome. Fortunately, Python provides an efficient way to automatically test the output of functions. If you automate the testing of get_formatted_name(), you can always be confident that the function will work correctly when supplied with the tested name.

2.1 Unit tests and test cases

The module unittest in the Python standard library provides code testing tools. Unit tests are used to verify that a certain aspect of a function is correct. A test case is a set of unit tests that together verify that a function behaves as expected under various circumstances. A good test case considers the various inputs a function may receive, and includes tests for all of these situations. Fully covered test cases include a full suite of unit tests covering every possible use of the function. For large projects, it can be difficult to test with full coverage. Usually, it is enough to write tests for important behaviors of the code at first, and then consider full coverage when the project is widely used.

2.2 Passable tests

It takes a while to get used to the syntax for creating test cases, but once the test cases are created, adding unit tests for functions is easy. To write test cases for a function, first import the module unittest and the function to be tested, then create a class that inherits unittest.TestCase, and write a series of methods to test different aspects of the function's behavior.

The following test case contains only one method, which checks that the function get_formatted_name() works correctly when given a first and last name:

  import unittest
  from name_function import get_formatted_name

❶ class NamesTestCase(unittest.TestCase):
      """测试name_function.py。"""

      def test_first_last_name(self):
          """能够正确地处理像Janis Joplin这样的姓名吗?"""
❷         formatted_name = get_formatted_name('janis', 'joplin')
❸         self.assertEqual(formatted_name, 'Janis Joplin')if __name__ == '__main__':
      unittest.main()

First, the module unittest and the function get_formatted_name() to be tested are imported. At ❶, a class named NamesTestCase is created to contain a series of unit tests for get_formatted_name(). The class can be named whatever you want, but it's best to make it appear to be related to the function to be tested and include the word Test. This class must extend the unittest.TestCase class so that Python knows how to run the tests you write.

NamesTestCase contains only one method for testing one aspect of get_formatted_name(). Name the method test_first_last_name() because you want to verify that a name with only first and last names is formatted correctly. When running test_name_function.py, all methods starting with test_ will be run automatically. In this method, the function to be tested is called. In this example, get_formatted_name() is called with the arguments 'janis' and 'joplin', and the result is assigned to the variable formatted_name (see ❷).

At ❸, one of the most useful features of the unittest class is used: the assert method. Assertion methods verify that the result obtained is consistent with the expected result. Here, we know that get_formatted_name() should return names with first and last capitalized with a space in between, so we expect the value of formatted_name to be JanisJoplin. To check if this is indeed the case, we call the unittest's method assertEqual(), passing it the formatted_name and 'Janis Joplin'. line of code.

self.assertEqual(formatted_name, 'Janis Joplin')

means: "Compare the value of formatted_name to the string 'Janis Joplin'. If they're equal, you're good to go; if they're not, let me know!"

We will run this file directly, but it should be pointed out that many test frameworks will first import the test file and then run it. When importing a file, the interpreter will execute it as it is being imported. The if block at ❹ checks the special variable __name__, which is set at program execution time. If this file is executed as the main program, the variable __name__ will be set to ' main '. Here, unittest.main() is called to run the test case. If this file is imported by the testing framework, the variable __name__ will have a value other than ' main ', so unittest.main() will not be called.

When I run test_name_function.py, I get the following output:

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

A period on the first line indicates that a test passed. The next line indicates that Python ran a test that took less than 0.001 seconds. OK at the end indicates that all unit tests in this test case have passed.

The above output shows that the function get_formatted_name() always handles it correctly when given a name that contains a first and last name. After modifying get_formatted_name(), the test case can be run again. If it passes, it means that given a name like Janis Joplin, the function still handles it correctly.

2.3 Failed tests

What happens when the test fails? Let's modify get_formatted_name() so that it can handle middle names, but at the same time deliberately make the function unable to correctly handle first and last names like Janis Joplin.

Here is a new version of the function get_formatted_name() that requires a middle name to be specified via an argument:

def get_formatted_name(first, middle, last):
    """生成整洁的姓名。"""
    full_name = f"{
      
      first} {
      
      middle} {
      
      last}"
    return full_name.title()

This version should correctly handle names that include a middle name, but when testing it we found that it no longer correctly handles names with only a first and last name. When running the program test_name_function.py this time, the output is as follows:

❶ E
  ======================================================================
❷ ERROR: test_first_last_name (__main__.NamesTestCase)
  ----------------------------------------------------------------------
❸ Traceback (most recent call last):
    File "test_name_function.py", line 8, in test_first_last_name
      formatted_name = get_formatted_name('janis', 'joplin')
  TypeError: get_formatted_name() missing 1 required positional argument: 'last'

  ----------------------------------------------------------------------
❹ Ran 1 test in 0.000s

❺ FAILED (errors=1)

There's a lot of information in there, because there can be a lot of things that need to be known to you when a test fails. The first line of output has only one letter E (see ❶), indicating that a unit test in the test case caused an error. Next, we see that test_first_last_name() in NamesTestCase causes an error (see ❷). When a test case contains many unit tests, it is crucial to know which test failed. At ❸ we see a standard traceback indicating that there is a problem with the function call get_formatted_name('janis', 'joplin') because an essential positional argument is missing.

We also see a unit test being run (see ❹). Finally, a message stating that the entire test case failed because an error occurred while running it (see ❺). This message is at the end of the output so you can see it at a glance. You don't want to have to scroll through a long output to find out how many tests failed.

2.4 What to do if the test fails

What if the test fails? If the conditions you check are correct, passing the test means that the function behaves correctly, and failing the test means that the new code written is wrong. Therefore, when a test fails, instead of modifying the test, fix the code that caused the test to fail: examine the changes that were just made to the function, and find the changes that caused the function to behave differently than expected.

In this example, get_formatted_name() previously required only two arguments, first and last, but now requires first, middle, and last names. The new middle name parameter was required, which caused get_formatted_name() to behave unexpectedly. The best option here is to make the middle name optional. After doing this, the test passes again when tested with a name similar to Janis Joplin, and middle names are also accepted. Let's modify get_formatted_name() to make the middle name optional, and run the test case again. If it passes, then proceed to verify that the function handles middle names properly.

To make the middle name optional, move the formal parameter middle to the end of the formal parameter list in the function definition and assign its default value to an empty string. You also need to add an if test so that names are created accordingly depending on whether a middle name is provided:

def get_formatted_name(first, last, middle=''):
    """生成整洁的姓名。"""
    if middle:
        full_name = f"{
      
      first} {
      
      middle} {
      
      last}"
    else:
        full_name = f"{
      
      first} {
      
      last}"
    return full_name.title()

In this new version of get_formatted_name(), the middle name is optional. If a middle name is passed to the function, name will consist of first name, middle name and last name, otherwise name will consist of first and last name only. Now, the function should handle the two different names correctly. To make sure the function still handles names like Janis Joplin correctly, let's run it again.

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Now, the test case passes. That's great, it means the function works correctly with names like Janis Joplin again, and we don't have to test the function by hand. The reason this function is easy to fix is ​​because the failing test lets us know that the new code breaks the original behavior of the function.

2.5 Adding new tests

Once we've made sure that get_formatted_name() handles simple names correctly again, let's write a test for names that include middle names. To do this, add one more method to the NamesTestCase class:

  --snip--
  class NamesTestCase(unittest.TestCase):
      """测试name_function.py。"""

      def test_first_last_name(self):
          --snip--

      def test_first_last_middle_name(self):
          """能够正确地处理像Wolfgang Amadeus Mozart这样的姓名吗?"""
❶         formatted_name = get_formatted_name(
              'wolfgang', 'mozart', 'amadeus')
          self.assertEqual(formatted_name, 'Wolfgang Amadeus Mozart')

  if __name__ == '__main__':
      unittest.main()

Name the method test_first_last_middle_name(). The method name must start with test_ so that it will be run automatically when we run test_name_function.py. The method name clearly indicates which behavior of get_formatted_name() it tests. That way, if the test fails, we'll know right away what type of name was affected. You can use very long method names in the TestCase class, and these method names must be descriptive so that you can understand the output when the test fails. These methods are called automatically by Python, you don't have to write code to call them at all.

To test the function get_formatted_name(), we call it with the first, last, and middle name (see ❶), and then use assertEqual() to check that the returned name matches the expected name (first, middle, and last). When running test_name_function.py again, both tests pass:

..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

Guess you like

Origin blog.csdn.net/qq_41600018/article/details/131333194