{"id":55017,"date":"2022-07-03T23:55:54","date_gmt":"2022-07-03T18:25:54","guid":{"rendered":"https:\/\/www.tothenew.com\/blog\/?p=55017"},"modified":"2022-07-05T09:29:41","modified_gmt":"2022-07-05T03:59:41","slug":"flutter-test-coverage","status":"publish","type":"post","link":"https:\/\/www.tothenew.com\/blog\/flutter-test-coverage\/","title":{"rendered":"Flutter Test Coverage"},"content":{"rendered":"<p class=\"p3\">As a good practice we should always write unit tests while writing the code for a software. Next, it is important to measure that the test we write is enough for our code or not, as per our coverage criteria.<\/p>\n<p class=\"p3\">We leverage a few tools, discussed below, that check which lines of the codes are not covered in the tests.<\/p>\n<p><strong>Pre-requisite (basic tools) required to do test Coverage<\/strong>:<\/p>\n<ul>\n<li class=\"p3\">IDE &#8211; Android Studio or VS code<\/li>\n<li class=\"p3\">Framework &#8211; flutter<\/li>\n<li class=\"p3\">Lcove<\/li>\n<li class=\"p3\">Knowledge of writing tests<\/li>\n<\/ul>\n<p>Following is the command to run test in flutter:<\/p>\n<p class=\"p3\"><strong><em>flutter test<span class=\"Apple-converted-space\">\u00a0<\/span><\/em><\/strong><\/p>\n<p class=\"p3\">This command will run all test files under main \/test folder in root project.\u00a0If we want to generate coverage also then we have to run the following command:<span class=\"Apple-converted-space\">\u00a0<\/span><\/p>\n<p class=\"p3\"><strong><em>Flutter test \u2014coverage<\/em><\/strong><\/p>\n<p class=\"p3\">Test with coverage<span class=\"Apple-converted-space\"> w<\/span>ill create coverage folder in root project with a file named\u00a0<strong><em><span class=\"s1\">lcov.info<\/span><\/em><\/strong><em><span class=\"s1\">. <\/span><\/em>This file\u00a0has all the info related to test coverage of each file.<\/p>\n<p class=\"p3\">There is an issue in flutter test coverage, it wouldn&#8217;t report untested files in lcov. There is a work around to include all in lcove test coverage report. We have to write a script to import all dart files excluding generated files like *.g.files, freezed files etc. You can see the issue in detail here: <a href=\"https:\/\/github.com\/flutter\/flutter\/issues\/27997\"><span class=\"s1\">https:\/\/github.com\/flutter\/flutter\/issues\/27997<\/span><\/a><\/p>\n<p class=\"p3\">Going ahead, we will also create file <strong><em>import_file_coverage.sh<\/em><\/strong><\/p>\n<table style=\"border-collapse: collapse; width: 100%; border-style: dotted;\" border=\"1\">\n<tbody>\n<tr>\n<td style=\"width: 98.1317%;\">\n<p class=\"p4\"><em>file=test\/coverage_helper_test.dart<br \/>\n<\/em><em>printf &#8220;\/\/ Helper file to make coverage work for all dart files\\n&#8221; &gt; $file<br \/>\n<\/em><em>printf &#8220;\/\/ **************************************************************************\\n&#8221; &gt;&gt; $file<br \/>\n<\/em><em>printf &#8220;\/\/ Because of this: https:\/\/github.com\/flutter\/flutter\/issues\/27997#issue-410722816\\n&#8221; &gt;&gt; $file<br \/>\n<\/em><em>printf &#8220;\/\/ DO NOT EDIT THIS FILE USE: sh scripts\/import_files_coverage.sh YOUR_PACKAGE_NAME\\n&#8221; &gt;&gt; $file<br \/>\n<\/em><em>printf &#8220;\/\/ **************************************************************************\\n&#8221; &gt;&gt; $file<br \/>\n<\/em><em>printf &#8220;\\n&#8221; &gt;&gt; $file<br \/>\n<\/em><em>printf &#8220;\/\/ ignore_for_file: unused_import\\n&#8221; &gt;&gt; $file<br \/>\n<\/em><em>find lib \\<br \/>\n<\/em><em>&#8216;!&#8217; -path &#8216;*generated*\/*&#8217; \\<br \/>\n<\/em><em>&#8216;!&#8217; -iname &#8216;*.g.dart&#8217; \\<br \/>\n<\/em><em>&#8216;!&#8217; -iname &#8216;*.freezed.dart&#8217; \\<br \/>\n<\/em><em>&#8216;!&#8217; -iname &#8216;*.part.dart&#8217; \\<br \/>\n<\/em><em>&#8216;!&#8217; -iname &#8216;generated_plugin_registrant.dart&#8217; \\<br \/>\n<\/em><em>-iname &#8216;*.dart&#8217; \\<br \/>\n<\/em><em>| cut -c4- | awk -v package=&#8221;${1}&#8221; &#8216;{printf &#8220;import &#8216;\\&#8221;package:%s%s&#8217;\\&#8221;;\\n&#8221;, package, $1}&#8217; &gt;&gt; $file<br \/>\n<\/em><em>printf &#8220;\\nvoid main(){}&#8221; &gt;&gt; $file<\/em><\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p class=\"p3\">This file (<strong><em>import_file_coverage.sh<\/em><\/strong><em>)<\/em> needs a package name as parameter. We add the package name and run command as follows:<\/p>\n<p class=\"p3\"><strong><em><span class=\"s2\">sh import_file_coverage.sh your_package_name<\/span><\/em><\/strong><\/p>\n<p class=\"p7\">This will generate <span class=\"s3\"><strong>coverage_helper_test.dart<\/strong> <\/span>file under test folder and add all these files in <span class=\"s1\">lcov.info<\/span> to generate report against all these files.<\/p>\n<p class=\"p7\">Now our next task is to create readable test coverage report out of <span class=\"s1\">lcove.info<\/span> file. To do this we will take help of <strong>genhtml<\/strong> command which will generate readable html file that would open in your default browser:<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full wp-image-55014\" src=\"\/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-14-at-12.37.08-PM.png\" alt=\"\" width=\"3572\" height=\"824\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-14-at-12.37.08-PM.png 3572w, \/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-14-at-12.37.08-PM-300x69.png 300w, \/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-14-at-12.37.08-PM-1024x236.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-14-at-12.37.08-PM-768x177.png 768w, \/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-14-at-12.37.08-PM-1536x354.png 1536w, \/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-14-at-12.37.08-PM-2048x472.png 2048w, \/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-14-at-12.37.08-PM-624x144.png 624w\" sizes=\"(max-width: 3572px) 100vw, 3572px\" \/><\/p>\n<p class=\"p3\">Bravo, now we have line coverage of each folder and file!<span class=\"Apple-converted-space\">\u00a0<\/span><\/p>\n<p class=\"p3\">When we click on any of the file in the above image, it will take us to a detailed coverage of <em><strong>MyHomePage.dart<\/strong><\/em> file like below:<\/p>\n<p class=\"p2\"><img decoding=\"async\" loading=\"lazy\" class=\"alignnone wp-image-55015\" src=\"\/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-14-at-1.27.44-PM-1024x520.png\" alt=\"\" width=\"1108\" height=\"564\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-14-at-1.27.44-PM-1024x520.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-14-at-1.27.44-PM-300x152.png 300w, \/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-14-at-1.27.44-PM-768x390.png 768w, \/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-14-at-1.27.44-PM-1536x780.png 1536w, \/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-14-at-1.27.44-PM-2048x1041.png 2048w, \/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-14-at-1.27.44-PM-624x317.png 624w\" sizes=\"(max-width: 1108px) 100vw, 1108px\" \/><\/p>\n<p class=\"p3\">In the above files the blue lines means lines covered and red means non tested lines. As we can see in <strong>MyHomePage.dart<\/strong> file we have covered 93.3%, with two line remain untested. We can also cover these lines by writing tests for these lines.<\/p>\n<p class=\"p2\"><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full wp-image-55016\" src=\"\/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-16-at-2.55.17-PM.png\" alt=\"\" width=\"3566\" height=\"526\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-16-at-2.55.17-PM.png 3566w, \/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-16-at-2.55.17-PM-300x44.png 300w, \/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-16-at-2.55.17-PM-1024x151.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-16-at-2.55.17-PM-768x113.png 768w, \/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-16-at-2.55.17-PM-1536x227.png 1536w, \/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-16-at-2.55.17-PM-2048x302.png 2048w, \/blog\/wp-ttn-blog\/uploads\/2022\/06\/Screenshot-2022-05-16-at-2.55.17-PM-624x92.png 624w\" sizes=\"(max-width: 3566px) 100vw, 3566px\" \/><\/p>\n<p class=\"p3\">But there is one challenge here. In models we see there is only 70.8% coverage (top right in red colored cell). This is because it has also covered the generated files for which we cannot write test case. Thus, we will remove all <strong>*.g.dart<\/strong> files and other generated files from our generated lcove file. Here is the command to remove generated files:<\/p>\n<table style=\"border-collapse: collapse; width: 100%;\" border=\"1\">\n<tbody>\n<tr>\n<td style=\"width: 98.1317%;\">\n<p class=\"p9\"><em>lcov &#8211;remove coverage\/lcov.base.info \\<br \/>\n<\/em><em><span class=\"Apple-converted-space\">\u00a0<\/span>&#8216;lib\/main.dart&#8217; \\<br \/>\n<\/em><em>&#8216;lib\/*\/*.freezed.dart&#8217; \\<br \/>\n<\/em><em>&#8216;lib\/*\/*.g.dart&#8217; \\<br \/>\n<\/em><em>&#8216;lib\/*\/*.part.dart&#8217; \\<br \/>\n<\/em><em>&#8216;**\/generated\/*.dart&#8217; \\<br \/>\n<\/em><em>&#8216;**\/generated\/*\/*.dart&#8217; \\<br \/>\n<\/em><em>&#8216;**\/gen\/*.dart&#8217; \\<br \/>\n<\/em><em>&#8216;**\/gen\/*\/*.dart&#8217; \\<br \/>\n<\/em><em>-o coverage\/<span class=\"s1\">lcov.base.info<\/span><\/em><\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p class=\"p3\">Now, the whole script will look like this:<\/p>\n<p class=\"p10\"><span class=\"s4\">File<\/span> <em><strong>create_clean_lcov_and_generate_html.sh<\/strong><\/em><\/p>\n<table style=\"border-collapse: collapse; width: 100%;\" border=\"1\">\n<tbody>\n<tr>\n<td style=\"width: 98.1317%;\">\n<p class=\"p9\"><em>#!\/bin\/bash<br \/>\n<\/em><em>set -e<br \/>\n<\/em><em>flutter pub get<br \/>\n<\/em><em>sh scripts\/import_files_coverage.sh <span class=\"s2\">your_package_name<br \/>\n<\/span><\/em><em>flutter test &#8211;coverage<br \/>\n<\/em><em>lcov &#8211;remove coverage\/lcov.base.info \\<br \/>\n<\/em><em><span class=\"Apple-converted-space\">\u00a0 <\/span>&#8216;lib\/main.dart&#8217; \\<br \/>\n<\/em><em><span class=\"Apple-converted-space\">\u00a0 <\/span>&#8216;lib\/*\/*.freezed.dart&#8217; \\<br \/>\n<\/em><em><span class=\"Apple-converted-space\">\u00a0 <\/span>&#8216;lib\/*\/*.g.dart&#8217; \\<br \/>\n<\/em><em><span class=\"Apple-converted-space\">\u00a0 <\/span>&#8216;lib\/*\/*.part.dart&#8217; \\<br \/>\n<\/em><em><span class=\"Apple-converted-space\">\u00a0 <\/span>&#8216;**\/generated\/*.dart&#8217; \\<br \/>\n<\/em><em><span class=\"Apple-converted-space\">\u00a0 <\/span>&#8216;**\/generated\/*\/*.dart&#8217; \\<br \/>\n<\/em><em><span class=\"Apple-converted-space\">\u00a0 <\/span>&#8216;**\/gen\/*.dart&#8217; \\<br \/>\n<\/em><em><span class=\"Apple-converted-space\">\u00a0 <\/span>&#8216;**\/gen\/*\/*.dart&#8217; \\<br \/>\n<\/em><span class=\"Apple-converted-space\">\u00a0 <\/span>-o coverage\/<span class=\"s1\">lcov.base.info<br \/>\n<\/span><span class=\"s3\"> <em>genhtml coverage\/lcov.info -o coverage<\/em> <\/span>\/\/ <em>will generate readable html file from <span class=\"s1\">lcov.info<\/span> file<\/em><br \/>\nopen <em>coverage\/index.html<\/em><\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p class=\"p12\">And finally we need to extract total test coverage percentage from <span class=\"s1\">lcov.info<\/span> file that is covered, for that we will write following command:<\/p>\n<p class=\"p9\"><em><strong>coverageRate=$(lcov &#8211;summary &#8220;coverage\/lcov.base.info&#8221; | grep &#8220;lines&#8230;&#8230;&#8221; | cut -d &#8216; &#8216; -f 4 | cut -d &#8216;%&#8217; -f 1)<\/strong><\/em><\/p>\n<p class=\"p3\"><span class=\"s3\">REQUIRED_COVERAGE_PERC=80<\/span> \/\/ <em>You can define the percentage you want to cover, in this case we defined 80%\u00a0<\/em><\/p>\n<p class=\"p3\">After getting the percentage we can use this in any CI to exit the build process if it is not meeting our minimum code coverage criteria or percentage. Here is the following code to do the same:<\/p>\n<table style=\"border-collapse: collapse; width: 100%;\" border=\"1\">\n<tbody>\n<tr>\n<td style=\"width: 98.1317%;\">\n<p class=\"p9\"><em><strong>coverageRate=$(lcov &#8211;summary &#8220;coverage\/lcov.base.info&#8221; | grep &#8220;lines&#8230;&#8230;&#8221; | cut -d &#8216; &#8216; -f 4 | cut -d &#8216;%&#8217; -f 1)<br \/>\n<\/strong><span class=\"s3\">REQUIRED_COVERAGE_PERC=80<\/span> \/\/ You can define the percentage you want to cover, in this case we defined 80%\u00a0<\/em><br \/>\n<em>if [ &#8220;$(echo &#8220;${coverageRate} &lt; $REQUIRED_COVERAGE_PERC&#8221; | bc)&#8221; -eq 1 ]; then<\/em><br \/>\n<em><span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>printf &#8220;coverage is too low, We required ${REQUIRED_COVERAGE_PERC}% and have ${coverageRate}%.\\n&#8221;<\/em><br \/>\n<em><span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>exit 1<\/em><br \/>\n<em>else<\/em><br \/>\n<em><span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>printf &#8220;Coverage rate is up to mark.\\n&#8221;<\/em><br \/>\n<em>fi<\/em><\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p class=\"p14\">I hope this blog helps you understand how you can measure flutter code coverage and if it meets our minimum coverage criteria.<\/p>\n<p>Stay tuned for Part 2 of this blog that will cover &#8220;How to test sub-packages of flutter project&#8221;.<\/p>\n<div class=\"ap-custom-wrapper\"><\/div><!--ap-custom-wrapper-->","protected":false},"excerpt":{"rendered":"<p>As a good practice we should always write unit tests while writing the code for a software. Next, it is important to measure that the test we write is enough for our code or not, as per our coverage criteria. We leverage a few tools, discussed below, that check which lines of the codes are [&hellip;]<\/p>\n","protected":false},"author":1462,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":70},"categories":[4687,1772,1816],"tags":[1593,4969,4968,4972,4973,4970,4971],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/55017"}],"collection":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/users\/1462"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/comments?post=55017"}],"version-history":[{"count":4,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/55017\/revisions"}],"predecessor-version":[{"id":55117,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/55017\/revisions\/55117"}],"wp:attachment":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/media?parent=55017"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/categories?post=55017"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/tags?post=55017"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}