Introduction to code Debugging Using unit testing in Python programming, and python Unit Testing

Source: Internet
Author: User
Tags integer division

Introduction to code Debugging Using unit testing in Python programming, and python Unit Testing

For new programmers, one of the most common confusions is the topic of testing. 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 extra "." Behind. Logic errors are caused when the algorithm (which can be regarded as a "method to solve the problem") is incorrect. Maybe programmers forget that Python is "zero indexed" and try to write
 
print (my_string [len (my_string)])
(This will cause IndexError) to print out the last character in a string. Larger and more systematic errors can also be detected. For example, when the user enters a number greater than 100, or suspends the site when the site search is not available, the program will always crash.

All these errors can be checked by careful testing of the code. Unit testing refers specifically to testing in a separate code unit. A unit can be an entire module, a separate class or function, or any code in between. However, it is important that the test code is isolated from other code that we have not tested (because other code itself has errors, it will confuse the test results). 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 True
 
def 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, we need to make sure that is_prime is correct, because this function is called in print_next_prime. In this case, the print_next_prime function is one 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 accurately test print_next_prime? (More on how to implement these tests later).

So, what should the test code look like? If the previous example exists in a file called primes.py, we can write the test code in a file called test_primes.py. The following is the most basic content in test_primes.py, such as the following test sample:
 
import unittest
from primes import is_prime
 
class 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 creates a unit test with a test case:? Test_is_five_prime. Through a test framework unittest embedded in Python. When unittest.main () is called, any member function named after test will be run. They are a derived class of unittest.TestCase and are checked by assertions. If we run the test by typing python test_primes.py, we can see the output of the unittest framework on the console:
 
$ python test_primes.py
E
================================================== ====================
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 division or modulo by zero
 
-------------------------------------------------- --------------------
Ran 1 test in 0.000s
The single "E" indicates the result of our unit test (if it succeeds, a "." Will be printed). We can see that our test failed, the line of code that caused the failure, and any exception information that was raised.

Why test?

Before we continue with that example, we have to ask a very important question: "Why is testing valuable to me?" This is a fair question and a question often asked by those unfamiliar with code testing. After all, testing takes a certain amount of time, and we can use this time to write code. Why test instead of doing the most productive things?

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

    Testing can ensure that your code works properly under a given set of conditions

The test ensures the correctness under a series of conditions. Grammatical errors must be detected through testing, and the basic logic of a code unit can also be detected through testing to ensure correctness under certain conditions. Again, it is not to prove that the code is correct under any conditions. We simply aimed at a relatively 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 characters for each parameter string)

    Testing allows people to ensure that changes to the code will not break existing functionality

This is especially useful when refactoring code. If testing is not in place, you ca n’t guarantee that changes to your code will not break things that worked before. If you want to change or rewrite your code, and hope that nothing will be broken, 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 the problems your code may encounter under abnormal conditions. In the above example, the my_addition_function function can add two numbers. A simple test to test basic correctness will call my_addition_function (2,2) and assert that the result is 4. However, further testing may call my_addition_function (2.0, 2.0) to test whether the function can correctly perform floating-point arithmetic. The defensive coding principle indicates that your code should be able to fail normally under illegal input, so when testing, an exception should be thrown when the string type is passed as a parameter to the function.

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

The overall approach of unit testing is to make it easier through loose coupling of code. If your application code directly calls the database, for example, the logic to test 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 mock objects during testing. Out of necessity, (people) designed test-ability applications eventually adopted modularity and loose coupling.

Anatomy of unit testing

By continuing 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 True
 
def 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 unittest
from primes import is_prime
 
class 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 an assertion

unittest is part of the Python standard library and is a good starting point for us to start our "unit testing journey". A unit test includes one or more assertions (some statements that declare that some properties of the code under test are true). I think when you go to school, the word "assertion" literally means "state the facts." In unit tests, assertions also have the same effect.

self.assertTrue is more like self-explanation. It can declare that the calculation result of passing past parameters is true. The unittest.TestCase class contains many assert methods, so be sure to check the list and choose the appropriate method to test. If assertTrue is used in each test, then an anti-pattern should be considered because it increases the reader's cognitive burden in the test. The correct way to use assertions should be to enable the test to clearly state what is being asserted (for example, obviously ?, just glance at the method name of assertIsInstance to know that it is to explain its parameters).

Each test should test a separate, specific code, and should be given a relevant name. Research on the unit test discovery mechanism shows (mainly in Python 2.7+ and 3.2+ versions) that the test method should be named with the prefix test_. (This is configurable, but its purpose is to distinguish between test methods and non-tested practical methods). If we change the name of test_is_five_prime to is_five_prime, the following information will be output when running test_primes.py in python:
 
$ python test_primes.py
 
-------------------------------------------------- --------------------
Ran 0 tests in 0.000s
 
OK
Don't be fooled by the "OK" in the above message, it will only show "OK" when no tests are actually run! I think that a test that did n’t run should actually show an error, but personally feel aside, this is a behavior you should pay attention to, especially when checking the test results by running the program (for example, a continuous integration tool Like TracisCI).

abnormal

Let's go back to the actual content of test_primes.py and recall the output of the python test_primes.py command:
 
$ python test_primes.py
E
================================================== ====================
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 division or modulo by zero
 
-------------------------------------------------- --------------------
Ran 1 test in 0.000s
These outputs tell us that the result of a test failed not because of an assertion failure, but because of an uncaught exception. In fact, due to an exception being thrown, the unittest framework returned without being able to run our test.

The problem here is clear: the calculation range of the modulo operation we use includes 0, so a division by 0 operation is performed. To solve this problem, we can simply change the starting value from 0 to 2 and point out that modulo 0 is wrong, and modulo 1 is always true (and a prime number can only be divided by itself and 1) , So we do not need to check 1).

Solve the problem

A failed test led us to modify the code. Once we fixed this error (change the line in s_prime to for element in range (2, number) :), we got the following output:
 
$ python test_primes.py
.
-------------------------------------------------- --------------------
Ran 1 test in 0.000s
Now that the error has been changed, does this mean that we should delete the test method test_is_five_prime (because it is clear that it will not always pass the test)? It should not be deleted. Since passing tests is the ultimate goal, unit tests should be deleted as little as possible. We have tested that the syntax of is_prime is valid, and, at least in one case, it returns the correct result. Our goal is to build a set of tests that can all pass (logical grouping of unit tests), although some may fail at first.

test_is_five_prime is used to handle a "non-special" prime number. Let us make sure that it also handles non-prime numbers correctly. Add the following method to the PrimeTestCase 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!')
Please note that at this time we added an optional msg parameter to the assert call. If the test fails, our information will be printed to the console and provide additional information to the person running the test.

Boundary conditions

We have successfully tested two common situations. Now let us consider the use cases of boundary conditions or unusual or unexpected inputs. When testing a function whose range is a positive integer, examples of boundary conditions include 0, 1, negative numbers, and a large number. Now let's test some of them.

Adding a test for 0 is simple. We expect? Is_prime (0) to return false, because, by definition, prime numbers 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.000s
 
FAILED (failures = 1)
0 is wrongly determined as a prime number. We forgot, we decided to skip 0 and 1 in the numeric range. Let's add a special check on 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 passed. How should our function handle a negative number? It is important to know the output before writing this test case. In this case, any negative number should return false.
 
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 feel to check all the numbers from -1 to -9. It is legal to call the test method in a loop. It is also possible to call the assert method multiple times in a test. We can rewrite the code in a (more detailed) way below.
 
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 that we do n’t want very much:

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.000s
 
FAILED (failures = 1)

Hmm ... We know the test failed, but on which negative number did it fail? Very useless is that Python's unit testing framework does not print out the expected and actual values. We can move to two ways and use one of them to solve the problem: through the msg parameter, or by using a third-party unit testing framework.

Using the msg parameter to assertFalse only makes us realize that we can use string formatting to solve the problem.
 
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))
This gives the following output information:
 
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.000s
 
FAILED (failures = 1)
Fix the code properly

We see that the negative number of failure is the first number: -1. In order to solve this problem, we can add a special check for the negative number increase, but the purpose of writing unit tests is not to blindly add code to detect boundary conditions. When a test fails, we should take a step back and determine the best way to solve the problem. In this case, we should not increase by one 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 latter code, we found that if the parameter is less than or equal to 1, two if statements can be merged into a statement that returns false. Not only is this more concise, it also fits well with the definition of prime numbers (a number larger than 1 and only divisible by 1 and itself).

Third-party testing framework

We could have solved this problem due to too little information by using a third-party testing framework. The two most commonly used are py.test and nose. You can get the following result by running the statement py.test -l (-l is to display the value of the local variable).
 
#! bash
 
py.test -l test_primes.py
============================= test session starts ================== ============
platform linux2-Python 2.7.6-pytest-2.4.2
collected 4 items
 
test_primes.py ... F
 
=================================== FAILURES ============== =====================
_____________________ PrimesTestCase.test_negative_number ______________________
 
self = <test_primes.PrimesTestCase testMethod = test_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))
E AssertionError: True is not false
 
index = -1
self = <test_primes.PrimesTestCase testMethod = test_negative_number>
 
test_primes.py:22: AssertionError
As you can see, some more useful information. These frameworks provide more functionality than simple, more detailed output, but the problem is only to know that they can exist and extend the functionality of the built-in unittest test 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 the surface of the test methodology. More advanced topics, such as test case organization, continuous integration, and test case management are available for those who want to learn more about Python. The test reader researched a good topic.

    Reorganize / clean code without changing its function
    Do not expose its internal data or functions when coding, and do not use the internal data or functions of other codes

Related 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.