One of the most common puzzles for novice program developers is the subject of the test. They vaguely feel that "unit tests" are good, and they should also do unit tests. But they do not understand the true meaning of the word. If it sounds like you, don't be afraid! In this article, I'll describe what a unit test is, why it works, and how to unit test Python code.
What is a test?
Before we discuss why tests are useful and how to test them, let's take a few minutes to define what "unit tests" really are. In general programming terms, "test" means to help you determine if there are errors in your program by writing code that can be invoked (independent of your actual application's code). This does not prove that your code is correct (this is only possible in very limited circumstances). It only reports if the tester thinks that the situation is handled correctly.
Note: When I use "test" once, I am referring to "automated testing", that is, these tests are run on the machine. "Manual Testing" is a stand-alone concept of a person running a program and interacting with it to discover vulnerabilities.
What kind of situation can the test check out? Grammatical errors are accidental misuse of language, such as
The extra "." In the back. A logical error is raised when an algorithm (which can be considered a "way to solve the problem") is incorrect. Maybe programmers forget that Python is a "0 index" and try to write
Print (My_string[len (my_string)])
(This can cause indexerror) to print out the last character in a string. Larger, more systematic bugs can also be checked out. For example, when a user enters a number greater than 100, or hangs up when the site is unavailable for retrieval, the program crashes.
All of these errors can be checked by careful testing of the code. Unit testing, especially a test in a delimited code cell. A unit can be an entire module, a separate class or function, or any code between the two. However, it is important that the test code be isolated from other code that we have not tested (because the other code itself has errors that 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 (num ber): "" "
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 the is_prime is correct, because this function is called in Print_next_prime. In this case, the Print_next_prime function is a unit, and the Is_prime function is another unit. Since unit tests test only one unit at a time, we need to think carefully about how we can 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 has a file called primes.py, we can write the test code in a file called test_primes.py. Below 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 passes a test case:? Test_is_five_prime. A unit test was created. UnitTest through a test framework embedded in Python. When Unittest.main () is invoked, any member functions named at the beginning of test will be run, they are unittest. A derived class of testcase, and is an assertion check. If we run the test by entering the 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% = = 0:
Zerodivisionerror:integer division or modulo by zero
--------------------------------------------------------- -------------
Ran 1 Test in 0.000s
The individual "E" represents the result of our unit test (if it succeeds, it will print out a "."). )。 We can see that our tests failed, the line of code that caused the failure, and any exception information that was thrown.
Why do you want to test?
Before we go on to that example, ask an important question: "Why is the test valuable to me?" This is a fair question, as is the question often asked by people who are unfamiliar with code testing. After all, the test takes a certain amount of time, and we can use the time to code, why test rather than do the most productive things?
There are a number of answers that can effectively answer this question, and I have listed the following points:
Testing can ensure that your code works in a series of given conditions
The tests ensure the correctness of a range of conditions. Grammatical errors are basically detected by testing, and the basic logic of a unit of code can be detected by testing to ensure correctness under certain conditions. Again, it is not to prove that the code is correct under any conditions. We simply aim 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 strings for each parameter)
Testing allows people to ensure that changes to the code do not break existing functionality
This is especially useful when refactoring code. Without a test in place, you won't be able to make sure that your code changes without disrupting what worked properly before. If you want to change or rewrite your code and hope it doesn't break anything, proper unit testing is necessary.
Tests force people to think about code under unusual conditions, which may reveal logical errors
Writing tests forces you to think about the problems your code might encounter under unusual conditions. In the example above, the My_addition_function function can add up to two digits. A simple test of the basic correctness of the test will call My_addition_function (2,2) and assert that the result is 4. However, further testing might test the ability of the function to correct floating-point number operations by calling My_addition_function (2.0,2.0). The defensive coding principle indicates that your code should be able to fail normally in the case of illegal input, so when testing, you should throw an exception when the string type is passed into the function as an argument.
Good test requirements modular, decoupled code, which is a good indicator of system design
The overall approach to unit testing is to make it easier by loosely coupled code. If your application code calls the database directly, for example, the logic of testing your application relies on a valid database connection, and the test data exists in the database. On the other hand, code that isolates external resources is more likely to be replaced by mock objects during testing. As necessary, the testing-capable applications (people) were designed to eventually adopt modularity 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
True
def print_next_prime (num ber): "" "
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 the unit test journey. One unit test includes one or more assertions (some statements that declare that some of the properties of the code being tested are true). Think you're going to school. The literal meaning of the word "assert" is "to state the facts." Assertions are equally useful in unit tests.
Self.asserttrue more like a self explanatory. It can declare that the results of passing past parameters are true. UnitTest. The TestCase class contains many assertion methods, so be sure to check the list and choose the appropriate method for testing. If Asserttrue is used in each test, an inverse pattern should be considered because it increases the cognitive burden of the reader in the test. The correct way to use assertions should be to enable the test to specify exactly what is being asserted (for example, it is obvious?), simply by scanning the Assertisinstance method name, knowing that it is describing its parameters.
Each test should test a separate, specific code, and should be given the relevant naming. Research on the unit Test discovery mechanism (mainly in python2.7+ and 3.2+ versions) indicates that the test method should be named after the Test_ prefix. (This is configurable, but its purpose is to identify test methods and practical methods that are not tested). If we change the name of Test_is_five_prime to Is_five_prime, running the test_primes.py in Python outputs the following message:
$ python test_primes.py
----------------------------------------------------------------------
Ran 0 tests In 0.000s
OK
Do not be fooled by the "OK" in the above information, only when the test is not really running the time will show "OK"! I don't think a test runs. It should actually show an error, but it's a personal feeling, and it's a behavior that you should be aware of, especially when running through a program to check test results (for example, an ongoing integration tool like Tracisci).
Abnormal
Let's go back to test_primes.py's actual content and recall the output after running the Python test_primes.py directive:
$ 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% = = 0:
Zerodivisionerror:integer division or modulo by zero
--------------------------------------------------------- -------------
Ran 1 Test in 0.000s
These outputs tell us that the results of one of our tests failed not because one assertion failed, but because an unhandled exception occurred. In fact, because an exception was thrown, the unittest framework was not able to run our tests and returned.
The problem here is clear: the computation scope of the modulo operation we used includes 0, so we performed a operation divided by 0. To solve this problem, we can easily change the starting value from 0 to 2, and point out that modulo 0 is wrong, and 1 modulo is true (and a prime number can only be divisible by itself and 1, so we don't need to check 1).
Solve the problem
A failed test led us to modify the code. Once we fix this error (change the line 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 that the error has changed, does this mean that we should delete the Test_is_five_prime test method (because it is obvious that it will not always pass the test)? should not be deleted. Unit tests should be deleted as little as possible because the final goal is to pass the test. We have tested that the Is_prime syntax is valid and, at least in one case, it returns the correct result. Our goal is to create a set of tests that can all be passed (a 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's make sure that it also handles the non primes correctly. Add the following methods to the Primestestcase class:
def test_is_four_non_prime (self): "" is
four correctly determined isn't to be prime?
"" Self.assertfalse (Is_prime (4), msg= ' Four is not prime! ')
Notice that we added an optional msg parameter to the Assert invocation. If the test fails, our information is printed to the console and additional information is provided to the person running the test.
Border situation
We have successfully tested two common situations. Now let's take a look at the boundary case, or those unusual or unexpected input cases. When testing a function whose scope is a positive integer, the instance in the boundary case includes 0, 1, negative numbers, and a large number. Now let's test some of them.
Adding a test to 0 is simple. We expect? Is_prime (0) returns false because, by definition, the primes must be greater than 1.
def test_is_zero_not_prime (self): "" is
zero correctly determined isn't 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 isn't to be prime?
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_primes.py", line, in Test_is_zero_not_prime
self.assertfalse (is_prime (0))
assertionerror:true Is isn't false
----------------------------------------------------------------------
Ran 3 tests in 0.000s
FAILED (Failures=1)
0 The wrong decision is prime. We forgot, we decided to skip 0 and 1 in the range of numbers. 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 the
element in range (2, number):
if number% element = = 0: return
Fa LSE return
True
Now the test is passed. What should our function do with a negative number? It is important to know the output results 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 checking all numbers from-1 to-9. It is very legitimate to call the test method in a loop, and it is also possible to invoke the assertion method multiple times in one test. We can rewrite the code in the following way (in more detail).
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 want:
Python test_primes.py ...
F
======================================================================
fail:test_negative_number (__ main__. Primestestcase) is
a negative number correctly determined isn't to be prime?
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_primes.py", line, in Test_negative_number
self.assertfalse (is_prime (index))
Assertionerror: True is isn't false
----------------------------------------------------------------------
Ran 4 tests in 0.000s
FAILED (Failures=1)
Well ... We know that the test failed, but on which negative number did it fail? Very useless, the Python unit test framework does not print out the expected and actual values. We can step into two ways and use one of them to solve the problem: through the MSG parameter, or by using a third party unit to test the framework.
Using the MSG parameter to Assertfalse can only make us realize that we can solve the problem with string formatting.
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 the not being determined to is prime '. Forma T (index))
The following output information is given:
Python test_primes ...
F
======================================================================
fail:test_negative_number ( Test_primes. Primestestcase) is
a negative number correctly determined isn't to be prime?
----------------------------------------------------------------------
Traceback (most recent call last):
File "./test_primes.py", line, in Test_negative_number
self.assertfalse (is_prime (index), msg= ' {} should Determined to being prime '. Format (index) Assertionerror:true is isn't
false:-1 should not being determined to be PRIME
----------------------------------------------------------------------
Ran 4 tests in 0.000s
FAILED ( Failures=1)
Properly fix code
We see that the negative numbers that failed are the first number:-1. To solve this problem, we can add a special check for negative numbers, but the purpose of writing unit tests is not to blindly add code to detect boundary conditions. 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 n Umber% Element = = 0: Return
False return
True
You should first use the following code:
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
fals e return
True
In the latter code, we find that if the argument is less than or equal to 1 o'clock, two if statements can be merged into a statement that returns a value of false. This is not only more concise, but also fits well with the definition of prime numbers (a number larger than 1 and divisible by 1 and itself).
Third-party testing framework
We could have solved this problem by using a Third-party test framework that caused the test to fail due to too little information. The two most commonly used are py.test and nose. The following results can be obtained by running the statement py.test-l (-l to display the value of a 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 is not 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 of the more useful information. These frameworks provide more functionality than simple, more detailed output, but the problem is simply knowing that they can exist and extend the functionality of the built-in UnitTest test packages.
Conclusion
In this article, you learned what unit tests are, why they are so important, and how to write Tests. That is to say, note that we just cut the surface of the test methodology, and more advanced topics such as the organization of test cases, continuous consolidation, and the management of test cases are good topics for readers who want to learn more about the tests in Python.
- Reorganize/clean code without changing its functionality
- Does not expose its internal data or functions while coding and does not use internal data or functions of other code