{"id":5320,"date":"2021-09-04T07:45:01","date_gmt":"2021-09-04T07:45:01","guid":{"rendered":"https:\/\/karneliuk.com\/?p=5320"},"modified":"2021-09-04T07:45:03","modified_gmt":"2021-09-04T07:45:03","slug":"pygnmi-11-measuring-automated-testing-with-coverage-py-and-pytest","status":"publish","type":"post","link":"https:\/\/karneliuk.com\/2021\/09\/pygnmi-11-measuring-automated-testing-with-coverage-py-and-pytest\/","title":{"rendered":"pygnmi 11. Measuring Automated Testing with Coverage.py and Pytest"},"content":{"rendered":"\n<p>Hello my friend,<\/p>\n\n\n\n<p>It is been a while since we posted our last blogpost, which was touching Infrastructure aspects of <a rel=\"noreferrer noopener\" href=\"https:\/\/bit.ly\/3m4GP9X\" target=\"_blank\">building Multi Server Cloud with ProxMox<\/a>. After summer break we continue our blogging and developing activities at Karneliuk.com. Today we&#8217;ll show some backstage of the software development of the <a href=\"https:\/\/bit.ly\/2RPF8gi\" target=\"_blank\" rel=\"noreferrer noopener\">pygnmi<\/a>.<\/p>\n\n\n\n<!--more-->\n\n\n\n<pre class=\"wp-block-code\">\n\n<div class=\"codecolorer-container text default\" style=\"overflow:auto;white-space:nowrap;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/>2<br \/>3<br \/>4<br \/>5<br \/><\/div><\/td><td><div class=\"text codecolorer\">No part of this blogpost could be reproduced, stored in a <br \/>\nretrieval system, or transmitted in any form or by any <br \/>\nmeans, electronic, mechanical or photocopying, recording, <br \/>\nor otherwise, for commercial purposes without the <br \/>\nprior permission of the author.<\/div><\/td><\/tr><\/tbody><\/table><\/div>\n\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">What is Automation? Network Automation? <\/h2>\n\n\n\n<p>In a nutshell, Automation and Network Automation are just subset of tasks from a broader topic called Software Development. With the move of the world towards Industry 4.0, the digital economy growth and others, all sort of applications are becoming more widely used. In order you create a history, and not being a part of that in the past, you shall know the principles of software development and be able to create applications yourselves. Sounds complicated? It may be, indeed. However, with our Network Automation Training you definitely will have a break-through in the software development world.<\/p>\n\n\n<div class='code-block code-block-1' style='margin: 8px auto; text-align: center; display: block; clear: both;'>\n<p><a href=\"http:\/\/bit.ly\/2mP3SJy\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" class=\"aligncenter wp-image-4479 size-large\" src=\"https:\/\/karneliuk.com\/wp-content\/uploads\/2022\/08\/4_trainings.jpg\" alt=\"\"\/><\/a><\/p><\/div>\n\n\n\n\n<p>At our trainings, <a rel=\"noreferrer noopener\" href=\"http:\/\/bit.ly\/2mP3SJy\" target=\"_blank\">advanced network automation<\/a> and <a href=\"https:\/\/bit.ly\/3obN0XQ\" target=\"_blank\" rel=\"noreferrer noopener\">automation with Nornir<\/a> (2nd step after advanced network automation), we give you detailed knowledge of all the technologies relevant:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Success and failure strategies to build the automation tools.<\/li><li>Principles of software developments and tools for that.<\/li><li>Data encoding (free-text, XML, JSON, YAML, Protobuf)<\/li><li>Model-driven network automation with YANG, NETCONF, RESTCONF, GNMI.<\/li><li>Full configuration templating with Jinja2 based on the source of truth (NetBox).<\/li><li>Best programming languages (Python, Bash) for developing automation, configuration management tools (Ansible) and automation frameworks (Nornir).<\/li><li>Network automation infrastructure (Linux, Linux networking, KVM, Docker).<\/li><\/ul>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p>Great class. Anton is top notch. Looking forward to more offerings from Karneliuk.com<\/p><cite>Rasheim Myers @ Cisco Systems<\/cite><\/blockquote>\n\n\n\n<p>Moreover, we put all mentions technologies in the context of the real use cases, which our team has solved and are solving in various projects in the service providers, enterprise and data centre networks and systems across the Europe and USA. That gives you opportunity to ask questions to understand the solutions in-depts and have discussions about your own projects. And on top of that, each technology is provided with online demos and you are doing the lab afterwards to master your skills. Such a mixture creates a unique learning environment, which all students value so much. Join us and unleash your potential.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p><a href=\"https:\/\/bit.ly\/2ZexI9M\" target=\"_blank\" rel=\"noreferrer noopener\">Start your automation training today.<\/a><\/p><\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">Brief Description<\/h2>\n\n\n\n<p>Some time ago we shared some ideas about <a rel=\"noreferrer noopener\" href=\"https:\/\/bit.ly\/3rNlGBQ\" target=\"_blank\">automated network testing with <strong>pygnmi<\/strong> and <strong>pytest<\/strong> libraries<\/a>. In that blogpost we explained, that we are using pytest for two purposes:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>For the automated testing of the network configuration.<\/li><li>For the automated testing of parts of our software (e.g., <strong>pygnmi<\/strong> itself), when we develop that.<\/li><\/ul>\n\n\n\n<p>The first use case we covered in the before mentioned blogpost, whereas today we&#8217;ll take a look more on the second one.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p>Refer <a href=\"https:\/\/bit.ly\/3rNlGBQ\" target=\"_blank\" rel=\"noreferrer noopener\">to the original blogpost<\/a> for further details on network configuration testing.<\/p><\/blockquote>\n\n\n\n<p>Today, we&#8217;ll talk about the second use case. Now, you may think, what is the advantage of the automated software testing with separate tools, if we can test ourselves manually? There are two good answers to this question:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Speed<\/strong>. You get the results of your tests almost instantly (just matter fo seconds) rather than hours when you analyse the outcome of tests manually.<\/li><li><strong>Consistency<\/strong>. Once you define test cases, you don&#8217;t worry anymore if you forget testing something. Time to time you would just need to validate if your test cases are still accurate or not.<\/li><\/ul>\n\n\n\n<p>From our project perspective, <strong>pygnmi<\/strong>, automated tests have another important benefit. We are quite proud that not only <a rel=\"noreferrer noopener\" href=\"https:\/\/karneliuk.com\" target=\"_blank\">Team Karneliuk.com<\/a> contributes to its development, but there is a number of other contributors world wide making our project truly <strong>open source<\/strong>. That enables much quicker development of the <strong>pygnmi<\/strong> compared to what it would have Benn if only we were developing that. On the other hand, being ultimately responsible for the stability of the <strong>pygnmi<\/strong>, we are genuinely interested in each release to be thoroughly tested. As such, we can focus on understanding the created code by contributors and developing the unit tests for them to make sure that new features and improvements of existing works properly.<\/p>\n\n\n\n<p>Okay, by this point you may be thinking, that automated tests for software development is a cool thing. However, what is the coverage of tests? Coverage is a metric showing how extensively we have tested our software. In essence, it shows percentage of the lines of code from the target module or modules used in your unit tests. The higher this percentage, the better it is, as it ultimately means that your module is tested more in-depth. Sounds good, isn&#8217;t it?<\/p>\n\n\n\n<p>Is there also a possibility to analyse the coverage of unit tests with <strong>pytest<\/strong> in an automated way? Yes, it is possible. To do that, we could use another Python library <a rel=\"noreferrer noopener\" href=\"https:\/\/pypi.org\/project\/coverage\/\" target=\"_blank\">called coverage.py<\/a>. This library allows you to:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>collect results of the <strong>pytest<\/strong> execution;<\/li><li>analyse which lines of code from a target module or modules were executed during the <strong>pytest<\/strong> run;<\/li><li>produce a report showing the coverage in percentage as well which lines were not used.<\/li><\/ul>\n\n\n\n<p>The outcome of the report can act as a guide for you in unit test developing to make sure you test everything (if you want to achieve 100% coverage). Let&#8217;s see how it works.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lab Setup<\/h2>\n\n\n\n<p>For this blogpost, as for other blogposts in <strong>pygnmi<\/strong> series, we are using the following topology:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"576\" src=\"https:\/\/karneliuk.com\/wp-content\/uploads\/2021\/01\/Karneliuk_com_pygnmi_topology-1024x576.png\" alt=\"\" class=\"wp-image-4350\" srcset=\"https:\/\/karneliuk.com\/wp-content\/uploads\/2021\/01\/Karneliuk_com_pygnmi_topology-1024x576.png 1024w, https:\/\/karneliuk.com\/wp-content\/uploads\/2021\/01\/Karneliuk_com_pygnmi_topology-300x169.png 300w, https:\/\/karneliuk.com\/wp-content\/uploads\/2021\/01\/Karneliuk_com_pygnmi_topology-768x432.png 768w, https:\/\/karneliuk.com\/wp-content\/uploads\/2021\/01\/Karneliuk_com_pygnmi_topology-711x400.png 711w, https:\/\/karneliuk.com\/wp-content\/uploads\/2021\/01\/Karneliuk_com_pygnmi_topology-409x230.png 409w, https:\/\/karneliuk.com\/wp-content\/uploads\/2021\/01\/Karneliuk_com_pygnmi_topology-600x338.png 600w, https:\/\/karneliuk.com\/wp-content\/uploads\/2021\/01\/Karneliuk_com_pygnmi_topology-818x460.png 818w, https:\/\/karneliuk.com\/wp-content\/uploads\/2021\/01\/Karneliuk_com_pygnmi_topology.png 1280w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>At the current moment, we are running automated tests only against Arista EOS due to nature of development lab setup. In future we&#8217;ll extend coverage for several major vendors.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p>At our <a href=\"https:\/\/bit.ly\/2WStZhj\" target=\"_blank\" rel=\"noreferrer noopener\">Zero-To-Hero Network Automation Training<\/a> you will learn how to build and automate your lab setup.<\/p><\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">Coverage.py Usage<\/h2>\n\n\n\n<p><a rel=\"noreferrer noopener\" href=\"https:\/\/coverage.readthedocs.io\/en\/coverage-5.5\/index.html\" target=\"_blank\"><strong>Coverage.py<\/strong> is a Python package<\/a> created to calculate the percentage of software module with unit tests. As such, it is being using together with some unit test tool (e.g., <strong>pytest<\/strong>). Once being used, it can show you the effectiveness of your unit tests and give you clear guidelines, which parts of code were and were not tested.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step #1. Basic Usage<\/h3>\n\n\n\n<p>It is important to emphasise, that coverage.py doesn&#8217;t replace unit testing package <strong>pytest<\/strong> neither do the unit tests itself. Therefore, it is important to create the unit tests with <strong>pytest<\/strong> first.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p>Read the details how to created tests for <a rel=\"noreferrer noopener\" href=\"https:\/\/bit.ly\/3rNlGBQ\" target=\"_blank\">pytest in one of the previous blogposts<\/a>.<\/p><\/blockquote>\n\n\n\n<p>After unit tests are created, it is very simple to start using the <strong>coverage.py<\/strong> package. First of all, you need to install it from PyPI:<\/p>\n\n\n\n<pre class=\"wp-block-code\">\n\n<div class=\"codecolorer-container text default\" style=\"overflow:auto;white-space:nowrap;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/><\/div><\/td><td><div class=\"text codecolorer\">$ pip install coverage<\/div><\/td><\/tr><\/tbody><\/table><\/div>\n\n<\/pre>\n\n\n\n<p>Once installed, you can execute your unit tests leveraging <strong>pytest<\/strong> via this module as simple as:<\/p>\n\n\n\n<pre class=\"wp-block-code\">\n\n<div class=\"codecolorer-container text default\" style=\"overflow:auto;white-space:nowrap;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/>2<br \/>3<br \/>4<br \/>5<br \/>6<br \/>7<br \/>8<br \/>9<br \/>10<br \/><\/div><\/td><td><div class=\"text codecolorer\">$ coverage run -m pytest<br \/>\n=================================== test session starts ====================================<br \/>\nplatform linux -- Python 3.9.2, pytest-6.2.4, py-1.10.0, pluggy-0.13.1<br \/>\nrootdir: \/home\/aaa\/Documents\/D\/Dev\/pygnmi<br \/>\ncollected 9 items &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br \/>\n<br \/>\ntests\/test_connect_methods.py .. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &amp;#91; 22%]<br \/>\ntests\/test_functionality.py ....... &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&amp;#91;100%]<br \/>\n<br \/>\n==================================== 9 passed in 3.77s =====================================<\/div><\/td><\/tr><\/tbody><\/table><\/div>\n\n<\/pre>\n\n\n\n<p>As you see, we specify that we us unit test based in <strong>pytest<\/strong> inside <strong>coverage<\/strong>. The tests are conducted and all of them are passed, which is good. However, at this stage you don&#8217;t see any signs of percentage of the tests coverage&#8230;<\/p>\n\n\n\n<p>To see that values, or to be precise &#8211; values, we need to generate report. During the previous execution of <strong>coverage run -m pytest<\/strong>, there was a new file <strong>.coverage <\/strong>created in your folder. This is a binary file having a lot of information, which is an input for report generation. To run the report, use the following command:<\/p>\n\n\n\n<pre class=\"wp-block-code\">\n\n<div class=\"codecolorer-container text default\" style=\"overflow:auto;white-space:nowrap;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/>2<br \/>3<br \/>4<br \/>5<br \/>6<br \/>7<br \/>8<br \/>9<br \/>10<br \/>11<br \/>12<br \/>13<br \/>14<br \/>15<br \/>16<br \/>17<br \/>18<br \/>19<br \/>20<br \/>21<br \/>22<br \/>23<br \/>24<br \/>25<br \/>26<br \/>27<br \/>28<br \/>29<br \/>30<br \/>31<br \/>32<br \/>33<br \/><\/div><\/td><td><div class=\"text codecolorer\">$ coverage report -m <br \/>\nName &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Stmts &nbsp; Miss &nbsp;Cover &nbsp; Missing<br \/>\n-------------------------------------------------------------------------------------------------------------------------<br \/>\npygnmi\/__init__.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;1 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\npygnmi\/client.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;589 &nbsp; &nbsp;288 &nbsp; &nbsp;51% &nbsp; 54-55, 57, 75-77, 81-88, 95-97, 109-111, 132-134, 139-141, 159, 161, 177-186, 225-232, 237-244, 248-249, 253-255, 260-261, 267-269, 274-276, 317-345, 353-362, 394-395, 402-408, 419-434, 445-460, 466-468, 473-475, 502, 512, 521-532, 538-547, 552, 557, 562, 566, 571-576, 585, 594, 598, 603, 617, 624, 628, 633, 639, 643, 648, 651, 658, 672, 675-682, 685-698, 704-706, 711-714, 717-720, 723-726, 766-782, 791-800, 803, 812-816, 819-828, 831, 834, 840, 852-855, 861, 869-870, 883-884, 887-891, 907, 919-920, 930-932, 942-955, 977, 981, 984, 989-1011, 1017-1025<br \/>\npygnmi\/path_generator.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 36 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\npygnmi\/spec\/gnmi_ext_pb2.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;44 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\npygnmi\/spec\/gnmi_pb2.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 251 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\npygnmi\/spec\/gnmi_pb2_grpc.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 29 &nbsp; &nbsp; 15 &nbsp; &nbsp;48% &nbsp; 52-54, 63-65, 73-75, 84-86, 90-114<br \/>\ntests\/__init__.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 0 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\ntests\/messages.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 4 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\ntests\/test_address_types.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 3 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\ntests\/test_connect_methods.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;31 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\ntests\/test_functionality.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 190 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\nvenv\/lib\/python3.9\/site-packages\/_pytest\/__init__.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;5 &nbsp; &nbsp; &nbsp;2 &nbsp; &nbsp;60% &nbsp; 5-8<br \/>\nvenv\/lib\/python3.9\/site-packages\/_pytest\/_argcomplete.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 37 &nbsp; &nbsp; 23 &nbsp; &nbsp;38% &nbsp; 77, 81-98, 102-109<br \/>\nvenv\/lib\/python3.9\/site-packages\/_pytest\/_code\/__init__.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 10 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\nvenv\/lib\/python3.9\/site-packages\/_pytest\/_code\/code.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;699 &nbsp; &nbsp;450 &nbsp; &nbsp;36% &nbsp; 48-51, 67, 78, 85, 90, 92-95, 100-101, 106, 115-120, 130, 134, 138, 142, 146, 151-153, 162-164, 168, 176-182, 195-197, 201, 204-205, 209, 213, 216, 221-223, 228, 233, 236, 242-260, 273-289, 292-302, 312, 324-335, 353-367, 371, 375, 378-381, 395, 399-403, 408-428, 467-475, 492-497, 502, 506-507, 512-515, 520-523, 528-531, 536-539, 544-546, 550, 553-555, 567-573, 582, 585-588, 629-648, 656-662, 684-695, 698-701, 704-709, 719-737, 745-754, 757-780, 787-815, 818-825, 828-848, 865-890, 895-939, 947-950, 953, 956, 967, 970, 973-975, 987-991, 994-999, 1008-1009, 1022-1036, 1041-1043, 1052, 1077-1105, 1108-1127, 1130, 1144-1149, 1157-1158, 1166-1181, 1196, 1200-1213, 1242-1259<br \/>\nvenv\/lib\/python3.9\/site-packages\/_pytest\/_code\/source.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;142 &nbsp; &nbsp;104 &nbsp; &nbsp;27% &nbsp; 24-38, 41-43, 50, 54, 57-64, 67, 70, 74-81, 86-88, 93-94, 99-102, 106-108, 111, 120-126, 133-139, 143, 149-165, 174-212<br \/>\nvenv\/lib\/python3.9\/site-packages\/_pytest\/_io\/__init__.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;3 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\n!<br \/>\n! A LOT OF OUTPUT IS TRUNCATED FOR BREVITY<br \/>\n!<br \/>\nvenv\/lib\/python3.9\/site-packages\/py\/_vendored_packages\/__init__.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\nvenv\/lib\/python3.9\/site-packages\/py\/_vendored_packages\/apipkg\/__init__.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 152 &nbsp; &nbsp; 55 &nbsp; &nbsp;64% &nbsp; 22, 30-36, 65-67, 74, 86-90, 128-135, 142-143, 149, 156-157, 166-175, 182-187, 192-195, 198-201, 204, 207<br \/>\nvenv\/lib\/python3.9\/site-packages\/py\/_vendored_packages\/apipkg\/version.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;1 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\nvenv\/lib\/python3.9\/site-packages\/py\/_version.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 1 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\nvenv\/lib\/python3.9\/site-packages\/pytest\/__init__.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;60 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\nvenv\/lib\/python3.9\/site-packages\/pytest\/__main__.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 3 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\nvenv\/lib\/python3.9\/site-packages\/pytest\/collect.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 21 &nbsp; &nbsp; &nbsp;5 &nbsp; &nbsp;76% &nbsp; 30, 33-36<br \/>\nvenv\/lib\/python3.9\/site-packages\/six.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 504 &nbsp; &nbsp;264 &nbsp; &nbsp;48% &nbsp; 49-72, 77, 103-104, 117, 123-126, 136-138, 150, 159-162, 165-166, 190-192, 196, 200-203, 206-217, 226, 232-233, 237, 240, 324, 504, 512, 517-523, 535-541, 546-548, 554-556, 561, 566, 570-584, 599, 602, 608, 616-632, 644, 647, 661-663, 669-689, 695, 699, 703, 707, 714-737, 753-754, 759-811, 813-820, 830-850, 869-871, 882-895, 909-913, 928-936, 950-955, 966-973, 994-995<br \/>\n-------------------------------------------------------------------------------------------------------------------------<br \/>\nTOTAL &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 33079 &nbsp;18252 &nbsp; &nbsp;45%<\/div><\/td><\/tr><\/tbody><\/table><\/div>\n\n<\/pre>\n\n\n\n<p>The output is relatively long and overall:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>You can see the coverage of used code lines per each Python file<\/li><li>You can see the average code coverage.<\/li><\/ul>\n\n\n\n<p>The immediate though you may have, there are so many unrelated Python files being checked. How can we improve the the signal\/noise ratio?<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step #2. Limiting the Reported Scope<\/h3>\n\n\n\n<p><strong>Coverage.py<\/strong> has opportunity to get extra attributes, when you run unit tests. For example, you can specify only the interested files\/folders, which shall be tracked in terms of coverage. Or, other way around, you can exclude certain files. However, first of all we need to delete the results of previous coverage.py run:<\/p>\n\n\n\n<pre class=\"wp-block-code\">\n\n<div class=\"codecolorer-container text default\" style=\"overflow:auto;white-space:nowrap;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/><\/div><\/td><td><div class=\"text codecolorer\">$ coverage erase<\/div><\/td><\/tr><\/tbody><\/table><\/div>\n\n<\/pre>\n\n\n\n<p>Once you are ready for the new run, let&#8217;s add some more arguments. In our case, we would use key <strong>&#8211;include<\/strong> to provide the names of the directories and\/or specific files and <strong>&#8211;omit<\/strong> key to exclude files\/folder correspondingly:<\/p>\n\n\n\n<pre class=\"wp-block-code\">\n\n<div class=\"codecolorer-container text default\" style=\"overflow:auto;white-space:nowrap;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/>2<br \/>3<br \/>4<br \/>5<br \/>6<br \/>7<br \/>8<br \/>9<br \/>10<br \/><\/div><\/td><td><div class=\"text codecolorer\">$ coverage run --include=pygnmi\/* --omit=pygnmi\/spec\/* -m pytest<br \/>\n=================================== test session starts ====================================<br \/>\nplatform linux -- Python 3.9.2, pytest-6.2.4, py-1.10.0, pluggy-0.13.1<br \/>\nrootdir: \/home\/aaa\/Documents\/D\/Dev\/pygnmi<br \/>\ncollected 9 items &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br \/>\n<br \/>\ntests\/test_connect_methods.py .. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &amp;#91; 22%]<br \/>\ntests\/test_functionality.py ....... &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&amp;#91;100%]<br \/>\n<br \/>\n==================================== 9 passed in 3.53s =====================================<\/div><\/td><\/tr><\/tbody><\/table><\/div>\n\n<\/pre>\n\n\n\n<p>What we have done is:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>we added <strong>&#8211;include<\/strong> key, which points towards <em>pygmni<\/em> folder, which contains the <strong>pygnmi<\/strong> module<\/li><li>we added <strong>&#8211;omit<\/strong> key, which excludes <em>spec<\/em> subdirectory from <em>pygnmi<\/em> directory. The reason for that is that spec contains original <a href=\"https:\/\/github.com\/openconfig\/gnmi\/tree\/master\/proto\/gnmi\" target=\"_blank\" rel=\"noreferrer noopener\">OpenConfig GNMI specification<\/a> modules and we are not test those original modules.<\/li><\/ul>\n\n\n\n<p>Let&#8217;s validate the new report:<\/p>\n\n\n\n<pre class=\"wp-block-code\">\n\n<div class=\"codecolorer-container text default\" style=\"overflow:auto;white-space:nowrap;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/>2<br \/>3<br \/>4<br \/>5<br \/>6<br \/>7<br \/>8<br \/><\/div><\/td><td><div class=\"text codecolorer\">$ coverage report -m<br \/>\nName &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Stmts &nbsp; Miss &nbsp;Cover &nbsp; Missing<br \/>\n--------------------------------------------------------<br \/>\npygnmi\/__init__.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 1 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\npygnmi\/client.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 589 &nbsp; &nbsp;288 &nbsp; &nbsp;51% &nbsp; 54-55, 57, 75-77, 81-88, 95-97, 109-111, 132-134, 139-141, 159, 161, 177-186, 225-232, 237-244, 248-249, 253-255, 260-261, 267-269, 274-276, 317-345, 353-362, 394-395, 402-408, 419-434, 445-460, 466-468, 473-475, 502, 512, 521-532, 538-547, 552, 557, 562, 566, 571-576, 585, 594, 598, 603, 617, 624, 628, 633, 639, 643, 648, 651, 658, 672, 675-682, 685-698, 704-706, 711-714, 717-720, 723-726, 766-782, 791-800, 803, 812-816, 819-828, 831, 834, 840, 852-855, 861, 869-870, 883-884, 887-891, 907, 919-920, 930-932, 942-955, 977, 981, 984, 989-1011, 1017-1025<br \/>\npygnmi\/path_generator.py &nbsp; &nbsp; &nbsp;36 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\n--------------------------------------------------------<br \/>\nTOTAL &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;626 &nbsp; &nbsp;288 &nbsp; &nbsp;54%<\/div><\/td><\/tr><\/tbody><\/table><\/div>\n\n<\/pre>\n\n\n\n<p>Now we see only Python modules we have created ourselves, which are the heart of <strong>pygnmi<\/strong>. Some of them have 100% <strong>coverage<\/strong> by our unit tests with <strong>pytest<\/strong>, whilst the main client is just 51%. How can we improve that number?<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step #3. Increasing Coverage<\/h3>\n\n\n\n<p>Let&#8217;s carefully analyse the output above. You may see the column <strong>Missing<\/strong>, which contains lines of code, which were not executed. The more lines you have, which are not executed, the less your coverage is. How can you increase amount of code executed? The answer is straightforward:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Either you add more logic to your existing unit tests<\/li><li>Or you create more new tests to cover those missing lines<\/li><\/ul>\n\n\n\n<p>We will show you here the second approach. Let&#8217;s take a look on missing lines 54-55 and 57 from <strong>pygnmi\/client.py<\/strong>:<\/p>\n\n\n\n<pre class=\"wp-block-code\">\n\n<div class=\"codecolorer-container text default\" style=\"overflow:auto;white-space:nowrap;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/>2<br \/>3<br \/>4<br \/>5<br \/><\/div><\/td><td><div class=\"text codecolorer\">53 &nbsp; &nbsp; &nbsp; &nbsp;if re.match('unix:.*', target&amp;#91;0]):<br \/>\n54 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;self.__target = target<br \/>\n55 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;self.__target_path = target&amp;#91;0]<br \/>\n56 &nbsp; &nbsp; &nbsp; &nbsp;elif re.match('.*:.*', target&amp;#91;0]):<br \/>\n57 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;self.__target = (f'&amp;#91;{target&amp;#91;0]}]', target&amp;#91;1])<\/div><\/td><\/tr><\/tbody><\/table><\/div>\n\n<\/pre>\n\n\n\n<p>Those ones are related to various type of how the name connectivity date (IPv4\/IPv6 address or FQDN and port). They are not being executed, as in our case for testing we provide the connectivity data as FQDN and parts for UNIX-socket or IPv6 aren&#8217;t used. Let&#8217;s fix IPv6 part.<\/p>\n\n\n\n<p>In our <em>.env<\/em> file, which stores connectivity data we add a new variables. Current state:<\/p>\n\n\n\n<pre class=\"wp-block-code\">\n\n<div class=\"codecolorer-container text default\" style=\"overflow:auto;white-space:nowrap;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/>2<br \/><\/div><\/td><td><div class=\"text codecolorer\">$ cat .env | grep PYGNMI_HOST<br \/>\nPYGNMI_HOST=&quot;EOS1&quot;<\/div><\/td><\/tr><\/tbody><\/table><\/div>\n\n<\/pre>\n\n\n\n<p>Modified state of <em>.env<\/em> file:<\/p>\n\n\n\n<pre class=\"wp-block-code\">\n\n<div class=\"codecolorer-container text default\" style=\"overflow:auto;white-space:nowrap;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/>2<br \/>3<br \/>4<br \/><\/div><\/td><td><div class=\"text codecolorer\">$ cat .env | grep PYGNMI_HOST<br \/>\nPYGNMI_HOST=&quot;EOS425&quot;<br \/>\nPYGNMI_HOST_2=&quot;169.254.255.10&quot;<br \/>\nPYGNMI_HOST_3=&quot;fc00:169:254:255::A&quot;<\/div><\/td><\/tr><\/tbody><\/table><\/div>\n\n<\/pre>\n\n\n\n<p>So we have created 2 new variables, and we will create a new file with unit tests to utilise those variables:<\/p>\n\n\n\n<pre class=\"wp-block-code\">\n\n<div class=\"codecolorer-container text default\" style=\"overflow:auto;white-space:nowrap;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/>2<br \/>3<br \/>4<br \/>5<br \/>6<br \/>7<br \/>8<br \/>9<br \/>10<br \/>11<br \/>12<br \/>13<br \/>14<br \/>15<br \/>16<br \/>17<br \/>18<br \/>19<br \/>20<br \/>21<br \/>22<br \/>23<br \/>24<br \/>25<br \/>26<br \/>27<br \/>28<br \/>29<br \/>30<br \/>31<br \/>32<br \/>33<br \/>34<br \/>35<br \/>36<br \/>37<br \/>38<br \/>39<br \/>40<br \/>41<br \/>42<br \/>43<br \/>44<br \/>45<br \/>46<br \/>47<br \/>48<br \/>49<br \/>50<br \/>51<br \/>52<br \/>53<br \/>54<br \/>55<br \/>56<br \/>57<br \/>58<br \/>59<br \/>60<br \/>61<br \/>62<br \/>63<br \/>64<br \/>65<br \/>66<br \/>67<br \/>68<br \/><\/div><\/td><td><div class=\"text codecolorer\">$ cat tests\/test_address_types.py<br \/>\n#!\/usr\/bin\/env python<br \/>\n<br \/>\n# Modules<br \/>\nfrom pygnmi.client import gNMIclient<br \/>\nfrom dotenv import load_dotenv<br \/>\nimport os<br \/>\n<br \/>\n<br \/>\n# User-defined functions (Tests)<br \/>\ndef test_fqdn_address():<br \/>\n&nbsp; &nbsp; load_dotenv()<br \/>\n&nbsp; &nbsp; username_str = os.getenv(&quot;PYGNMI_USER&quot;)<br \/>\n&nbsp; &nbsp; password_str = os.getenv(&quot;PYGNMI_PASS&quot;)<br \/>\n&nbsp; &nbsp; hostname_str = os.getenv(&quot;PYGNMI_HOST&quot;)<br \/>\n&nbsp; &nbsp; port_str = os.getenv(&quot;PYGNMI_PORT&quot;)<br \/>\n&nbsp; &nbsp; path_cert_str = os.getenv(&quot;PYGNMI_CERT&quot;)<br \/>\n<br \/>\n&nbsp; &nbsp; gc = gNMIclient(target=(hostname_str, port_str), username=username_str, password=password_str, path_cert=path_cert_str)<br \/>\n&nbsp; &nbsp; gc.connect()<br \/>\n<br \/>\n&nbsp; &nbsp; result = gc.capabilities()<br \/>\n<br \/>\n&nbsp; &nbsp; gc.close()<br \/>\n<br \/>\n&nbsp; &nbsp; assert &quot;supported_models&quot; in result<br \/>\n&nbsp; &nbsp; assert &quot;supported_encodings&quot; in result<br \/>\n&nbsp; &nbsp; assert &quot;gnmi_version&quot; in result<br \/>\n<br \/>\n<br \/>\ndef test_ipv4_address_override():<br \/>\n&nbsp; &nbsp; load_dotenv()<br \/>\n&nbsp; &nbsp; username_str = os.getenv(&quot;PYGNMI_USER&quot;)<br \/>\n&nbsp; &nbsp; password_str = os.getenv(&quot;PYGNMI_PASS&quot;)<br \/>\n&nbsp; &nbsp; hostname_str = os.getenv(&quot;PYGNMI_HOST_2&quot;)<br \/>\n&nbsp; &nbsp; port_str = os.getenv(&quot;PYGNMI_PORT&quot;)<br \/>\n&nbsp; &nbsp; path_cert_str = os.getenv(&quot;PYGNMI_CERT&quot;)<br \/>\n<br \/>\n&nbsp; &nbsp; gc = gNMIclient(target=(hostname_str, port_str), username=username_str, password=password_str, path_cert=path_cert_str, override=&quot;EOS425&quot;)<br \/>\n&nbsp; &nbsp; gc.connect()<br \/>\n<br \/>\n&nbsp; &nbsp; result = gc.capabilities()<br \/>\n<br \/>\n&nbsp; &nbsp; gc.close()<br \/>\n<br \/>\n&nbsp; &nbsp; assert &quot;supported_models&quot; in result<br \/>\n&nbsp; &nbsp; assert &quot;supported_encodings&quot; in result<br \/>\n&nbsp; &nbsp; assert &quot;gnmi_version&quot; in result<br \/>\n<br \/>\n<br \/>\ndef test_ipv6_address_override():<br \/>\n&nbsp; &nbsp; load_dotenv()<br \/>\n&nbsp; &nbsp; username_str = os.getenv(&quot;PYGNMI_USER&quot;)<br \/>\n&nbsp; &nbsp; password_str = os.getenv(&quot;PYGNMI_PASS&quot;)<br \/>\n&nbsp; &nbsp; hostname_str = os.getenv(&quot;PYGNMI_HOST_3&quot;)<br \/>\n&nbsp; &nbsp; port_str = os.getenv(&quot;PYGNMI_PORT&quot;)<br \/>\n&nbsp; &nbsp; path_cert_str = os.getenv(&quot;PYGNMI_CERT&quot;)<br \/>\n<br \/>\n&nbsp; &nbsp; gc = gNMIclient(target=(hostname_str, port_str), username=username_str, password=password_str, path_cert=path_cert_str, override=&quot;EOS425&quot;)<br \/>\n&nbsp; &nbsp; gc.connect()<br \/>\n<br \/>\n&nbsp; &nbsp; result = gc.capabilities()<br \/>\n<br \/>\n&nbsp; &nbsp; gc.close()<br \/>\n<br \/>\n&nbsp; &nbsp; assert &quot;supported_models&quot; in result<br \/>\n&nbsp; &nbsp; assert &quot;supported_encodings&quot; in result<br \/>\n&nbsp; &nbsp; assert &quot;gnmi_version&quot; in result<\/div><\/td><\/tr><\/tbody><\/table><\/div>\n\n<\/pre>\n\n\n\n<p>As you see, tests are fairly identical:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>we make connection to device over gNMI<\/li><li>we do capabilities() call<\/li><li>we terminate connection<\/li><li>we validate if the needed information is provided in the output<\/li><\/ul>\n\n\n\n<p>In all three tests we validate the capabilities call, as it is the most simple one. What is different between three those functions is that we are going to use:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>FQDN<\/li><li>IPv4 address<\/li><li>IPv6 address<\/li><\/ul>\n\n\n\n<p>to connect to devices.<\/p>\n\n\n\n<p>Let&#8217;s erase the previous results, re-run the unit tests with <strong>pytest<\/strong> and <strong>coverage.py<\/strong> and check the results:<\/p>\n\n\n\n<pre class=\"wp-block-code\">\n\n<div class=\"codecolorer-container text default\" style=\"overflow:auto;white-space:nowrap;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/>2<br \/>3<br \/>4<br \/>5<br \/>6<br \/>7<br \/>8<br \/>9<br \/>10<br \/>11<br \/><\/div><\/td><td><div class=\"text codecolorer\">$ coverage run --include=pygnmi\/* --omit=pygnmi\/spec\/* -m pytest<br \/>\n=================================== test session starts ====================================<br \/>\nplatform linux -- Python 3.9.2, pytest-6.2.4, py-1.10.0, pluggy-0.13.1<br \/>\nrootdir: \/home\/aaa\/Documents\/D\/Dev\/pygnmi<br \/>\ncollected 12 items &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <br \/>\n<br \/>\ntests\/test_address_types.py ... &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&amp;#91; 25%]<br \/>\ntests\/test_connect_methods.py .. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &amp;#91; 41%]<br \/>\ntests\/test_functionality.py ....... &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&amp;#91;100%]<br \/>\n<br \/>\n==================================== 12 passed in 9.46s ====================================<\/div><\/td><\/tr><\/tbody><\/table><\/div>\n\n<\/pre>\n\n\n\n<p>You can see that we have 3 more tests now. What about the coverage?<\/p>\n\n\n\n<pre class=\"wp-block-code\">\n\n<div class=\"codecolorer-container text default\" style=\"overflow:auto;white-space:nowrap;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/>2<br \/>3<br \/>4<br \/>5<br \/>6<br \/>7<br \/>8<br \/><\/div><\/td><td><div class=\"text codecolorer\">$ coverage report -m<br \/>\nName &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Stmts &nbsp; Miss &nbsp;Cover &nbsp; Missing<br \/>\n--------------------------------------------------------<br \/>\npygnmi\/__init__.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 1 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\npygnmi\/client.py &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 589 &nbsp; &nbsp;287 &nbsp; &nbsp;51% &nbsp; 54-55, 75-77, 81-88, 95-97, 109-111, 132-134, 139-141, 159, 161, 177-186, 225-232, 237-244, 248-249, 253-255, 260-261, 267-269, 274-276, 317-345, 353-362, 394-395, 402-408, 419-434, 445-460, 466-468, 473-475, 502, 512, 521-532, 538-547, 552, 557, 562, 566, 571-576, 585, 594, 598, 603, 617, 624, 628, 633, 639, 643, 648, 651, 658, 672, 675-682, 685-698, 704-706, 711-714, 717-720, 723-726, 766-782, 791-800, 803, 812-816, 819-828, 831, 834, 840, 852-855, 861, 869-870, 883-884, 887-891, 907, 919-920, 930-932, 942-955, 977, 981, 984, 989-1011, 1017-1025<br \/>\npygnmi\/path_generator.py &nbsp; &nbsp; &nbsp;36 &nbsp; &nbsp; &nbsp;0 &nbsp; 100%<br \/>\n--------------------------------------------------------<br \/>\nTOTAL &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;626 &nbsp; &nbsp;287 &nbsp; &nbsp;54%<\/div><\/td><\/tr><\/tbody><\/table><\/div>\n\n<\/pre>\n\n\n\n<p>The line 57 now is removed from Missing column, meaning that this part of code was executed. Thinking about what you are willing to cover, you shall be creating new unit tests.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step #4. Changing the Output Format<\/h3>\n\n\n\n<p>The last point we&#8217;ll tackle in this blogpost is the output format. The one you have seen above us good for humans, but it is not necessary suitable if you want to build automated pipelines. In the latter case, the JSON output is more preferable. Actually, <strong>coverage.py<\/strong> can give you that output:<\/p>\n\n\n\n<pre class=\"wp-block-code\">\n\n<div class=\"codecolorer-container text default\" style=\"overflow:auto;white-space:nowrap;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/><\/div><\/td><td><div class=\"text codecolorer\">$ coverage json<\/div><\/td><\/tr><\/tbody><\/table><\/div>\n\n<\/pre>\n\n\n\n<p>This command generates the file called <em>coverage.json<\/em>. This file contains more detailed representation of the unit test report:<\/p>\n\n\n\n<pre class=\"wp-block-code\">\n\n<div class=\"codecolorer-container text default\" style=\"overflow:auto;white-space:nowrap;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/>2<br \/>3<br \/>4<br \/>5<br \/>6<br \/>7<br \/>8<br \/>9<br \/>10<br \/>11<br \/>12<br \/>13<br \/>14<br \/>15<br \/>16<br \/>17<br \/>18<br \/>19<br \/>20<br \/>21<br \/>22<br \/>23<br \/>24<br \/>25<br \/>26<br \/>27<br \/>28<br \/>29<br \/>30<br \/>31<br \/>32<br \/>33<br \/>34<br \/>35<br \/>36<br \/>37<br \/>38<br \/>39<br \/>40<br \/>41<br \/>42<br \/>43<br \/>44<br \/>45<br \/>46<br \/>47<br \/>48<br \/>49<br \/>50<br \/>51<br \/>52<br \/>53<br \/>54<br \/><\/div><\/td><td><div class=\"text codecolorer\">$ cat coverage.json<br \/>\n{<br \/>\n&nbsp; &nbsp; &quot;meta&quot;: {<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &quot;version&quot;: &quot;5.5&quot;, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &quot;timestamp&quot;: &quot;2021-09-03T20:52:34.302178&quot;, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &quot;branch_coverage&quot;: false, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &quot;show_contexts&quot;: false<br \/>\n&nbsp; &nbsp; }, <br \/>\n&nbsp; &nbsp; &quot;files&quot;: {<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &quot;pygnmi\/__init__.py&quot;: {<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;executed_lines&quot;: &amp;#91;3], <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;summary&quot;: {<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;covered_lines&quot;: 1, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;num_statements&quot;: 1, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;percent_covered&quot;: 100.0, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;missing_lines&quot;: 0, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;excluded_lines&quot;: 0<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;missing_lines&quot;: &amp;#91;], <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;excluded_lines&quot;: &amp;#91;]<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; }, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &quot;pygnmi\/client.py&quot;: {<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;executed_lines&quot;: &amp;#91;4, 5, 6, 9, 10, 11, 12, 13, 14, 15, 18, 19, 20, 23, 27, 28, 31, 32, 35, 42, 43, 44, 45, 46, 47, 48, 49, 50, 52, 53, 56, 57, 59, 62, 66, 69, 74, 80, 90, 91, 92, 93, 100, 101, 102, 103, 104, 105, 106, 107, 113, 115, 116, 118, 121, 126, 128, 129, 131, 136, 138, 143, 144, 146, 147, 149, 150, 152, 153, 155, 156, 157, 158, 160, 162, 163, 165, 167, 169, 170, 172, 174, 175, 189, 216, 218, 219, 220, 222, 223, 224, 234, 235, 236, 246, 247, 251, 257, 258, 259, 263, 264, 266, 271, 273, 278, 279, 281, 282, 284, 285, 287, 289, 290, 292, 293, 295, 296, 297, 298, 299, 300, 302, 303, 304, 306, 308, 311, 313, 314, 315, 347, 349, 351, 365, 388, 389, 390, 391, 393, 397, 398, 399, 400, 410, 411, 412, 413, 414, 415, 417, 418, 436, 437, 438, 439, 440, 441, 443, 444, 462, 463, 465, 470, 472, 477, 478, 480, 481, 483, 484, 486, 487, 488, 489, 490, 491, 493, 494, 495, 497, 499, 504, 505, 506, 507, 508, 509, 510, 514, 516, 518, 534, 549, 551, 553, 556, 559, 560, 565, 568, 569, 570, 579, 580, 582, 583, 588, 589, 591, 592, 597, 600, 601, 606, 607, 613, 614, 616, 620, 621, 623, 627, 630, 632, 634, 637, 638, 641, 645, 646, 650, 653, 655, 656, 660, 663, 665, 669, 671, 674, 684, 700, 701, 703, 708, 710, 716, 722, 728, 734, 736, 737, 740, 741, 743, 744, 752, 784, 802, 805, 818, 830, 833, 836, 842, 846, 857, 863, 872, 873, 882, 886, 893, 894, 906, 909, 910, 918, 924, 929, 934, 935, 936, 937, 939, 941, 957, 958, 960, 961, 962, 963, 964, 965, 967, 969, 970, 972, 974, 979, 980, 983, 986, 987, 1013, 1015], <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;summary&quot;: {<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;covered_lines&quot;: 302, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;num_statements&quot;: 589, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;percent_covered&quot;: 51.27334465195246, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;missing_lines&quot;: 287, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;excluded_lines&quot;: 0<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;missing_lines&quot;: &amp;#91;54, 55, 75, 76, 77, 81, 82, 83, 84, 85, 86, 87, 88, 95, 96, 97, 109, 110, 111, 132, 133, 134, 139, 140, 141, 159, 161, 177, 178, 179, 181, 183, 184, 186, 225, 226, 227, 228, 229, 230, 232, 237, 238, 239, 240, 241, 242, 244, 248, 249, 253, 254, 255, 260, 261, 267, 268, 269, 274, 275, 276, 317, 318, 320, 321, 323, 324, 326, 327, 329, 330, 332, 333, 335, 336, 338, 339, 341, 342, 344, 345, 353, 354, 355, 357, 359, 360, 362, 394, 395, 402, 403, 404, 407, 408, 419, 420, 421, 422, 423, 424, 425, 426, 429, 430, 433, 434, 445, 446, 447, 448, 449, 450, 451, 452, 455, 456, 459, 460, 466, 467, 468, 473, 474, 475, 502, 512, 521, 522, 524, 525, 526, 528, 530, 531, 532, 538, 539, 540, 541, 543, 544, 545, 546, 547, 552, 557, 562, 566, 571, 572, 573, 574, 576, 585, 594, 598, 603, 617, 624, 628, 633, 639, 643, 648, 651, 658, 672, 675, 676, 677, 679, 682, 685, 686, 687, 688, 689, 690, 693, 695, 698, 704, 705, 706, 711, 712, 713, 714, 717, 718, 719, 720, 723, 724, 725, 726, 766, 771, 774, 775, 776, 777, 778, 779, 781, 782, 791, 792, 794, 795, 796, 797, 798, 800, 803, 812, 813, 814, 815, 816, 819, 820, 821, 822, 823, 824, 826, 827, 828, 831, 834, 840, 852, 853, 854, 855, 861, 869, 870, 883, 884, 887, 888, 889, 891, 907, 919, 920, 930, 931, 932, 942, 943, 944, 945, 946, 948, 950, 951, 953, 955, 977, 981, 984, 989, 990, 992, 993, 995, 996, 998, 999, 1001, 1002, 1004, 1005, 1007, 1008, 1010, 1011, 1017, 1018, 1020, 1022, 1023, 1025], <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;excluded_lines&quot;: &amp;#91;]<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; }, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &quot;pygnmi\/path_generator.py&quot;: {<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;executed_lines&quot;: &amp;#91;5, 6, 9, 10, 11, 12, 13, 16, 17, 18, 20, 21, 22, 23, 24, 25, 27, 28, 30, 32, 33, 35, 36, 37, 38, 40, 41, 43, 44, 45, 46, 48, 49, 51, 54, 56], <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;summary&quot;: {<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;covered_lines&quot;: 36, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;num_statements&quot;: 36, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;percent_covered&quot;: 100.0, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;missing_lines&quot;: 0, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;excluded_lines&quot;: 0<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;missing_lines&quot;: &amp;#91;], <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;excluded_lines&quot;: &amp;#91;]<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; }<br \/>\n&nbsp; &nbsp; }, <br \/>\n&nbsp; &nbsp; &quot;totals&quot;: {<br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &quot;covered_lines&quot;: 339, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &quot;num_statements&quot;: 626, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &quot;percent_covered&quot;: 54.153354632587856, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &quot;missing_lines&quot;: 287, <br \/>\n&nbsp; &nbsp; &nbsp; &nbsp; &quot;excluded_lines&quot;: 0<br \/>\n&nbsp; &nbsp; }<br \/>\n}<\/div><\/td><\/tr><\/tbody><\/table><\/div>\n\n<\/pre>\n\n\n\n<p>As this is a JSON file, it is relatively easy to import that in Python, Ansible or any other tool.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p><a href=\"https:\/\/coverage.readthedocs.io\/en\/coverage-5.5\/index.html\" target=\"_blank\" rel=\"noreferrer noopener\">Refer to the documentation <\/a>for further output formats.<\/p><\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">If You Prefer Video<\/h2>\n\n\n\n<p>You can watch demos from pygnmi articles <a rel=\"noreferrer noopener\" href=\"https:\/\/www.youtube.com\/channel\/UCsW5YtOX3dGVB2d-XTqWhUQ\" target=\"_blank\">in our YouTube channel<\/a>. However, this specific blogpost doesn&#8217;t have video.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Examples in GitHub<\/h2>\n\n\n\n<p>You can find this and other examples <a href=\"https:\/\/github.com\/karneliuk-com\/pygnmi_pytets_openconfig\" target=\"_blank\" rel=\"noreferrer noopener\">in our GitHub repository<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lessons Learned<\/h2>\n\n\n\n<p>The biggest lessons learned from this blogpost is that coverage is a helpful metric, but metric. What does that mean? It means that with the help of coverage.py you can see what lines of code you have used with your unit tests and what not. You may achieve 100% code&#8217;s coverage, but it is up to you to define, whether this is something you are after. Probably 50-60% is enough provided you tested the most critical components. Don&#8217;t be driven by metrics. Drive the metrics yourself.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>Software development is an interesting yet complicated topic. There are tons of various aspects. Many of them we we cover at our <a rel=\"noreferrer noopener\" href=\"https:\/\/bit.ly\/2WStZhj\" target=\"_blank\">Network Automation Training<\/a>, and you can benefit from that education. <strong>Coverage.py<\/strong> and <strong>pytest<\/strong> together are very useful helpers to make sure your software is rock solid and fit for use in production systems. Take care and good bye.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Support us<\/h2>\n\n\n\n<style>\r\n        .wpedon-container .wpedon-select,\r\n        .wpedon-container .wpedon-input {\r\n            width: 171px;\r\n            min-width: 171px;\r\n            max-width: 171px;\r\n        }\r\n    <\/style><div class='wpedon-container wpedon-align-center'><label id='wpedon-1919-name-label'>Support new interop and automation articles at karneliuk.com<br \/><span class='price'><\/span>EUR<\/label><br \/>\r\n\t\t<script>\r\n\t\tjQuery(document).ready(function(){\r\n\t\t\tjQuery('#dd_f0242720706db95f09e393276fa1ffea').on('change', function() {\r\n\t\t\t  jQuery('#amount_f0242720706db95f09e393276fa1ffea').val(this.value);\r\n              jQuery('#price_f0242720706db95f09e393276fa1ffea').val(this.value);\r\n\t\t\t});\r\n\t\t});\r\n\t\t<\/script>\r\n\t\t<br \/><label style='font-size:11pt !important;'>I want to support with:<\/label><br \/><select class='wpedon-select' name='dd_f0242720706db95f09e393276fa1ffea' id='dd_f0242720706db95f09e393276fa1ffea'><option value='9.99'>9.99 EUR<\/option><option value='4.99'>4.99 EUR<\/option><option value='24.99'>24.99 EUR<\/option><option value='49.99'>49.99 EUR<\/option><option value='99.99'>99.99 EUR<\/option><option value='199.99'>199.99 EUR<\/option><\/select><br \/><br \/><form target='_blank' action='https:\/\/www.paypal.com\/cgi-bin\/webscr' method='post' class='wpedon-form'><input type='hidden' name='cmd' value='_donations' \/><input type='hidden' name='business' value='MZVY3WH2X7HPN' \/><input type='hidden' name='currency_code' value='EUR' \/><input type='hidden' name='notify_url' value='https:\/\/karneliuk.com\/wp-admin\/admin-post.php?action=add_wpedon_button_ipn'><input type='hidden' name='lc' value='en_US'><input type='hidden' name='bn' value='WPPlugin_SP'><input type='hidden' name='return' value='http:\/\/karneliuk.com\/thanks\/' \/><input type='hidden' name='cancel_return' value='' \/><input class='wpedon_paypalbuttonimage' type='image' src='https:\/\/www.paypal.com\/en_US\/i\/btn\/btn_donateCC_LG.gif' border='0' name='submit' alt='Make your payments with PayPal. It is free, secure, effective.' style='border: none;'><img alt='' border='0' style='border:none;display:none;' src='https:\/\/www.paypal.com\/en_US\/i\/scr\/pixel.gif' width='1' height='1'><input type='hidden' name='amount' id='amount_f0242720706db95f09e393276fa1ffea' value='9.99' \/><input type='hidden' name='price' id='price_f0242720706db95f09e393276fa1ffea' value='9.99' \/><input type='hidden' name='item_number' value='karneliuk.com-general' \/><input type='hidden' name='item_name' value='Support new interop and automation articles at karneliuk.com' \/><input type='hidden' name='name' value='Support new interop and automation articles at karneliuk.com' \/><input type='hidden' name='custom' value='1919'><input type='hidden' name='no_shipping' value='1'><input type='hidden' name='no_note' value='0'><input type='hidden' name='currency_code' value='EUR'><\/form><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">P.S.<\/h2>\n\n\n\n<p>If you have further questions or you need help with your networks, we are happy to assist you, <a href=\"http:\/\/karneliuk.com\/contact\/\">just send us a message<\/a>. Also don\u2019t forget to share the article on your social media, if you like it.<\/p>\n\n\n\n<p>BR,<\/p>\n\n\n\n<p>Anton Karneliuk <\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hello my friend, It is been a while since we posted our last blogpost, which was touching Infrastructure aspects of building Multi Server Cloud with ProxMox. After summer break we continue our blogging and developing activities at Karneliuk.com. Today we&#8217;ll show some backstage of the software development of the pygnmi.<\/p>\n","protected":false},"author":1,"featured_media":5373,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[16],"tags":[142,131,237,70,208,231,230,39],"class_list":{"0":"post-5320","1":"post","2":"type-post","3":"status-publish","4":"format-standard","5":"has-post-thumbnail","7":"category-network","8":"tag-arista","9":"tag-automation","10":"tag-coverage","11":"tag-nokia","12":"tag-pygnmi","13":"tag-pytest","14":"tag-testing","15":"tag-troubleshooting"},"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/karneliuk.com\/wp-json\/wp\/v2\/posts\/5320","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/karneliuk.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/karneliuk.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/karneliuk.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/karneliuk.com\/wp-json\/wp\/v2\/comments?post=5320"}],"version-history":[{"count":52,"href":"https:\/\/karneliuk.com\/wp-json\/wp\/v2\/posts\/5320\/revisions"}],"predecessor-version":[{"id":5372,"href":"https:\/\/karneliuk.com\/wp-json\/wp\/v2\/posts\/5320\/revisions\/5372"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/karneliuk.com\/wp-json\/wp\/v2\/media\/5373"}],"wp:attachment":[{"href":"https:\/\/karneliuk.com\/wp-json\/wp\/v2\/media?parent=5320"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/karneliuk.com\/wp-json\/wp\/v2\/categories?post=5320"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/karneliuk.com\/wp-json\/wp\/v2\/tags?post=5320"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}