[ad_1]
Final Up to date on March 22, 2022
Unit testing is a technique for testing software program which appears on the smallest testable items of code, referred to as items, are examined for proper operation. By doing unit testing, we’re capable of confirm that every a part of the code, together with helper capabilities which might not be uncovered to the person, works accurately and as supposed.
The thought is that we’re independently checking every small piece of our program to make sure that it really works. This contrasts with regression and integration testing which exams that the completely different elements of this system work nicely collectively and as supposed.
On this publish, you’ll uncover easy methods to implement unit testing in Python utilizing two common unit testing frameworks, the built-in PyUnit framework and the PyTest framework.
After finishing this tutorial, you’ll know:
- Unit testing libraries in Python comparable to PyUnit and PyTest
- Checking anticipated perform habits by way of the usage of unit exams
Let’s get began!
A Light Introduction to Unit Testing in Python
Photograph by Bee Naturalles. Some rights reserved.
Overview
The tutorial is split into 5 elements, they’re:
- What are unit exams and why are they necessary?
- What’s Check Pushed Growth (TDD)?
- Utilizing Python’s built-in PyUnit framework
- Utilizing PyTest library
- Unit testing in motion
What are unit exams and why are they necessary?
Bear in mind doing math again at school, finishing completely different arithmetic procedures earlier than combining them to get the proper reply? Think about how you’ll test to make sure that the calculations achieved at every step have been appropriate and also you didn’t make any careless errors or miswrote something.
Now, lengthen that concept to code! We wouldn’t need to should continuously look by way of our code to statically confirm its correctness, so how would you create a check to make sure that the next piece of code really returns the realm of the rectangle?
|
def calculate_area_rectangle(width, top): return width * top |
We may run the code with a number of check examples and see if it returns the anticipated output.
That’s the thought of a unit check! A unit check is a check which checks a single element of code, often modularized as a perform, and making certain that it performs as anticipated.
Unit exams are an necessary a part of regression testing to make sure that the code nonetheless capabilities as anticipated after we’ve got made adjustments to the code and helps to make sure code stability. After making adjustments to our code, we are able to run the unit exams we’ve got created beforehand to make sure that the prevailing performance in different elements of the codebase has not been impacted by our adjustments.
One other key advantage of unit exams is that they assist to simply isolate errors. Think about operating the complete challenge and receiving a string of errors. How would we go about debugging our code?
That’s the place unit exams are available in, we are able to analyze the outputs of our unit exams to see if any element of our code has been throwing errors and begin debugging from there. That’s to not say that unit testing can all the time assist us discover the bug, nevertheless it permits for a way more handy place to begin earlier than we begin wanting on the integration of parts in integration testing.
For the remainder of the article, we shall be exhibiting easy methods to do unit testing by testing the capabilities on this Rectangle class
|
class Rectangle: def __init__(self, width, top): self.width = width self.top = top
def get_area(self): return self.width * self.top
def set_width(self, width): self.width = width
def set_height(self, top): self.top = top |
Now that we’ve got motivated unit exams, let’s discover how precisely we are able to use unit exams as a part of our improvement pipeline and easy methods to implement them in Python!
Check Pushed Growth
Testing is so necessary to good software program improvement that there’s even a software program improvement course of primarily based on testing, which is Check Pushed Growth (TDD). Three guidelines of TDD proposed by Robert C. Martin are:
- You aren’t allowed to jot down any manufacturing code except it’s to make a failing unit check cross.
- You aren’t allowed to jot down any extra of a unit check than is ample to fail; and compilation failures are failures.
- You aren’t allowed to jot down any extra manufacturing code than is ample to cross the one failing unit check.
The important thing thought of TDD is that we base our software program improvement round a set of unit exams that we’ve got created, which makes unit testing the center of the TDD software program improvement course of. On this method, you assured that you’ve got a check for each element you developed.
TDD additionally biases in direction of having smaller exams which implies exams which can be extra particular and check fewer parts at a time. This aids in monitoring down errors and smaller exams are additionally simpler to learn and perceive since there are much less parts at play in a single run.
It doesn’t imply you could use TDD to your tasks. However you could think about that as a technique to develop your code and the exams on the identical time.
Utilizing Python built-in PyUnit framework
You is likely to be questioning, why do we’d like unit testing frameworks since Python and different languages supply the assert key phrase? Unit testing frameworks assist to automate the testing course of and permit us to run a number of exams on the identical perform with completely different parameters, test for anticipated exceptions, and lots of others.
PyUnit is Python’s built-in unit testing framework and is Python’s model of the corresponding JUnit testing framework for Java. To get began constructing a check file, we have to import the unittest library to make use of PyUnit:
Then, we are able to get began writing out first unit check. Unit exams in PyUnit are structured as subclasses of the unittest.TestCase class and we are able to override the runTest() technique to carry out our personal unit exams which test situations utilizing completely different assert capabilities in unittest.TestCase:
|
class TestGetAreaRectangle(unittest.TestCase): def runTest(self): rectangle = Rectangle(2, 3) self.assertEqual(rectangle.get_area(), 6, “incorrect space”) |
That’s our first unit check! It checks if the rectangle.get_area() technique returns the proper space for a rectangle with width = 2 and size = 3. We use self.assertEqual as a substitute of merely utilizing assert to permit the unittest library to permit the runner to build up all check instances and produce a report.
Utilizing the completely different assert capabilities in unittest.TestCase additionally offers us a greater means to check completely different behaviors comparable to self.assertRaises(exception) which permits us to test if a sure block of code produces an anticipated exception.
To run the unit check, we make a name to unittest.primary() in our program,
Because the code returns the anticipated output for this case, it returns that the exams run efficiently, with the output:
|
. ———————————————————————- Ran 1 check in 0.003s
OK |
The whole code is as follows:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import unittest
# Our code to be examined class Rectangle: def __init__(self, width, top): self.width = width self.top = top
def get_area(self): return self.width * self.top
def set_width(self, width): self.width = width
def set_height(self, top): self.top = top
# The check primarily based on unittest module class TestGetAreaRectangle(unittest.TestCase): def runTest(self): rectangle = Rectangle(2, 3) self.assertEqual(rectangle.get_area(), 6, “incorrect space”)
# run the check unittest.primary() |
Observe: Whereas within the above, our enterprise logic Rectangle class and our check code TestGetAreaRectangle are put collectively, in actuality you could put them in separate information and import the enterprise logic into your check code. This may help you higher handle the code.
We are able to additionally nest a number of unit exams collectively in a single subclass of unittest.TestCase, by naming strategies within the new subclass with the “check” prefix, for instance:
|
class TestGetAreaRectangle(unittest.TestCase): def test_normal_case(self): rectangle = Rectangle(2, 3) self.assertEqual(rectangle.get_area(), 6, “incorrect space”)
def test_negative_case(self): “”“count on -1 as output to indicate error when destructive space”“” rectangle = Rectangle(–1, 2) self.assertEqual(rectangle.get_area(), –1, “incorrect destructive output”) |
Working this may give us our first error:
|
F. ====================================================================== FAIL: test_negative_case (__main__.TestGetAreaRectangle) count on -1 as output to indicate error when destructive space ———————————————————————- Traceback (most up-to-date name final): File “<ipython-input-96-59b1047bb08a>”, line 9, in test_negative_case self.assertEqual(rectangle.get_area(), -1, “incorrect destructive output”) AssertionError: -2 != -1 : incorrect destructive output ———————————————————————- Ran 2 exams in 0.003s
FAILED (failures=1) |
We are able to see the unit check which failed, which is the test_negative_case as highlighted within the output together with the stderr message, since get_area() doesn’t return -1 as we anticipated in our check.
There are various completely different sorts of assert capabilities outlined within the unittest. TestCase class which we are able to use, for instance
|
def test_geq(self): “”“exams if worth is larger than or equal to a selected goal”“” self.assertGreaterEqual(self.rectangle.get_area(), –1) |
We are able to even test whether or not a selected exception was thrown throughout execution
|
def test_assert_raises(self): “”“utilizing assertRaises to detect if an anticipated error is raised when operating a selected block of code”“” with self.assertRaises(ZeroDivisionError): a = 1 / 0 |
Now, we take a look at build up our exams. What if we had some code that we would have liked to run to arrange earlier than operating every check? Effectively, we are able to override the setUp technique in unittest.TestCase.
|
class TestGetAreaRectangleWithSetUp(unittest.TestCase): def setUp(self): self.rectangle = Rectangle(0, 0)
def test_normal_case(self): self.rectangle.set_width(2) self.rectangle.set_height(3) self.assertEqual(self.rectangle.get_area(), 6, “incorrect space”)
def test_negative_case(self): “”“count on -1 as output to indicate error when destructive space”“” self.rectangle.set_width(–1) self.rectangle.set_height(2) self.assertEqual(self.rectangle.get_area(), –1, “incorrect destructive output”) |
Within the above code instance, we’ve got overridden the setUp() technique from unittest.TestCase, with our personal setUp() technique that initializes a Rectangle object. This setUp() technique is run prior to every unit check and is useful in avoiding code duplication when a number of exams depend on the identical piece of code to arrange the check. That is just like the @Earlier than decorator in JUnit.
Likewise, there’s a tearDown() technique that we are able to override as nicely for code to be executed after every check.
To run the strategy solely as soon as per TestCase class, we are able to additionally use the setUpClass technique as follows.
|
class TestGetAreaRectangleWithSetUp(unittest.TestCase): @classmethod def setUpClass(self): self.rectangle = Rectangle(0, 0) |
The above code is just run as soon as per TestCase as a substitute of as soon as per check run as is the case with setUp.
To assist us set up exams and choose which set of exams we need to run, we are able to mixture check instances into check suites which assist to group exams that ought to executed collectively right into a single object:
|
... # masses all unit exams from TestGetAreaRectangle right into a check suite calculate_area_suite = unittest.TestLoader() .loadTestsFromTestCase(TestGetAreaRectangleWithSetUp) |
Right here, we additionally introduce one other method to run exams in PyUnit which is utilizing the unittest.TextTestRunner class, which permits us to run particular check suites.
|
runner = unittest.TextTestRunner() runner.run(calculate_area_suite) |
which supplies the identical output as operating the file from the command line and calling unittest.primary().
Bringing every little thing collectively, that is what the whole script for the unit check would appear to be:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class TestGetAreaRectangleWithSetUp(unittest.TestCase):
@classmethod def setUpClass(self): #this technique is just run as soon as for the complete class slightly than being run for every check which is completed for setUp() self.rectangle = Rectangle(0, 0)
def test_normal_case(self): self.rectangle.set_width(2) self.rectangle.set_height(3) self.assertEqual(self.rectangle.get_area(), 6, “incorrect space”)
def test_geq(self): “”“exams if worth is larger than or equal to a selected goal”“” self.assertGreaterEqual(self.rectangle.get_area(), –1)
def test_assert_raises(self): “”“utilizing assertRaises to detect if an anticipated error is raised when operating a selected block of code”“” with self.assertRaises(ZeroDivisionError): a = 1 / 0 |
That is simply the tip of the iceberg with what you are able to do with PyUnit. We are able to additionally write exams that search for exception messages that match a regex expression or setUp/tearDown strategies which can be run solely as soon as (setUpClass), for instance.
Utilizing PyTest
PyTest is an alternative choice to the built-in unittest module. To get began with PyTest, you’ll first want to put in it, which you are able to do utilizing
To put in writing exams, you simply want to jot down capabilities with names prefixed with “check” and PyTest’s check discovery process will be capable of discover your exams, e.g.,
|
def test_normal_case(self): rectangle = Rectangle(2, 3) assert rectangle.get_area() == 6, “incorrect space” |
You’ll discover that PyTest makes use of Python’s built-in assert key phrase as a substitute of its personal set of assert capabilities as PyUnit does, which could make it barely extra handy since we are able to keep away from looking up the completely different assert capabilities.
The whole code is as follows:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# Our code to be examined class Rectangle: def __init__(self, width, top): self.width = width self.top = top
def get_area(self): return self.width * self.top
def set_width(self, width): self.width = width
def set_height(self, top): self.top = top
# The check perform to be executed by PyTest def test_normal_case(): rectangle = Rectangle(2, 3) assert rectangle.get_area() == 6, “incorrect space” |
After saving this right into a file test_file.py, we are able to run PyTest unit check by:
|
python –m pytest test_file.py |
And this provides us the output:
|
=================== check session begins ==================== platform darwin — Python 3.9.9, pytest-7.0.1, pluggy-1.0.0 rootdir: /Customers/MLM plugins: anyio-3.4.0, typeguard-2.13.2 collected 1 merchandise
test_file.py . [100%]
==================== 1 handed in 0.01s ===================== |
It’s possible you’ll discover that whereas in PyUnit, we have to invoke the check routine by a runner or calling unittest.primary(). However in PyTest, we merely cross the file to the module. The PyTest module will acquire all of the capabilities outlined with prefix check and name them one after the other. After which it’ll confirm if any exception is raised by the assert assertion. It may be extra handy to permit the exams stick with the enterprise logic.
PyTest additionally helps grouping capabilities collectively in courses, however the class ought to be named with prefix “Check” (with uppercase T), e.g.
|
class TestGetAreaRectangle: def test_normal_case(self): rectangle = Rectangle(2, 3) assert rectangle.get_area() == 6, “incorrect space” def test_negative_case(self): “”“count on -1 as output to indicate error when destructive space”“” rectangle = Rectangle(–1, 2) assert rectangle.get_area() == –1, “incorrect destructive output” |
Working this with PyTest will produce the next output:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
=================== check session begins ==================== platform darwin — Python 3.9.9, pytest-7.0.1, pluggy-1.0.0 rootdir: /Customers/MLM plugins: anyio-3.4.0, typeguard-2.13.2 collected 2 gadgets
test_code.py .F [100%]
========================= FAILURES ========================= _________ TestGetAreaRectangle.test_negative_case __________
self = <test_code.TestGetAreaRectangle object at 0x10f5b3fd0>
def test_negative_case(self): “””count on -1 as output to indicate error when destructive space””” rectangle = Rectangle(-1, 2) > assert rectangle.get_area() == -1, “incorrect destructive output” E AssertionError: incorrect destructive output E assert -2 == -1 E + the place -2 = <certain technique Rectangle.get_area of <test_code.Rectangle object at 0x10f5b3df0>>() E + the place <certain technique Rectangle.get_area of <test_code.Rectangle object at 0x10f5b3df0>> = <test_code.Rectangle object at 0x10f5b3df0>.get_area
unittest5.py:24: AssertionError ================= brief check abstract data ================== FAILED test_code.py::TestGetAreaRectangle::test_negative_case =============== 1 failed, 1 handed in 0.12s ================ |
The whole code is as follows:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# Our code to be examined class Rectangle: def __init__(self, width, top): self.width = width self.top = top
def get_area(self): return self.width * self.top
def set_width(self, width): self.width = width
def set_height(self, top): self.top = top
# The check capabilities to be executed by PyTest class TestGetAreaRectangle: def test_normal_case(self): rectangle = Rectangle(2, 3) assert rectangle.get_area() == 6, “incorrect space” def test_negative_case(self): “”“count on -1 as output to indicate error when destructive space”“” rectangle = Rectangle(–1, 2) assert rectangle.get_area() == –1, “incorrect destructive output” |
To implement setup and teardown code for our exams, PyTest has a particularly versatile fixture system, the place fixtures are capabilities which have a return worth. PyTest’s fixture system permits for sharing of fixtures throughout courses, modules, packages, or classes, and fixtures that may name different fixtures as arguments.
Right here we embrace a easy introduction to PyTest’s fixture system,
|
@pytest.fixture def rectangle(): return Rectangle(0, 0)
def test_negative_case(rectangle): print (rectangle.width) rectangle.set_width(–1) rectangle.set_height(2) assert rectangle.get_area() == –1, “incorrect destructive output” |
The above code introduces rectangle as a fixture and PyTest matches the rectangle within the argument checklist of test_negative_case with the fixture and supplies test_negative_case with its personal set of outputs from the rectangle perform, and does this for each different check. Nonetheless, notice that fixtures will be requested greater than as soon as per check and for every check, the fixture is just run as soon as and the result’s cached. Because of this all references to that fixture through the operating of a person check are referencing the identical return worth (which is necessary if the return worth is a reference kind).
The whole code is as follows:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import pytest
# Our code to be examined class Rectangle: def __init__(self, width, top): self.width = width self.top = top
def get_area(self): return self.width * self.top
def set_width(self, width): self.width = width
def set_height(self, top): self.top = top
@pytest.fixture def rectangle(): return Rectangle(0, 0)
def test_negative_case(rectangle): print (rectangle.width) rectangle.set_width(–1) rectangle.set_height(2) assert rectangle.get_area() == –1, “incorrect destructive output” |
Like PyUnit, PyTest has numerous different performance that may will let you construct extra complete and superior unit exams.
Unit Testing in Motion
Now, we’ll discover unit testing in motion. For our instance, we’ll be testing a perform that will get inventory knowledge from Yahoo Finance utilizing pandas_datareader and doing this in PyUnit:
|
import pandas_datareader.knowledge as net
def get_stock_data(ticker): “”“pull knowledge from stooq”“” df = net.DataReader(ticker, “yahoo”) return df |
This perform will get the inventory knowledge on a selected inventory ticker by crawling from Yahoo Finance web site and returns the pandas DataFrame. This may fail in a number of methods, for instance, the information reader might did not return something (if Yahoo Finance is down), or return a DataFrame with lacking columns or lacking knowledge within the columns (if the supply restructured its web site). Due to this fact, we must always present a number of check capabilities to test for a number of modes of failure:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
import datetime import unittest
import pandas as pd import pandas_datareader.knowledge as net
def get_stock_data(ticker): “”“pull knowledge from stooq”“” df = net.DataReader(ticker, ‘yahoo’) return df
class TestGetStockData(unittest.TestCase): @classmethod def setUpClass(self): “”“We solely need to pull this knowledge as soon as for every TestCase since it’s an costly operation”“” self.df = get_stock_data(‘^DJI’)
def test_columns_present(self): “”“ensures that the anticipated columns are all current”“” self.assertIn(“Open”, self.df.columns) self.assertIn(“Excessive”, self.df.columns) self.assertIn(“Low”, self.df.columns) self.assertIn(“Shut”, self.df.columns) self.assertIn(“Quantity”, self.df.columns)
def test_non_empty(self): “”“ensures that there’s multiple row of knowledge”“” self.assertNotEqual(len(self.df.index), 0)
def test_high_low(self): “”“guarantee excessive and low are the very best and lowest in the identical row”“” ohlc = self.df[[“Open”,“High”,“Low”,“Close”]] highest = ohlc.max(axis=1) lowest = ohlc.min(axis=1) self.assertTrue(ohlc.le(highest, axis=0).all(axis=None)) self.assertTrue(ohlc.ge(lowest, axis=0).all(axis=None))
def test_most_recent_within_week(self): “”“most up-to-date knowledge was collected throughout the final week”“” most_recent_date = pd.to_datetime(self.df.index[–1]) self.assertLessEqual((datetime.datetime.as we speak() – most_recent_date).days, 7)
unittest.primary() |
Our sequence of unit exams above test if sure columns are current (test_columns_present), whether or not the dataframe is non-empty (test_non_empty), whether or not the “excessive” and “low” columns are actually the excessive and low of the identical row (test_high_low), and whether or not the newest knowledge within the DataFrame was throughout the final 7 days (test_most_recent_within_week).
Think about you might be doing a machine studying challenge that consumes the inventory market knowledge. Having a unit check framework may help you determine in case your knowledge preprocessing is working as anticipated.
Utilizing these unit exams, we’re capable of determine if there was a fabric change within the output of our perform, and will be part of a Steady Integration (CI) course of. We are able to additionally connect different unit exams as required relying on the performance that we rely on from that perform.
For completeness, right here’s an equal model for PyTest.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import pytest
# scope=”class” tears down the fixture solely on the finish of the final check within the class, so we keep away from rerunning this step. @pytest.fixture(scope=“class”) def stock_df(): # We solely need to pull this knowledge as soon as for every TestCase since it’s an costly operation df = get_stock_data(‘^DJI’) return df
class TestGetStockData:
def test_columns_present(self, stock_df): # ensures that the anticipated columns are all current assert “Open” in stock_df.columns assert “Excessive” in stock_df.columns assert “Low” in stock_df.columns assert “Shut” in stock_df.columns assert “Quantity” in stock_df.columns
def test_non_empty(self, stock_df): # ensures that there’s multiple row of knowledge assert len(stock_df.index) != 0
def test_most_recent_within_week(self, stock_df): # most up-to-date knowledge was collected throughout the final week most_recent_date = pd.to_datetime(stock_df.index[0]) assert (datetime.datetime.as we speak() – most_recent_date).days <= 7 |
Constructing unit exams might sound time consuming and tedious however they could be a crucial a part of any CI pipeline and are invaluable instruments for catching bugs early on earlier than they transfer additional down the pipeline and turn into extra pricey to deal with.
For those who prefer it then you need to have put a check on it
— Software program Engineering at Google
Additional studying
This part supplies extra assets on the subject if you’re seeking to go deeper.
Libraries
Articles
Books
Abstract
On this publish, you found what unit testing is and easy methods to use two common libraries in Python to conduct unit testing (PyUnit, PyTest). You might have additionally learnt easy methods to configure unit exams and seen an instance of a use case for unit testing within the knowledge science pipeline.
Particularly, you discovered:
- what unit testing is and why it’s helpful
- how unit testing suits throughout the Check Pushed Growth pipeline
- easy methods to do unit testing in Python utilizing PyUnit and PyTest
[ad_2]
