Like many of you, I come from a purely networking background. When I first started my network automation journey, I felt I was making great strides. However, I was, unknowingly, missing a very important component in my solutions—testing. I don’t mean testing what my code did in the network; I mean testing my code to ensure it acts as expected. That’s where pytest comes in. There are numerous examples of pytest in action on the internet but none showing those examples with networking data. As a somewhat visual learner myself, seeing an example of code with arbitrary data (e.g., Foo, Bar, i, j) versus seeing it with networking data (e.g., GigabiteEthernet1/1, Vlan100, etc.) makes a world of a difference to me. In this blog post I will give a quick overview on what pytest is, some of its important compenents, and show an example of how it can be used to test a function that normalizes MAC addresses.
What Is Pytest?
Pytest is a framework that helps you write unit and integration tests for your code. At its core, it’s pretty simple. However, that simplicity allows the pytest framework to be scaled very easily to accurately test large projects a well as lend itself to a test-driven development (TDD) approach to writing code. I’ll take this approach when I explain examples down below. Pytest has some cool features that you need to know about to understand the examples.
Any files following the conventions test_*.py or *_test.py are automatically discovered by pytest.
Any functions with test prefixed are automatically discovered.
Fixtures can be used to pass generated arguments to your tests.
Let’s say we are gathering MAC address info from our campus switches. After looking through what we’ve gathered, we notice we’re getting MAC addresses in two different formats:
aa:bb:cc:dd:ee:ff
aabb.ccdd.eeff
We need to normalize these to a single format. We decide that we want all MACs to be in the format aa:bb:cc:dd:ee:ff. Taking a TDD approach, we want to write a test before we actually develop any code. Our goal is to take a non-normalized MAC address, format it, and assert that the newly formated MAC address conforms to our defined standard. With that in mind, let’s create a file called test_mac_address.py and create the test:
Let’s break this down. The first line is defining our function, test_normalize_mac_address, which is expecting 2 arguments. Where are these arguments coming from? The arguments are coming from what pytest calls fixtures. These will be discussed in the next section. Our next line calls the function normalize_mac_address (which we have yet to define) passing in our non-normalized MAC address. Finally, the last line asserts that the MAC returned from our function is what we expect. Now that we’ve gone over what each line is doing, let’s quickly discuss fixtures.
Adding Fixtures
Fixtures are functions that are run before tests and are used to pass arguments into test functions. They are defined by the decorator @pytest.fixture. I mentioned before that we had two arguments we needed to pass into our test, so let’s make those two fixtures. In the same file where added our test function, let’s add these two fixtures:
You’ll notice the names of these fixtures match the names of the arguments in our test function. This must be done this way. When we run our test function, the two arguments are noticed and pytest looks for fixtures that have the same name. The fixture functions are then run and the returned value is used as the argument into the original test function.
Adding Our normalize_mac_address Function
Lastly, we need to add our normalize_mac_address function that we defined on the second line of our test function. Let’s create a file in the same directory we created our test file and name it mac_address.py. In this file let’s put our function:
For now, lets return the non-normalized MAC address to see what results pytest displays. We need to do one last thing before we run our pytest, and that is telling our test_mac_address.py file where to find the normalize_mac_address function. We can do this by adding the line:
from mac_address import normalize_mac_address
Our test_mac_address.py file, in its complete form, should look like this.
Now, let’s run our test! In the same directory as our two files, let’s run pytest -v.
===================================================== test session starts =========================================platform linux -- Python 3.8.2, pytest-6.1.2, py-1.9.0, pluggy-0.13.1--/home/adam/.virtualenvs/pytest/bin/pythoncachedir:.pytest_cacherootdir:/home/adam/repo/Sandbox/blog_pytestcollected 1 itemtest_mac_address.py::test_normalize_mac_address FAILED [100%]========================================================= FAILURES =================================================non_normalized_mac_address ='aabb.ccdd.eeff', expected_mac_address ='aa:bb:cc:dd:ee:ff' def test_normalize_mac_address(non_normalized_mac_address, expected_mac_address): normalized_mac_address =normalize_mac_address(mac_address)> assert normalized_mac_address == expected_mac_addressE AssertionError: assert 'aabb.ccdd.eeff'=='aa:bb:cc:dd:ee:ff'E -aa:bb:cc:dd:ee:ffE ?-^-^-E + aabb.ccdd.eeffE ?^^test_mac_address.py:6: AssertionError================================================= short test summary info ============================================FAILED test_mac_address.py::test_normalize_mac_address -AssertionError: assert 'aabb.ccdd.eeff'=='aa:bb:cc:dd:ee:ff'
The last line gives a clear picture of why the assertion failed. This is what we expected as we didn’t do anything to the MAC address in the normalize_mac_address function. Let’s update that function and run the test again. Here is the updated function:
>>> def normalize_mac_address(mac):>>>if mac.count(".") ==2:>>> mac = f"{mac[0:2]}:{mac[2:4]}:{mac[5:7]}:{mac[7:9]}:{mac[10:12]}:{mac[12:14]}">>>return mac
Now we get this output when we run pytest.
======================================== test session starts =====================================================platform linux -- Python 3.8.2, pytest-6.1.2, py-1.9.0, pluggy-0.13.1--/home/adam/.virtualenvs/pytest/bin/pythoncachedir:.pytest_cacherootdir:/home/adam/repo/Sandbox/blog_pytestcollected 1 itemtest_mac_address.py::test_normalize_mac_address PASSED [100%]========================================1 passed in 0.01s =======================================================
Our test has passed!
Conclusion
Unit testing, and testing in general, is a very important part of the network automation journey. I hope this blog post has shed some light on what pytest is and the basic functionality of it. As the example is, there are only two types of MAC addresses. What would happen if we passed in a MAC address in the format aabbcc-ddeeff to our function? The test would fail.
In my next blog post I’ll dive into expanding on this example to use parameterization and other features of pytest. Thanks for reading!
Does this all sound amazing? Want to know more about how Network to Code can help you do this, reach out to our sales team. If you want to help make this a reality for our clients, check out our careers page.
We use cookies on our website to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “Accept”, you consent to the use of ALL the cookies. In case of sale of your personal information, you may opt out by using the link Do not sell my personal information. Privacy | Cookies
This website uses cookies to improve your experience while you navigate through the website. Out of these cookies, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may have an effect on your browsing experience.
Necessary cookies are absolutely essential for the website to function properly. These cookies ensure basic functionalities and security features of the website, anonymously.
Cookie
Duration
Description
__hssc
30 minutes
HubSpot sets this cookie to keep track of sessions and to determine if HubSpot should increment the session number and timestamps in the __hstc cookie.
__hssrc
session
This cookie is set by Hubspot whenever it changes the session cookie. The __hssrc cookie set to 1 indicates that the user has restarted the browser, and if the cookie does not exist, it is assumed to be a new session.
cookielawinfo-checkbox-advertisement
1 year
Set by the GDPR Cookie Consent plugin, this cookie records the user consent for the cookies in the "Advertisement" category.
cookielawinfo-checkbox-analytics
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics".
cookielawinfo-checkbox-functional
11 months
The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional".
cookielawinfo-checkbox-necessary
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary".
cookielawinfo-checkbox-others
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other.
cookielawinfo-checkbox-performance
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance".
CookieLawInfoConsent
1 year
CookieYes sets this cookie to record the default button state of the corresponding category and the status of CCPA. It works only in coordination with the primary cookie.
viewed_cookie_policy
11 months
The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data.
Functional cookies help to perform certain functionalities like sharing the content of the website on social media platforms, collect feedbacks, and other third-party features.
Cookie
Duration
Description
__cf_bm
30 minutes
Cloudflare set the cookie to support Cloudflare Bot Management.
li_gc
5 months 27 days
Linkedin set this cookie for storing visitor's consent regarding using cookies for non-essential purposes.
lidc
1 day
LinkedIn sets the lidc cookie to facilitate data center selection.
UserMatchHistory
1 month
LinkedIn sets this cookie for LinkedIn Ads ID syncing.
Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.
Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics the number of visitors, bounce rate, traffic source, etc.
Cookie
Duration
Description
__hstc
5 months 27 days
Hubspot set this main cookie for tracking visitors. It contains the domain, initial timestamp (first visit), last timestamp (last visit), current timestamp (this visit), and session number (increments for each subsequent session).
_ga
1 year 1 month 4 days
Google Analytics sets this cookie to calculate visitor, session and campaign data and track site usage for the site's analytics report. The cookie stores information anonymously and assigns a randomly generated number to recognise unique visitors.
_gat_gtag_UA_*
1 minute
Google Analytics sets this cookie to store a unique user ID.
_gid
1 day
Google Analytics sets this cookie to store information on how visitors use a website while also creating an analytics report of the website's performance. Some of the collected data includes the number of visitors, their source, and the pages they visit anonymously.
AnalyticsSyncHistory
1 month
Linkedin set this cookie to store information about the time a sync took place with the lms_analytics cookie.
CONSENT
2 years
YouTube sets this cookie via embedded YouTube videos and registers anonymous statistical data.
hubspotutk
5 months 27 days
HubSpot sets this cookie to keep track of the visitors to the website. This cookie is passed to HubSpot on form submission and used when deduplicating contacts.
ln_or
1 day
Linkedin sets this cookie to registers statistical data on users' behaviour on the website for internal analytics.
Advertisement cookies are used to provide visitors with relevant ads and marketing campaigns. These cookies track visitors across websites and collect information to provide customized ads.
Cookie
Duration
Description
bcookie
1 year
LinkedIn sets this cookie from LinkedIn share buttons and ad tags to recognize browser IDs.
bscookie
1 year
LinkedIn sets this cookie to store performed actions on the website.
li_sugr
3 months
LinkedIn sets this cookie to collect user behaviour data to optimise the website and make advertisements on the website more relevant.
VISITOR_INFO1_LIVE
5 months 27 days
YouTube sets this cookie to measure bandwidth, determining whether the user gets the new or old player interface.
YSC
session
Youtube sets this cookie to track the views of embedded videos on Youtube pages.
yt-remote-connected-devices
never
YouTube sets this cookie to store the user's video preferences using embedded YouTube videos.
yt-remote-device-id
never
YouTube sets this cookie to store the user's video preferences using embedded YouTube videos.
yt.innertube::nextId
never
YouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.
yt.innertube::requests
never
YouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen.