Improve your Python capabilities: understand Unit testing

Source: Internet
Author: User
Improve your Python capabilities: Understanding unit testing is the topic of testing for new programmers. They vaguely think that "unit testing" is good, and they should also do unit testing. But they do not understand the true meaning of the word. If it sounds like you, don't be afraid! In this article, I will introduce what unit testing is, why it is useful, and how to perform unit testing on Python code.

What is a test?

Before discussing why the test is useful and how to perform the test, let's take a few minutes to define what the unit test is. In general programming terms, "test" refers to the compilation of code that can be called (independent from the code of your actual application) to help you determine whether there are errors in the program. This does not prove that your code is correct (this is the only possibility in very limited cases ). It only reports whether the situations that the tester thinks are correctly handled.

Note: When I use "test" once, I mean "automated testing", that is, these tests run on the machine. "Manual testing" is an independent concept that a person runs a program and interacts with it to discover vulnerabilities.

What can be checked during the test? Syntax errors are accidental misuse of languages, such

my_list..append(foo)

The additional ".". Logical errors are caused when the algorithm (which can be viewed as a "solution to the problem") is incorrect. Maybe the programmer forgets that Python is a "zero-Line Guide" and tries to write

print(my_string[len(my_string)])

(This will cause IndexError) to print the last character in a string. Larger and more system errors can also be checked out. For example, if a user enters a number greater than 100 or suspends a website when it is unavailable for retrieval, the program will crash.

All these errors can be checked by carefully testing the code. Unit testing refers to testing in a separate code Unit. A unit can be the entire module, a separate class or function, or any code between the two. However, it is important that the test code should be isolated from other codes that we have not tested (the test results will be confused if there are errors in other codes ). Consider the following example:

def is_prime(number):    """Return True if *number* is prime."""    for element in range(number):        if number % element == 0:            return False    return Truedef print_next_prime(number):    """Print the closest prime number larger than *number*."""    index = number    while True:        index += 1        if is_prime(index):            print(index)

You have two functions: is_prime and print_next_prime. If you want to test print_next_prime, make sure that is_prime is correct, because print_next_prime calls this function. In this case, the print_next_prime function is a unit, and the is_prime function is another unit. Since unit tests only test one unit at a time, we need to carefully consider how to test print_next_prime accurately? (More about how to implement these tests later ).

So what should the test code look like? If the previous example contains a file named primes. py, we can write the test code in a file named test_primes.py. Below is the most basic content in test_primes.py, for example, the following test sample:

import unittestfrom primes import is_primeclass PrimesTestCase(unittest.TestCase):    """Tests for `primes.py`."""    def test_is_five_prime(self):        """Is five successfully determined to be prime?"""        self.assertTrue(is_prime(5))if __name__ == '__main__':    unittest.main()

This file uses a test case :? Test_is_five_prime. a unit test is created. Use unittest, a test framework embedded in Python. When unittest. main () is called, any member function with the name of "test" will be run. they are a derived class of unittest. TestCase and are asserted. If we run the test by entering python test_primes.py, we can see the output of the unittest framework on the console:

$ python test_primes.pyE======================================================================ERROR: test_is_five_prime (__main__.PrimesTestCase)----------------------------------------------------------------------Traceback (most recent call last):File "test_primes.py", line 8, in test_is_five_prime    self.assertTrue(is_prime(5))File "/home/jknupp/code/github_code/blug_private/primes.py", line 4, in is_prime    if number % element == 0:ZeroDivisionError: integer pision or modulo by zero----------------------------------------------------------------------Ran 1 test in 0.000s

An independent "E" indicates the result of our unit test (if it succeeds, a "." is printed). We can see that our test failed, the line of code that caused the failure, and any exception information.

Why test?

Before proceeding to that example, I would like to ask a very important question: "Why is testing valuable to me "? This is a fair question, and is also a question that people who are not familiar with code testing often ask. After all, testing takes some time, and we can use this time to compile the code. why do we need to test instead of doing the most productive tasks?

There are many answers that can effectively answer this question. I have listed the following points:

Testing ensures that your code works properly under a series of given conditions

The test ensures the correctness under a series of conditions. Syntax errors must be detected through tests. The basic logic of a code unit can also be detected through tests to ensure the correctness under certain conditions. Again, it does not mean that the code is correct under any conditions. We simply aim at a complete set of possible conditions (for example, you can write a test to monitor when you call my_addition_function (3, 'refrigerator, but you do not have to detect all possible strings for each parameter)

Tests allow people to ensure that changes to code do not disrupt existing functions

This is particularly useful when refactoring code. If the test is not in place, you cannot ensure that your code changes do not disrupt the previous normal operations. If you want to change or rewrite your code and do not want to destroy anything, proper unit testing is necessary.

Testing forces people to think about code under unusual conditions, which may reveal logical errors

Writing tests forces you to think about possible problems with your code under unusual conditions. In the preceding example, the my_addition_function can add two numbers. A simple test of basic correctness will call my_addition_function () and assert that the result is 4. However, further tests may call my_addition_function (2.0, 2.0) to test whether the function can correctly perform floating point calculation. The defensive encoding principle indicates that your code should be valid without illegal input. Therefore, an exception should be thrown when the string type is passed to the function as a parameter during testing.

Good testing requires modularity and code decoupling, which is a good sign of system design.

The overall practice of unit testing is to make it easier through loose coupling of code. If your application code calls the database directly, for example, testing the logic of your application depends on a valid database connection, and the test data must exist in the database. On the other hand, code that isolates external resources is more likely to be replaced by simulated objects during testing. Out of necessity, (people)-designed tested applications eventually adopt modular and loose coupling.

Analysis of unit test

By continuing with the previous example, we will see how to write and organize unit tests. Recall that primes. py contains the following code:

def is_prime(number):    """Return True if *number* is prime."""    for element in range(number):        if number % element == 0:            return False    return Truedef print_next_prime(number):    """Print the closest prime number larger than *number*."""    index = number    while True:        index += 1        if is_prime(index):            print(index)

At the same time, the file test_primes.py contains the following code:

import unittestfrom primes import is_primeclass PrimesTestCase(unittest.TestCase):    """Tests for `primes.py`."""    def test_is_five_prime(self):        """Is five successfully determined to be prime?"""        self.assertTrue(is_prime(5))if __name__ == '__main__':    unittest.main()

Make assertions

Unittest is part of the Python standard library and a good starting point for getting started with unit testing. A unit test contains one or more assertions (some attributes that declare the tested code are true statements ). I think the literal meaning of the word "assertion" when you go to school is "presenting facts ". In unit tests, assertions also play the same role.

Self. assertTrue is more like self-explanation. It can declare that the calculation result of the passed parameter is true. The unittest. TestCase class contains many asserted methods. Therefore, check the list and select an appropriate method for testing. If assertTrue is used in each test, an anti-pattern should be considered because it increases the cognitive burden of the readers in the test. The correct method to use assertions should be to enable the test to clearly describe what is being asserted (for example, obviously ?, Just glance at the method name of assertIsInstance and you will know that it is its parameter ).

Each test should test a separate code with specific features and should be named accordingly. Studies on the unit test discovery mechanism show that (mainly in Python2.7 + and 3.2 + versions) the test method should be named with the prefix test. (This is configurable, but its purpose is to identify test methods and non-test practical methods ). If we change the name of test_is_five_prime to is_five_prime, the following information will be output when we run test_primes.py in python:

$ python test_primes.py----------------------------------------------------------------------Ran 0 tests in 0.000sOK

Do not be fooled by "OK" in the above information. "OK" is displayed only when no tests are actually running "! I think an error should be displayed even if a test is not run, but I personally think it is a behavior that you should pay attention, especially when the test results are checked through a program running (for example, a continuous integration tool, such as TracisCI ).

Exception

Let's go back to the actual content of test_primes.py and recall the output result after running the python test_primes.py command:

$ python test_primes.pyE======================================================================ERROR: test_is_five_prime (__main__.PrimesTestCase)----------------------------------------------------------------------Traceback (most recent call last):File "test_primes.py", line 8, in test_is_five_prime    self.assertTrue(is_prime(5))File "/home/jknupp/code/github_code/blug_private/primes.py", line 4, in is_prime    if number % element == 0:ZeroDivisionError: integer pision or modulo by zero----------------------------------------------------------------------Ran 1 test in 0.000s

These outputs tell us that the result of a test fails not because of an asserted failure, but because of an uncaptured exception. In fact, because an exception is thrown, the unittest framework does not have the ability to run our test and returns.

The problem here is very clear: the calculated range of the modulo operation we use includes 0, so we executed an operation divided by 0. To solve this problem, we can easily change the starting value from 0 to 2, and point out that the modulo for 0 is incorrect, the modulo of 1 is always true (and a prime number can only be divisible by itself and 1, so we do not need to check 1 ).

Solve the problem

A failed test made us modify the code. Once we have changed this error (change the row in s_prime to for element in range (2, number) :), we get the following output:

$ python test_primes.py.----------------------------------------------------------------------Ran 1 test in 0.000s

Now the error has been changed. does this mean we should delete the test method test_is_five_prime (because it is obvious that it will not always pass the test )? It should not be deleted. The unit test should be deleted as few as possible because passing the test is the final goal. We have tested that the is_prime syntax is valid and, in at least one case, it returns the correct result. Our goal is to create a set of all-pass (logical grouping of unit tests) tests, although some may fail at the beginning.

Test_is_five_prime is used to process a non-special prime number. Let's make sure that it can also correctly handle non-prime numbers. Add the following method to the PrimesTestCase class:

def test_is_four_non_prime(self):    """Is four correctly determined not to be prime?"""    self.assertFalse(is_prime(4), msg='Four is not prime!')

Note that the optional msg parameter is added to the assert call. If the test fails, our information will be printed to the console and additional information will be provided to the person running the test.

Boundary condition

We have successfully tested two common cases. Now let's consider boundary situations, or use cases that are unusual or unexpected input. When you test a function with a positive integer in the range, the instances under the boundary condition include 0, 1, negative, and a large number. Now let's test some of them.

It is easy to add a 0-to-0 Test. What we expect? Is_prime (0) returns false because, according to definition, the prime number must be greater than 1.

def test_is_zero_not_prime(self):    """Is zero correctly determined not to be prime?"""    self.assertFalse(is_prime(0))

Unfortunately, the output is:

python test_primes.py..F======================================================================FAIL: test_is_zero_not_prime (__main__.PrimesTestCase)Is zero correctly determined not to be prime?----------------------------------------------------------------------Traceback (most recent call last):File "test_primes.py", line 17, in test_is_zero_not_prime    self.assertFalse(is_prime(0))AssertionError: True is not false----------------------------------------------------------------------Ran 3 tests in 0.000sFAILED (failures=1)

0 is incorrectly identified as a prime number. We forgot. we decided to skip 0 and 1 in the number range. Let's add a special check for them.

def is_prime(number):    """Return True if *number* is prime."""    if number in (0, 1):        return False    for element in range(2, number):        if number % element == 0:            return False    return True

Now the test is passed. How should our function handle a negative number? It is important to know the output results before writing this test case. In this case, false should be returned for any negative number.

def test_negative_number(self):    """Is a negative number correctly determined not to be prime?"""    for index in range(-1, -10, -1):        self.assertFalse(is_prime(index))

Here we will check all numbers from-1 to-9. It is very legal to call the test method in a loop. you can also call the asserted method multiple times in a test. We can rewrite the code in the following (more detailed) method.

def test_negative_number(self):    """Is a negative number correctly determined not to be prime?"""    self.assertFalse(is_prime(-1))    self.assertFalse(is_prime(-2))    self.assertFalse(is_prime(-3))    self.assertFalse(is_prime(-4))    self.assertFalse(is_prime(-5))    self.assertFalse(is_prime(-6))    self.assertFalse(is_prime(-7))    self.assertFalse(is_prime(-8))    self.assertFalse(is_prime(-9))

These two are completely equivalent. Except when we run the loop version, we get a message we don't really want:

python test_primes.py...F======================================================================FAIL: test_negative_number (__main__.PrimesTestCase)Is a negative number correctly determined not to be prime?----------------------------------------------------------------------Traceback (most recent call last):File "test_primes.py", line 22, in test_negative_number    self.assertFalse(is_prime(index))AssertionError: True is not false----------------------------------------------------------------------Ran 4 tests in 0.000sFAILED (failures=1)

Well... we know that the test failed, but on which negative number did it fail? It is useless that the Python unit test framework does not print the expected value and the actual value. You can use either the msg parameter or a third-party unit test framework to solve the problem.

Using the msg parameter to assertFalse only makes us realize that we can solve the problem by setting the string format.

def test_negative_number(self):    """Is a negative number correctly determined not to be prime?"""    for index in range(-1, -10, -1):        self.assertFalse(is_prime(index), msg='{} should not be determined to be prime'.format(index))

The following output information is provided:

python test_primes...F======================================================================FAIL: test_negative_number (test_primes.PrimesTestCase)Is a negative number correctly determined not to be prime?----------------------------------------------------------------------Traceback (most recent call last):File "./test_primes.py", line 22, in test_negative_number    self.assertFalse(is_prime(index), msg='{} should not be determined to be prime'.format(index))AssertionError: True is not false : -1 should not be determined to be prime----------------------------------------------------------------------Ran 4 tests in 0.000sFAILED (failures=1)

Properly fix code

We can see that the negative number of failures is the first number:-1. To solve this problem, we can add a special check for the negative increase, but the purpose of writing a unit test is not to blindly add code to detect the boundary. When a test fails, we should step back and determine the best way to solve the problem. In this case, we should not add an additional if:

def is_prime(number):    """Return True if *number* is prime."""    if number < 0:        return False    if number in (0, 1):        return False    for element in range(2, number):        if number % element == 0:            return False    return True

The following code should be used first:

def is_prime(number):    """Return True if *number* is prime."""    if number <= 1:        return False    for element in range(2, number):        if number % element == 0:            return False    return True

In the next code, we found that if the parameter is less than or equal to 1, the two if statements can be merged into a statement with a return value of false. This is not only more concise, but also well fits the definition of prime numbers (a number larger than 1 and only divisible by 1 ).

Third-party testing framework

We can also use a third-party testing framework to solve the test failure problem caused by too little information. The two most common examples are py. test and nose. Run the py. test-l statement (-l is the value of the local variable) to obtain the following results.

#! bashpy.test -l test_primes.py============================= test session starts ==============================platform linux2 -- Python 2.7.6 -- pytest-2.4.2collected 4 itemstest_primes.py ...F=================================== FAILURES ===================================_____________________ PrimesTestCase.test_negative_number ______________________self =     def test_negative_number(self):        """Is a negative number correctly determined not to be prime?"""        for index in range(-1, -10, -1):>           self.assertFalse(is_prime(index))E           AssertionError: True is not falseindex      = -1self       = test_primes.py:22: AssertionError


As you can see, some more useful information. These frameworks provide more functions than simply providing more detailed output, but the problem is that they only know that they can exist and expand the functionality of the built-in unittest package.

Conclusion

In this article, you learned what unit tests are, why they are so important, and how to write tests. That is to say, we should note that we just cut off the surface of the test methodology, more advanced topics, for example, the organization, continuous integration, and management of test cases are good topics for readers who want to further study the tests in Python.

Reorganize/clear code without changing its functions

Internal data or functions of other codes are not exposed during code compilation.


Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.