<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-01-27T09:38:23+00:00</updated><id>/feed.xml</id><title type="html">Jan Grodowski</title><subtitle>Staff software engineer at Shopify. Salzburg, Austria.</subtitle><author><name>Jan Grodowski</name><email>mrgrodo@hey.com</email></author><entry><title type="html">Großglockner 400</title><link href="/2024/01/03/grossglockner-400.html" rel="alternate" type="text/html" title="Großglockner 400" /><published>2024-01-03T00:00:00+00:00</published><updated>2024-01-03T00:00:00+00:00</updated><id>/2024/01/03/grossglockner-400</id><content type="html" xml:base="/2024/01/03/grossglockner-400.html"><![CDATA[<p>A celebration of the Alps.</p><figure><img alt="" src="/assets/images/posts/kE-XuH_qdukBp9WCEv0Fiw.jpeg" /><figcaption>View over the Großglockner Hochalpenstrasse</figcaption></figure><p>It’s a sunny May afternoon and my bicycle is packed and ready for a breathtaking weekend on some of the most stunning roads of Austria.</p><figure><img alt="" src="/assets/images/posts/aLmkEHjFaMf4oCvZxw1tLA.jpeg" /><figcaption>Gear: two kits, one set of apres-bike clothing, misc essentials. Gloves and warmers mandatory for the high-altitude sections.</figcaption></figure><p>The journey to Großglockner Hochalpenstraße was my first bikepacking experience and oh how excited I was counting days to the afternoon marking the start of the three day trip. The plan was to get close to Glocknerstraße from Salzburg on day one and then cycle over the alpine road towards Lienz on day two, while exploring the nearby viewpoints. Day three was about closing the loop back to Salzburg over the Felbertauern tunnel pass. Three days, three regions: Salzburg, Carinthia and East Tyrol.</p><p>Riding on my own, I expected lots of space to think about nothing while just absorbing the surroundings and pushing the bike forward on the numerous climbs. Pure enjoyment of the moment and a reset of the mind.</p><figure><img alt="" src="/assets/images/posts/tqed_HRSWK1fZ3SwoaiTiw.jpeg" /></figure><figure><img alt="" src="/assets/images/posts/vpXFyVGwui4Qybxla5uDEg.jpeg" /><figcaption>On the way from Salzburg to Bischofshofen.</figcaption></figure><p>The first Friday stage was meant to be means to an end: arrive at the Glocknerstraße after a full day at the desk. I had some first thoughts about this article already in my head as I zoomed through city traffic. Funny enough, anxiety and anticipation for what’s coming was still occupying my headspace. I thought about the numbers, steep grades, elevation gains and potential risks. Do I have enough spare tubes? Will it be 400 or 380 km in total? Will I get caught in the rain?</p><figure><img alt="" src="/assets/images/posts/22rSoZrM8aqiL5e8Kkb2Bg.jpeg" /><figcaption>The majesty Hochkönig from the east side ascent to Dienter Sattel. Austrians casually loading a trailer with roadside rocks in the background.</figcaption></figure><h3>Hochkönig</h3><p>As soon as I reached the town of Bischofshofen and took the right turn into a steep road towards Dienter Sattel, my mindset drifted away from the earlier performance-focused attitude into a peaceful state of flow. So much more than means to an end. The traffic calmed down and there I was: me and the beast, the winding road facing the Salzburger giant, the Hochkönig. Cool air from road-adjacent waterfalls, mellow turns revealing more and more of the breathtaking landscape and just pushing the pedals hard was all I had in mind.</p><figure><img alt="" src="/assets/images/posts/W4wM6yGMg3I1gpT2xCaKug.jpeg" /></figure><figure><img alt="" src="/assets/images/posts/wlOB_c0aR9IuIff0j5XO8w.jpeg" /></figure><figure><img alt="" src="/assets/images/posts/ifGbPL29fFaPYsAEz3zzew.jpeg" /><figcaption>Tranquility of the Hochkönig massif, Dienter Sattel (top) and Maria Alm am Steineren Meer (bottom)</figcaption></figure><p>I won’t be bothering you with numbers and stats too much, though I’ll share the route in more detail in the appendix (1). Long story short, the final bit of the 17km Dienter Sattel ascent really squeezed my legs. The Dolomite-like views and a few sweet and loud 911s passing by were only the prelude to what was about to come on Day 2.</p><p>The descent of the day brought me to Maria Alm am Steinernen Meer with a big smile. The bags attached to my bike didn’t affect the handling and I was positively surprised with how they felt on the long downhill: bike was still fast, planted and sharp around the corners. The ski village welcomed me with a solid glass of Hefeweizen at an outdoor restaurant, surrounded by tourists and hikers resting quietly after their day adventures. Not finishing my ride back at the start felt amazing, no matter how trivial this sounds.</p><figure><img alt="" src="/assets/images/posts/v0qyy1ryMZi5Y_Gyze5urg.jpeg" /></figure><figure><img alt="" src="/assets/images/posts/YvkIWGdwiYADFG6tDFssfg.jpeg" /><figcaption>Left: afternoon prize, right: morning in Maria Alm just before departure</figcaption></figure><p>Maria Alm is also where one of my earlier anxieties materialised… I learned how the only USB-C cable I packed doesn’t charge my bike computer properly and that GPS routes no longer load on the Wahoo computer, due to a software problem. Well, I knew I’ll figure it somehow, and I did! I went for a short walk and then straight to bed, excited for the upcoming adventures.</p><h3>Großglockner</h3><p>If you decide to do this trip and sleep at hotels like I did, book them <strong>with</strong> breakfast. Austrian breakfast buffet game is strong, trust me.</p><p>Saturday started with soft-boiled eggs, fragrant bread rolls and a big pot of filter coffee. Such a nice surprise at the cheapest hotel I could find in Maria Alm.</p><figure><img alt="" src="/assets/images/posts/G6L0TdoIeaUE1cS8i6j_jw.jpeg" /><figcaption>First kms of Day 2 at the crystal-clear waters of Zeller See. Tyre valves not aligned, sigh.</figcaption></figure><p>The famous toll road across the High Tauern and nearby the Großglockner peak is an Austrian version of the Stelvio. Expect tourists, cars and motorbikes. Fortunately, starting early paid off and I had the climb to myself most of the day.</p><figure><img alt="" src="/assets/images/posts/aA6AA9uNcfyY9L7EIyyFXw.jpeg" /></figure><figure><img alt="" src="/assets/images/posts/Slfl6qQMirFutmj4L0loww.jpeg" /></figure><figure><img alt="" src="/assets/images/posts/Rp7To1zG7fZ3GXDndYfJuQ.jpeg" /></figure><figure><img alt="" src="/assets/images/posts/nvkSqxrKoOymE8SWlGiL8g.jpeg" /><figcaption>Highlights of the Großglockner toll road</figcaption></figure><p>Departing from Maria Alm, it took a while to cycle along the Zell lake to Bruck, where the Großglocker climb officially begins. If you’re planning to just ride up there, Bruck is a perfect place to park the car. I was over the moon as the road elevated past the tree line to the first patches of snow, as end of May turned out to be perfect timing for epic snow walls. I did my best to capture all of it, but trust me the scale hits different when you’re there.</p><figure><img alt="" src="/assets/images/posts/pJAsH5x0Ux-xjCeEP4BDtg.jpeg" /></figure><figure><img alt="" src="/assets/images/posts/zYbrJnCmNgEHMvp14f5vSA.jpeg" /><figcaption>Endurance athlete essentials: gummibears.</figcaption></figure><p>I love cycling up the Hochalpenstraße as an opportunity to observe how the landscape changes with every few hundred vertical meters gained. Few KMs past the toll road booths in Fusch and the forest eventually gives space to mountain pines, barren rocks, and then just snow that kept melting slowly in the sun rays on that day. The air got chilly. The breathtaking views really opened up. There was a moment where I could see far ahead into the valley, where the climb begun. I have no idea how far it was, but the distance was spectacular. Finally, a dozen or so switchbacks until the end I could see Fuscher Törl with the iconic 180-degree final turn and viewpoint, marking the end of a 2h+ climbing effort.</p><figure><img alt="" src="/assets/images/posts/RIm-bBQSyqIT9mJJMYPK_Q.jpeg" /></figure><figure><img alt="" src="/assets/images/posts/q_anf8vQ8vd8RnoT05pL7A.jpeg" /></figure><figure><img alt="" src="/assets/images/posts/a2RCnI51deUsRgfR9lg0-A.jpeg" /><figcaption>Germknödel mit Vanillasose, the Austrian king of fluff. Food aside, panorama from Edelweißspitze overlooking the final meters of the main climb and the famous final 180 degree turn (bottom left).</figcaption></figure><p>Though, I ended the journey a bit further at the cobbled “bonus climb” leading to a nearby peak, the Edelweißspitze. Had a moment of rest there and ate lunch on the sun terrace. The sun and leftover heat from the climb kept me warm only for a short moment, so I slowly started putting on more and more winter gear for the first descent of the day. All that while appreciating the accomplishment, the views and the Germknödel swimming in a bowl of vanilla sauce.</p><figure><img alt="" src="/assets/images/posts/TARsACrIC_NRU3GQPJSb0g.jpeg" /><figcaption>Top of the cobbled road to Edelweißspitze (2571m a.s.l.)</figcaption></figure><figure><img alt="" src="/assets/images/posts/krFUi2yfeCej7Uccy9m_kg.jpeg" /></figure><figure><img alt="" src="/assets/images/posts/nDfIfQUwIXp4uO9Sa8nUgQ.jpeg" /></figure><figure><img alt="" src="/assets/images/posts/pxNOuorI4j0Audv7nvVkWg.jpeg" /><figcaption>Top: May snow walls on the ascent to Hochtor (2428m a.s.l.). Bottom left: Franz-Jozef Höhe viewpoint. Right: Heiligenblut am Großglockner and descent towards Lienz.</figcaption></figure><p>The well-deserved, long and twisty descent from Franz-Jozef’s Höhe towards Lienz in Osttirol quickly turned into the lowest point of the day and the whole trip. On the first few KMs as the road enters a wide parking area, I was stopped by police together with several cars and witnessed a cyclist being rescued by a medical helicopter after a severe accident with a car. I learned later the driver was on the incorrect lane (2).</p><p>As I’m writing this today, I know that the 44-year old cyclist from Salzburg passed away days after the crash due to irrecoverable trauma. My thoughts are with them and their relatives.</p><h3>Day 3: Felbertauernstraße</h3><p>It’s a Sunday morning. Bartek sends me this drum and bass set by Wilkinson, which was about to power me up the lengthy climb to Felbertauern tunnel.</p><p><a href="https://soundcloud.com/sheetal-sethi/sleepless-nights">https://soundcloud.com/sheetal-sethi/sleepless-nights</a></p><p><a href="https://music.apple.com/pl/album/sleepless-nights-wilkinson-dj-mix/1689299942">Sleepless Nights: Wilkinson (DJ Mix) by Wilkinson</a></p><figure><img alt="" src="/assets/images/posts/0HaRqW0Sb8pv2_qIahwdVw.jpeg" /></figure><figure><img alt="" src="/assets/images/posts/hMhrLVUGEVdj3LMAZXxqNQ.jpeg" /><figcaption>Felbertauernstraße. Long fake-flat with traffic. The price for nice views has to be paid, just push through it.</figcaption></figure><p>Located west of and parallel to the Großglockner road, the pass is a popular Alpine crossing with heavy traffic. It’s a long fake-flat climb and honestly it felt like a grind, so music helped a lot. Was it worth it? Yes, would do this loop again, because loops are the best.</p><p>In the town of Matrei close to the tunnel entry, I called the toll booths and organised a minibus transfer. It’s not cheap (€30 per car) and unavoidable, as the 7km tunnel is not open for cyclists. The friendly Austrian tunnel operator who took me &amp; the bike through to the other side provided good company and some laughs.</p><figure><img alt="" src="/assets/images/posts/e8iJfITjtPMYcRnbbqIqcQ.jpeg" /></figure><figure><img alt="" src="/assets/images/posts/FLAeYAlWkMdZf2w35X9HeQ.jpeg" /></figure><figure><img alt="" src="/assets/images/posts/sD_32k2-VOT_CJ9v1H4wMQ.jpeg" /></figure><figure><img alt="" src="/assets/images/posts/n9_4cWjwU8PwiHDJleBE2Q.jpeg" /></figure><figure><img alt="" src="/assets/images/posts/7nwr0AKA3OllHQB9gBHqXQ.jpeg" /><figcaption>Top: Felbertauerntunnel crossing. Bottom left to right: losing all the altitude meters back towards Salzburg.</figcaption></figure><p>On the other side waited the final stretch, almost all the way downhill to my hometown Salzburg. Almost, because for whatever reason I decided to do the “bonus climb” to Hirschbichl. If you decide to follow the suggested route, get ready for up to 31% grades. Lowest gear, full power off the saddle and barely moving. Type-2 fun at times. Did I discover the steepest tarmac road in the area? Maybe!</p><p>The reward was there: a peaceful descent into Klausbachtal, Hintersee and further towards Salzburg, with great views (not worse than the rest of this trip) and no traffic, only tour buses allowed on the top section of the road. I stopped at the Klausbach valley for a final snack, a drink straight from the mountain stream and a moment of appreciation for what was about to end, as I was getting closer to home and the transition back to the day-to-day. I felt joy and gratitude for this cycling adventure around my still relatively new home region.</p><p>I hope you’re motivated to get up and do your own crazy thing, whatever it is. That was at least my goal for writing down all of these feelings and facts after the big weekend in the Alps. Ride On!✌️</p><p>Thanks to all supporters of my cycling journey: Early Birds Salzburg for keeping me in shape and able to do such adventures. Cyclite.cc for the saddle and frame bags. Marco Protič (probably the best mechanic in Salzburg) for rescuing my dead shifter levers this summer. The iPhone 12 for being such a reliable adventure camera.</p><figure><img alt="" src="/assets/images/posts/SpFUO1uB6g6nBMxX3JnN3g.jpeg" /><figcaption>Cobbled finish on Edelweißspitze. Will be remembered.</figcaption></figure><p>(1) Appendix: full 392km / 6500m route on Strava (ride at your own risk)</p><p>Day 1: 88km / 1400m. Salzburg to Maria Alm am Steinernen Meer over Dienten am Hochkönig and Dienter Sattel</p><p><a href="https://www.strava.com/routes/3090748832789983786">S1 Hochkönig | 85.6 km Cycling Route on Strava</a></p><p>Day 2: 128km / 3200m. Maria Alm to Lienz in Osttirol over the Glocknerstraße and Paß Iselberg</p><p><a href="https://www.strava.com/routes/3097444616059962650">S2 | 125.9 km Cycling Route on Strava</a></p><p>Day 3: 176km / 1800m. Lienz to Salzburg over Felbertauernstraße and Klausbachtal.</p><ul><li><a href="https://www.strava.com/routes/3090750602863882794">S3E01 Zum Felbertauerntunnel | 47.2 km Cycling Route on Strava</a></li><li><a href="https://www.strava.com/routes/3090753339688584310">S3E02 Epilogue | 130.4 km Cycling Route on Strava</a></li></ul><p>(2) <a href="https://www.salzburg24.at/news/oesterreich/grossglockner-hochalpenstrasse-salzburgerin-stirbt-nach-fahrrad-unfall-139589563">https://www.salzburg24.at/news/oesterreich/grossglockner-hochalpenstrasse-salzburgerin-stirbt-nach-fahrrad-unfall-139589563</a></p>]]></content><author><name>Jan Grodowski</name><email>mrgrodo@hey.com</email></author><category term="austria" /><category term="cycling" /><category term="bikepacking" /><category term="travel" /><category term="road-cycling" /><summary type="html"><![CDATA[A celebration of the Alps.View over the Großglockner HochalpenstrasseIt’s a sunny May afternoon and my bicycle is packed and ready for a breathtaking weekend on some of the most stunning roads of Austria.Gear: two kits, one set of apres-bike clothing, misc essentials. Gloves and warmers mandatory for the high-altitude sections.The journey to Großglockner Hochalpenstraße was my first bikepacking experience and oh how excited I was counting days to the afternoon marking the start of the three day trip. The plan was to get close to Glocknerstraße from Salzburg on day one and then cycle over the alpine road towards Lienz on day two, while exploring the nearby viewpoints. Day three was about closing the loop back to Salzburg over the Felbertauern tunnel pass. Three days, three regions: Salzburg, Carinthia and East Tyrol.Riding on my own, I expected lots of space to think about nothing while just absorbing the surroundings and pushing the bike forward on the numerous climbs. Pure enjoyment of the moment and a reset of the mind.On the way from Salzburg to Bischofshofen.The first Friday stage was meant to be means to an end: arrive at the Glocknerstraße after a full day at the desk. I had some first thoughts about this article already in my head as I zoomed through city traffic. Funny enough, anxiety and anticipation for what’s coming was still occupying my headspace. I thought about the numbers, steep grades, elevation gains and potential risks. Do I have enough spare tubes? Will it be 400 or 380 km in total? Will I get caught in the rain?The majesty Hochkönig from the east side ascent to Dienter Sattel. Austrians casually loading a trailer with roadside rocks in the background.HochkönigAs soon as I reached the town of Bischofshofen and took the right turn into a steep road towards Dienter Sattel, my mindset drifted away from the earlier performance-focused attitude into a peaceful state of flow. So much more than means to an end. The traffic calmed down and there I was: me and the beast, the winding road facing the Salzburger giant, the Hochkönig. Cool air from road-adjacent waterfalls, mellow turns revealing more and more of the breathtaking landscape and just pushing the pedals hard was all I had in mind.Tranquility of the Hochkönig massif, Dienter Sattel (top) and Maria Alm am Steineren Meer (bottom)I won’t be bothering you with numbers and stats too much, though I’ll share the route in more detail in the appendix (1). Long story short, the final bit of the 17km Dienter Sattel ascent really squeezed my legs. The Dolomite-like views and a few sweet and loud 911s passing by were only the prelude to what was about to come on Day 2.The descent of the day brought me to Maria Alm am Steinernen Meer with a big smile. The bags attached to my bike didn’t affect the handling and I was positively surprised with how they felt on the long downhill: bike was still fast, planted and sharp around the corners. The ski village welcomed me with a solid glass of Hefeweizen at an outdoor restaurant, surrounded by tourists and hikers resting quietly after their day adventures. Not finishing my ride back at the start felt amazing, no matter how trivial this sounds.Left: afternoon prize, right: morning in Maria Alm just before departureMaria Alm is also where one of my earlier anxieties materialised… I learned how the only USB-C cable I packed doesn’t charge my bike computer properly and that GPS routes no longer load on the Wahoo computer, due to a software problem. Well, I knew I’ll figure it somehow, and I did! I went for a short walk and then straight to bed, excited for the upcoming adventures.GroßglocknerIf you decide to do this trip and sleep at hotels like I did, book them with breakfast. Austrian breakfast buffet game is strong, trust me.Saturday started with soft-boiled eggs, fragrant bread rolls and a big pot of filter coffee. Such a nice surprise at the cheapest hotel I could find in Maria Alm.First kms of Day 2 at the crystal-clear waters of Zeller See. Tyre valves not aligned, sigh.The famous toll road across the High Tauern and nearby the Großglockner peak is an Austrian version of the Stelvio. Expect tourists, cars and motorbikes. Fortunately, starting early paid off and I had the climb to myself most of the day.Highlights of the Großglockner toll roadDeparting from Maria Alm, it took a while to cycle along the Zell lake to Bruck, where the Großglocker climb officially begins. If you’re planning to just ride up there, Bruck is a perfect place to park the car. I was over the moon as the road elevated past the tree line to the first patches of snow, as end of May turned out to be perfect timing for epic snow walls. I did my best to capture all of it, but trust me the scale hits different when you’re there.Endurance athlete essentials: gummibears.I love cycling up the Hochalpenstraße as an opportunity to observe how the landscape changes with every few hundred vertical meters gained. Few KMs past the toll road booths in Fusch and the forest eventually gives space to mountain pines, barren rocks, and then just snow that kept melting slowly in the sun rays on that day. The air got chilly. The breathtaking views really opened up. There was a moment where I could see far ahead into the valley, where the climb begun. I have no idea how far it was, but the distance was spectacular. Finally, a dozen or so switchbacks until the end I could see Fuscher Törl with the iconic 180-degree final turn and viewpoint, marking the end of a 2h+ climbing effort.Germknödel mit Vanillasose, the Austrian king of fluff. Food aside, panorama from Edelweißspitze overlooking the final meters of the main climb and the famous final 180 degree turn (bottom left).Though, I ended the journey a bit further at the cobbled “bonus climb” leading to a nearby peak, the Edelweißspitze. Had a moment of rest there and ate lunch on the sun terrace. The sun and leftover heat from the climb kept me warm only for a short moment, so I slowly started putting on more and more winter gear for the first descent of the day. All that while appreciating the accomplishment, the views and the Germknödel swimming in a bowl of vanilla sauce.Top of the cobbled road to Edelweißspitze (2571m a.s.l.)Top: May snow walls on the ascent to Hochtor (2428m a.s.l.). Bottom left: Franz-Jozef Höhe viewpoint. Right: Heiligenblut am Großglockner and descent towards Lienz.The well-deserved, long and twisty descent from Franz-Jozef’s Höhe towards Lienz in Osttirol quickly turned into the lowest point of the day and the whole trip. On the first few KMs as the road enters a wide parking area, I was stopped by police together with several cars and witnessed a cyclist being rescued by a medical helicopter after a severe accident with a car. I learned later the driver was on the incorrect lane (2).As I’m writing this today, I know that the 44-year old cyclist from Salzburg passed away days after the crash due to irrecoverable trauma. My thoughts are with them and their relatives.Day 3: FelbertauernstraßeIt’s a Sunday morning. Bartek sends me this drum and bass set by Wilkinson, which was about to power me up the lengthy climb to Felbertauern tunnel.https://soundcloud.com/sheetal-sethi/sleepless-nightsSleepless Nights: Wilkinson (DJ Mix) by WilkinsonFelbertauernstraße. Long fake-flat with traffic. The price for nice views has to be paid, just push through it.Located west of and parallel to the Großglockner road, the pass is a popular Alpine crossing with heavy traffic. It’s a long fake-flat climb and honestly it felt like a grind, so music helped a lot. Was it worth it? Yes, would do this loop again, because loops are the best.In the town of Matrei close to the tunnel entry, I called the toll booths and organised a minibus transfer. It’s not cheap (€30 per car) and unavoidable, as the 7km tunnel is not open for cyclists. The friendly Austrian tunnel operator who took me &amp; the bike through to the other side provided good company and some laughs.Top: Felbertauerntunnel crossing. Bottom left to right: losing all the altitude meters back towards Salzburg.On the other side waited the final stretch, almost all the way downhill to my hometown Salzburg. Almost, because for whatever reason I decided to do the “bonus climb” to Hirschbichl. If you decide to follow the suggested route, get ready for up to 31% grades. Lowest gear, full power off the saddle and barely moving. Type-2 fun at times. Did I discover the steepest tarmac road in the area? Maybe!The reward was there: a peaceful descent into Klausbachtal, Hintersee and further towards Salzburg, with great views (not worse than the rest of this trip) and no traffic, only tour buses allowed on the top section of the road. I stopped at the Klausbach valley for a final snack, a drink straight from the mountain stream and a moment of appreciation for what was about to end, as I was getting closer to home and the transition back to the day-to-day. I felt joy and gratitude for this cycling adventure around my still relatively new home region.I hope you’re motivated to get up and do your own crazy thing, whatever it is. That was at least my goal for writing down all of these feelings and facts after the big weekend in the Alps. Ride On!✌️Thanks to all supporters of my cycling journey: Early Birds Salzburg for keeping me in shape and able to do such adventures. Cyclite.cc for the saddle and frame bags. Marco Protič (probably the best mechanic in Salzburg) for rescuing my dead shifter levers this summer. The iPhone 12 for being such a reliable adventure camera.Cobbled finish on Edelweißspitze. Will be remembered.(1) Appendix: full 392km / 6500m route on Strava (ride at your own risk)Day 1: 88km / 1400m. Salzburg to Maria Alm am Steinernen Meer over Dienten am Hochkönig and Dienter SattelS1 Hochkönig | 85.6 km Cycling Route on StravaDay 2: 128km / 3200m. Maria Alm to Lienz in Osttirol over the Glocknerstraße and Paß IselbergS2 | 125.9 km Cycling Route on StravaDay 3: 176km / 1800m. Lienz to Salzburg over Felbertauernstraße and Klausbachtal.S3E01 Zum Felbertauerntunnel | 47.2 km Cycling Route on StravaS3E02 Epilogue | 130.4 km Cycling Route on Strava(2) https://www.salzburg24.at/news/oesterreich/grossglockner-hochalpenstrasse-salzburgerin-stirbt-nach-fahrrad-unfall-139589563]]></summary></entry><entry><title type="html">Skipping selected test coverage warnings with undercover</title><link href="/2022/03/31/skipping-selected-test-coverage-warnings-with-undercover.html" rel="alternate" type="text/html" title="Skipping selected test coverage warnings with undercover" /><published>2022-03-31T00:00:00+00:00</published><updated>2022-03-31T00:00:00+00:00</updated><id>/2022/03/31/skipping-selected-test-coverage-warnings-with-undercover</id><content type="html" xml:base="/2022/03/31/skipping-selected-test-coverage-warnings-with-undercover.html"><![CDATA[<p>Undercover checks are all about making sure that no changed line of code is merged without test coverage. Ideally, this happens in code review with warnings auto-delivered as review comments on a method-by-method basis</p><p>If you’re new to undercover, make sure to visit <a href="https://undercover-ci.com/">https://undercover-ci.com</a> for more info, or read the <a href="/2018/12/19/improve-your-ruby-code-reviews-with-actionable-code-coverage-and-undercover.html">getting started guide</a>.</p><p>I was recently contacted by Aleksandr in <a href="https://github.com/grodowski/pronto-undercover/issues/20">this GitHub issue</a> for pronto-undercover who proposed making undercover warnings less strict with configurable required percentage. After all, pull requests in projects with low or nonexistent initial test coverage can likely generate large numbers of hard-to-deal-with warnings which you might not want to address all at once. This was the case for the issue author.</p><figure><img alt="" src="/assets/images/posts/amdigjwB2lDMT7vXUR2R2A.jpeg" /></figure><p>You can set the undercover PR check to optional, which I normally recommend, but it won’t fix the noise generated by a poorly tested codebase.</p><p>Should you then just rely on a global coverage threshold (e.g. SimpleCov.minimum_coverage) that allows more wiggle room, e.g. 50%? I don’t think so, because random lines might slip through review and make bugs more likely to happen. But, I recently found out about a better and more explicit test coverage strategy thanks to SimpleCov.</p><p>✨ SimpleCov lets you wrap any Ruby code in :nocov: comments to disable coverage reporting, which also works great for suppressing any undercover warnings. I didn’t know about this. ✨</p><pre># :nocov:<br>def skip_this_method<br>  never_reached<br>end<br># :nocov:</pre><p>As I wrote earlier, my general undercover CI workflow was to set undercover as an optional check, so that anyone can merge changes even when some acknowledged lines and methods are missing tests. It’s good, because these are surfaced and scrutinized in code review.</p><p>But what about this strategy instead:</p><ul><li>mark the coverage pull request check as <strong>required</strong> instead of optional</li><li>in case you want to acknowledge and skip a particular warning, update the code and tag relevant lines with :nocov:(could be fully automated by UndercoverCI with repo write permission in the future!)</li><li>only ship on green, with potential untested lines flagged and silenced</li></ul><p>Why? This way information loss is avoided and your future self (and team) will clearly know which code parts went to production untested thanks to all the :nocov: comments.</p><p>Let me know what you think about this. Will you ever get back to those comments to fix them? 😅 Did you know about this handy SimpleCov feature? Maybe it’s a good idea to update your test coverage strategy to allow untested changes only when wrapped in :nocov: blocks.</p><p>📚 The :nocov: syntax has now a dedicated section in <a href="https://undercover-ci.com/docs#ignoring-test-coverage-checks">UndercoverCI docs: skipping/ignoring coverage</a>.</p>]]></content><author><name>Jan Grodowski</name><email>mrgrodo@hey.com</email></author><category term="git" /><category term="ruby" /><category term="testing" /><category term="github" /><category term="ruby-on-rails" /><summary type="html"><![CDATA[Undercover checks are all about making sure that no changed line of code is merged without test coverage. Ideally, this happens in code review with warnings auto-delivered as review comments on a method-by-method basisIf you’re new to undercover, make sure to visit https://undercover-ci.com for more info, or read the getting started guide.I was recently contacted by Aleksandr in this GitHub issue for pronto-undercover who proposed making undercover warnings less strict with configurable required percentage. After all, pull requests in projects with low or nonexistent initial test coverage can likely generate large numbers of hard-to-deal-with warnings which you might not want to address all at once. This was the case for the issue author.You can set the undercover PR check to optional, which I normally recommend, but it won’t fix the noise generated by a poorly tested codebase.Should you then just rely on a global coverage threshold (e.g. SimpleCov.minimum_coverage) that allows more wiggle room, e.g. 50%? I don’t think so, because random lines might slip through review and make bugs more likely to happen. But, I recently found out about a better and more explicit test coverage strategy thanks to SimpleCov.✨ SimpleCov lets you wrap any Ruby code in :nocov: comments to disable coverage reporting, which also works great for suppressing any undercover warnings. I didn’t know about this. ✨# :nocov:def skip_this_method never_reachedend# :nocov:As I wrote earlier, my general undercover CI workflow was to set undercover as an optional check, so that anyone can merge changes even when some acknowledged lines and methods are missing tests. It’s good, because these are surfaced and scrutinized in code review.But what about this strategy instead:mark the coverage pull request check as required instead of optionalin case you want to acknowledge and skip a particular warning, update the code and tag relevant lines with :nocov:(could be fully automated by UndercoverCI with repo write permission in the future!)only ship on green, with potential untested lines flagged and silencedWhy? This way information loss is avoided and your future self (and team) will clearly know which code parts went to production untested thanks to all the :nocov: comments.Let me know what you think about this. Will you ever get back to those comments to fix them? 😅 Did you know about this handy SimpleCov feature? Maybe it’s a good idea to update your test coverage strategy to allow untested changes only when wrapped in :nocov: blocks.📚 The :nocov: syntax has now a dedicated section in UndercoverCI docs: skipping/ignoring coverage.]]></summary></entry><entry><title type="html">Branch coverage now available in Undercover checks ✨</title><link href="/2021/03/16/branch-coverage-now-available-in-undercover-checks.html" rel="alternate" type="text/html" title="Branch coverage now available in Undercover checks ✨" /><published>2021-03-16T00:00:00+00:00</published><updated>2021-03-16T00:00:00+00:00</updated><id>/2021/03/16/branch-coverage-now-available-in-undercover-checks</id><content type="html" xml:base="/2021/03/16/branch-coverage-now-available-in-undercover-checks.html"><![CDATA[<p>The purpose of branch coverage is to check whether every conditional branch in a given line of code has been tested and Ruby has this feature since 2.5. Ruby’s simplecov can report branch coverage too and now so does undercover with the new 0.4 release!</p><p>This addition allows you to detect <em>even more </em>untested code within each automated pull request review, including untested case statements, conditional method calls (e.g. foo&amp;.jump), single-line ifs (return :foo if valid?) or ternaries (foo ? 1 : 2).</p><p>Head over to <a href="https://github.com/grodowski/undercover/blob/master/README.md">the readme</a> if you’re getting started, otherwise here’s what to do to add branch coverage reporting to a pre-existing undercover or UndercoverCI project setup:</p><p>1️⃣ Ensure your test command reports coverage using simplecov &gt;= 0.18.0 and simplecov-lcov &gt;= 0.8.0</p><p>2️⃣ Add enable_coverage(:branch) to the simplecov configuration block in spec_ or test_helper.rb:</p><pre>SimpleCov.start do<br>  enable_coverage(:branch)<br>end</pre><p>3️⃣ That’s it! From now on, your checks will pick up and flag any untested code branches, plus you’ll see the total number of branches per method in the UndercoverCI check summary. 🤙🕵</p><p>This is how it looks in a sample check result from <a href="https://undercover-ci.com">UndercoverCI</a> (GitHub App):</p><figure><img alt="" src="/assets/images/posts/ffLoVBkNXZNTGpGvtSyOXw.png" /><figcaption>Check the exact count of untested branches in each line by expanding the “Raw output” view</figcaption></figure><p>And that’s a sample branch coverage result from undercover CLI:</p><figure><img alt="" src="/assets/images/posts/2I3merI8ZizpbjJK5uykuQ.png" /><figcaption>Line 5 was flagged with a single hit, but only one branch covered</figcaption></figure><p>On a closing note, this addition to undercover was possible thanks to <a href="https://github.com/PragTob">PragTob</a> who added branch coverage to SimpleCov and <a href="https://github.com/magneland">Magne Land</a>’s contribution to the branch cov feature in Undercover. Thank you! ❤️</p>]]></content><author><name>Jan Grodowski</name><email>mrgrodo@hey.com</email></author><category term="ruby-on-rails" /><category term="github" /><category term="github-actions" /><category term="testing" /><category term="ruby" /><summary type="html"><![CDATA[The purpose of branch coverage is to check whether every conditional branch in a given line of code has been tested and Ruby has this feature since 2.5. Ruby’s simplecov can report branch coverage too and now so does undercover with the new 0.4 release!This addition allows you to detect even more untested code within each automated pull request review, including untested case statements, conditional method calls (e.g. foo&amp;.jump), single-line ifs (return :foo if valid?) or ternaries (foo ? 1 : 2).Head over to the readme if you’re getting started, otherwise here’s what to do to add branch coverage reporting to a pre-existing undercover or UndercoverCI project setup:1️⃣ Ensure your test command reports coverage using simplecov &gt;= 0.18.0 and simplecov-lcov &gt;= 0.8.02️⃣ Add enable_coverage(:branch) to the simplecov configuration block in spec_ or test_helper.rb:SimpleCov.start do enable_coverage(:branch)end3️⃣ That’s it! From now on, your checks will pick up and flag any untested code branches, plus you’ll see the total number of branches per method in the UndercoverCI check summary. 🤙🕵This is how it looks in a sample check result from UndercoverCI (GitHub App):Check the exact count of untested branches in each line by expanding the “Raw output” viewAnd that’s a sample branch coverage result from undercover CLI:Line 5 was flagged with a single hit, but only one branch coveredOn a closing note, this addition to undercover was possible thanks to PragTob who added branch coverage to SimpleCov and Magne Land’s contribution to the branch cov feature in Undercover. Thank you! ❤️]]></summary></entry><entry><title type="html">Introducing UndercoverCI!</title><link href="/2020/06/24/introducing-undercoverci.html" rel="alternate" type="text/html" title="Introducing UndercoverCI!" /><published>2020-06-24T00:00:00+00:00</published><updated>2020-06-24T00:00:00+00:00</updated><id>/2020/06/24/introducing-undercoverci</id><content type="html" xml:base="/2020/06/24/introducing-undercoverci.html"><![CDATA[<p>Today, I’m launching UndercoverCI— a CI robot who protects your Ruby codebase from untested code changes. Take a moment to learn why you should add it to your team’s code review workflow.</p><h4>What?</h4><p>Adding UndercoverCI to your code review workflow will prevent untested code changes from slipping into production without anyone noticing.</p><figure><img alt="" src="/assets/images/posts/9kZajb6Rudg8EjKpzqY2fQ.png" /><figcaption>A sample pull request comment from UndercoverCI: the new method is missing a test</figcaption></figure><p>UndercoverCI is a GitHub App for Ruby that finds untested code changes in your commits and pull requests by reading each diff and comparing against a test coverage report. The result: actionable comments on untested methods, classes and blocks, whereas other coverage tools would only give you percentage data.</p><h4>Why is that better than $my_current_coverage_tool?</h4><p>I’ve written about why you should <a href="/2018/07/31/stop-shipping-untested-ruby-code-with-undercover.html">stop shipping untested Ruby code with undercover</a> and how to <a href="/2018/12/19/improve-your-ruby-code-reviews-with-actionable-code-coverage-and-undercover.html">wire it up with your workflow</a> when I released undercover — the core gem that actually powers UndercoverCI (<a href="http://github.com/grodowski/undercover">470+ stars on GitHub and counting!</a>).</p><p>Here’s a TLDR. You should consider UndercoverCI if:</p><ul><li>You think that aiming for full test coverage might not be the best investment for your team, but want to ensure all pull requests are well-tested.</li><li>You’d like to encourage your team to write more robust tests and increase test coverage where it matters and at the right time.</li><li>You’d rather automate such code review comments (my coworkers’ real replies to undercover):</li></ul><blockquote>Actually the tests weren’t even calling the code I’ve changed.</blockquote><p>, or:</p><blockquote>Looks like this branch was never tested…</blockquote><p>To recap the above points, existing coverage tools for Ruby provide percentage data and diffs, yet they guarantee that all committed lines are covered only if you hit 100% or review the test coverage report manually before each merge. You can automate that effort with UndercoverCI and get PR checks that look like if someone has searched for missing tests in the SimpleCov report. This makes it useful for projects scoring below 100% coverage or ones that won’t give you the confidence of a comprehensive test-suite at all. It’s a focused and local approach, which ensures that you invest in test coverage where it matters most.</p><h4>🕵️‍♀️ Ok, sign me up!</h4><p>If you’re convinced, <strong>sign up on the </strong><a href="https://undercover-ci.com"><strong>UndercoverCI website</strong></a><strong> or go straight to </strong><a href="https://github.com/apps/undercoverci"><strong>UndercoverCI on GitHub</strong></a><strong> to install the app!</strong></p><p>How much does it cost? Yes it’s going to be a paid product with a monthly subscription starting at about $29 (exact pricing TBD). Good news is that you can still get it for free if you sign up before <strong>2020–07–31!</strong></p>]]></content><author><name>Jan Grodowski</name><email>mrgrodo@hey.com</email></author><category term="ruby" /><category term="software-testing" /><category term="code-quality" /><category term="ruby-on-rails" /><category term="programming" /><summary type="html"><![CDATA[Today, I’m launching UndercoverCI— a CI robot who protects your Ruby codebase from untested code changes. Take a moment to learn why you should add it to your team’s code review workflow.What?Adding UndercoverCI to your code review workflow will prevent untested code changes from slipping into production without anyone noticing.A sample pull request comment from UndercoverCI: the new method is missing a testUndercoverCI is a GitHub App for Ruby that finds untested code changes in your commits and pull requests by reading each diff and comparing against a test coverage report. The result: actionable comments on untested methods, classes and blocks, whereas other coverage tools would only give you percentage data.Why is that better than $my_current_coverage_tool?I’ve written about why you should stop shipping untested Ruby code with undercover and how to wire it up with your workflow when I released undercover — the core gem that actually powers UndercoverCI (470+ stars on GitHub and counting!).Here’s a TLDR. You should consider UndercoverCI if:You think that aiming for full test coverage might not be the best investment for your team, but want to ensure all pull requests are well-tested.You’d like to encourage your team to write more robust tests and increase test coverage where it matters and at the right time.You’d rather automate such code review comments (my coworkers’ real replies to undercover):Actually the tests weren’t even calling the code I’ve changed., or:Looks like this branch was never tested…To recap the above points, existing coverage tools for Ruby provide percentage data and diffs, yet they guarantee that all committed lines are covered only if you hit 100% or review the test coverage report manually before each merge. You can automate that effort with UndercoverCI and get PR checks that look like if someone has searched for missing tests in the SimpleCov report. This makes it useful for projects scoring below 100% coverage or ones that won’t give you the confidence of a comprehensive test-suite at all. It’s a focused and local approach, which ensures that you invest in test coverage where it matters most.🕵️‍♀️ Ok, sign me up!If you’re convinced, sign up on the UndercoverCI website or go straight to UndercoverCI on GitHub to install the app!How much does it cost? Yes it’s going to be a paid product with a monthly subscription starting at about $29 (exact pricing TBD). Good news is that you can still get it for free if you sign up before 2020–07–31!]]></summary></entry><entry><title type="html">Improve your Ruby code reviews with actionable code coverage and Undercover</title><link href="/2018/12/19/improve-your-ruby-code-reviews-with-actionable-code-coverage-and-undercover.html" rel="alternate" type="text/html" title="Improve your Ruby code reviews with actionable code coverage and Undercover" /><published>2018-12-19T00:00:00+00:00</published><updated>2018-12-19T00:00:00+00:00</updated><id>/2018/12/19/improve-your-ruby-code-reviews-with-actionable-code-coverage-and-undercover</id><content type="html" xml:base="/2018/12/19/improve-your-ruby-code-reviews-with-actionable-code-coverage-and-undercover.html"><![CDATA[<p><em>This is part 2 of </em><a href="/2018/07/31/stop-shipping-untested-ruby-code-with-undercover.html"><em>Stop Shipping Untested Ruby Code with Undercover</em></a><em>. If you’re wondering What The Heck™️ all of this is about, you may want to read the first post in a new tab.</em></p><p>Ruby’s <a href="https://github.com/colszowka/simplecov">SimpleCov</a> lets us track test coverage and is common when developing Ruby on Rails apps. Many code review services that rely on SimpleCov allow us to see the coverage delta or diff coverage as a percentage. I’ve been recently talking to Ruby developer friends and gathering feedback about an alternative approach, which is to turn untested code into actionable pull request warnings, because <a href="https://www.codementor.io/theundefined/ruby-the-misconceptions-of-100-code-coverage-keituk3qc">coverage as percentage is insufficient</a>.</p><p>This post will teach you how to get started with Undercover. We’ll set it up locally, so that commits are blocked until we have tests in place. Then, we’ll learn how to add a code build status check in CI services and finally, how to post automated review comments to GitHub using Pronto.</p><p>It’s almost Christmas, so let’s get started and give our Ruby projects some love!</p><h3>Getting started with local coverage checks</h3><h4>Setup</h4><p>First fetch the gem from RubyGems with:</p><pre>gem install undercover</pre><p>Once installed, the undercover command should be available to your system. However, to make it fully functional we need to set up test coverage tracking.</p><p>Update your Gemfile with SimpleCov and an LCOV reporter, which is Undercover’s coverage input file format:</p><pre>gem &#39;simplecov&#39;<br>gem &#39;simplecov-lcov&#39;<br>gem &#39;simplecov-html&#39; # if you&#39;d like classic html coverage reports</pre><p>After installing them with bundle install, we enable coverage tracking at the very top of the test_helper.rb or spec_helper.rb, before any of the application code is required.</p><pre>require &#39;simplecov&#39;<br>require &#39;simplecov-lcov&#39;<br>SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true<br>SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([<br>  SimpleCov::Formatter::LcovFormatter,<br>  SimpleCov::Formatter::HTMLFormatter,<br>])<br>SimpleCov.start do<br>  add_filter(/^\/spec\//) # For RSpec  <br>  add_filter(/^\/test\//) # For Minitest<br>  enable_coverage(:branch) # Enables branch coverage<br>end</pre><p>To verify the above set up, we can now run specs and see that a nonempty coverage/ directory has appeared in the project root.</p><h4>Using the CLI</h4><p>When undercover is invoked with no arguments, the command-line interface inspects the most recent coverage report and looks for untested methods that appear in the current changeset. What are these methods exactly? These are all code blocks that have any source lines appearing in the current git index (staged) and work tree (unstaged). Let’s illustrate that with an example.</p><p>Assuming no changes have been made since last commit, undercover prints out a message and exits cleanly.</p><pre>$ undercover<br>✅ No reportable changes<br>Undercover finished in 0.0742s</pre><p>The fun begins when we start making changes to our code and test suite. Let’s assume we change a method, run our test suite and invoke undercover again. Now, we get a different output:</p><figure><img alt="" src="/assets/images/posts/RCw7Hr6vjqfZ25Bdz5PcKQ.png" /></figure><p>Whoops! We need to add tests to the validate instance method to keep Undercover happy. If we add those and run undercover again, we’ll be back to green.</p><h4>Compare to branch or commit</h4><p>We have analysed test coverage for the current diff, but what about bigger chunks of work? Undercover can perform the same analysis with respect to any branch or commit. This is where the -c or --compare option comes in handy.</p><p>Try this set of command arguments:</p><pre>undercover --compare master</pre><p>It will look at the most recent coverage report and find untested methods in the entire diff between HEAD and what we’ve set in --compare. Comes in handy for various branching workflows!</p><h4>Automate</h4><p>We now have what we need to create the beginning of an automated feedback loop. Let’s get started with git hooks.</p><p>What are they? Git hooks are customisable scripts that live in .git/hooks of every repository and are executed before or after git events. They can be edited manually, but I suggest to start with a helper gem called <a href="https://github.com/brigade/overcommit">overcommit</a>, which does an excellent job managing community standard git hooks and custom ones.</p><p>Follow Overcommit’s <a href="https://github.com/brigade/overcommit/blob/master/README.md">readme</a> to install it and place the contents of this suggested config in .overcommit.yml. An starter config should have been created during installation.</p><pre>PrePush:<br>  Undercover:<br>    enabled: true<br>    required: true<br>    command: [&#39;undercover&#39;, &#39;--compare&#39;, &#39;master&#39;]</pre><p>This is my config of choice that ensures all tests are in place before code is pushed (PrePush). You might want to change this to suite your preferences and perhaps use a PreCommit variant instead. It’s also good to know that hooks can be disabled in an emergency by setting OVERCOMMIT_DISABLE=1 or using the --no-verify option in commands like commit or push.</p><h3>Set up Continuous Integration</h3><p>The next steps will cover adding Undercover to analyse commits project-wide on the CI server, so that we close the feedback loop and make it part of the code review process.</p><h3>Step 1: Build status check</h3><p>We’ll start with a status check for test coverage that runs tests and then Undercover. I have prepared a few starter configs for a few popular CI services: Travis CI, CircleCI, Semaphore and Codeship. They are also stored in the repo in case you need them later.</p><figure><img alt="" src="/assets/images/posts/oGYkCqiQOfeXlkHt3GmsIw.png" /></figure><h4>GitHub Actions</h4><p>The sample workflow config was taken straight from the undercover repository. It fails the build based on coverage results and failures can be inspected within the output log. This is not ideal, but works just fine.</p><pre># .github/workflows/ruby.yml<br>name: Rails Unit Tests<br>on: [push, pull_request]<br>jobs:<br>  build:<br>    runs-on: ubuntu-latest<br>    steps:<br>    - uses: actions/checkout@v4<br>    - name: Set up Ruby 3.2<br>      uses: ruby/setup-ruby@v1<br>      with:<br>        ruby-version: 3.2<br>    - name: Build and test with Rake<br>      env:<br>        RAILS_ENV: test<br>      run: |<br>        # other pre-requisite setup steps...<br>        gem install bundler undercover<br>        bundle install --jobs 4 --retry 3<br>        bundle exec rake test<br>        undercover --compare origin/master</pre><h4><strong>CircleCI</strong></h4><p>Sample single-job config for CircleCI that runs specs and validates coverage with Undercover.</p><pre># .circleci/config.yml<br>version: 2<br>jobs:<br>  build:<br>    docker:<br>      - image: circleci/ruby:2.5-browsers<br>    steps:<br>      - checkout<br>      - run:<br>          name: Install dependencies<br>          command: |<br>            sudo apt-get install cmake<br>            bundle install<br>      - run:<br>          name: Run RSpec<br>          command: bundle exec rspec<br>      - run:<br>          name: Check coverage<br>          command: |<br>            gem install undercover<br>            undercover --compare origin/master</pre><p>We can go even further and create a separate job for analysing coverage, leveraging features of CircleCI 2.0. That is useful, because we can benefit from workflows and build conditions, but requires a bit more work. Having that in mind, there is <a href="https://github.com/grodowski/undercover/blob/master/docs/circleci_advanced.yml">another more advanced starter config</a> available on GitHub, where the LCOV report file is shared between multiple CircleCI jobs to create individual build stages for test and coverage.</p><figure><img alt="" src="/assets/images/posts/nDpcWHjJ5FhOraQtPVKhrQ.png" /></figure><h4><strong>Semaphore</strong></h4><p>Semaphore pipeline example with a single job running rspec and undercover:</p><pre>version: v1.0<br>name: RSpec + Undercover<br>agent:<br>  machine:<br>    type: e1-standard-2<br>    os_image: ubuntu1804<br>blocks:<br>  - name: &quot;RSpec&quot;<br>    task:<br>      jobs:<br>      - name: Run Specs and Check Coverage<br>        commands:<br>          - checkout<br>          - bundle install<br>          - bundle exec rspec<br>          - gem install undercover<br>          - undercover --compare origin/master</pre><h4><strong>Codeship</strong></h4><p>To run specs and analyse coverage in Codeship, go to <strong>Project Settings </strong>and place the following instructions in the <strong>Setup Commands </strong>section:</p><pre>rvm use 2.5.3 --install<br>bundle install<br>gem install undercover</pre><p>Then, run specs and undercover as part of the <strong>Test Pipeline</strong>:</p><pre>bundle exec rspec --format documentation --color<br># pull origin/master to have a ref to compare against<br>git remote set-branches --add origin master<br>git fetch<br>undercover --compare origin/master</pre><p>One caveat is that Codeship uses a shallow git clone, so we need to specifically add the master branch (or any other branch we want to compare with) to the cloned repository. This way we can use undercover --compare origin/master.</p><h4><strong>Merging test results from multiple test runners</strong></h4><p>Another common caveat that applies to most CI services supporting parallel execution is about partial test results. Undercover does not currently support partial coverage reports, so we need to merge them into one before running the undercover command. If this applies to you, <a href="https://gist.github.com/grodowski/9744ff91034dce8df20c2a8210409fb0">this gist</a> might be a good starting point.</p><h3>Step 2: Automated GitHub comments</h3><p>We have already closed the feedback loop with the build status, but that’s not all we can do. How about not having to inspect the build output every time untested code is detected? We can create automated pull request comments thanks to <a href="https://github.com/prontolabs/pronto">Pronto</a> and its integration with Undercover!</p><p>Pronto is an open-source automated code review framework that integrates with <a href="https://github.com/prontolabs/pronto#runners">multiple tools</a> (Rubocop, Reek, Brakeman included) and offers integrations with SCMs like GitHub, GitLab and Bitbucket.</p><figure><img alt="" src="/assets/images/posts/lO7redYdsF3WnQJDbLZ2_w.png" /><figcaption>That’s how automated code review looks like at Rainforest</figcaption></figure><p>To start setting up review comments, we need a GitHub API OAuth token. It’s up to you to create a new bot user account or use a personal one. Either way, please sign in to GitHub and go to Settings → Developer Settings → Personal Access Token. Then click on “Generate New Token” illustrated below.</p><figure><img alt="" src="/assets/images/posts/eGOJ8HYl7VnwS3AeGTn_jw.png" /></figure><p>Let’s create a token with repo or public_repo privilege scope and make it available to the CI environment. From now on we’ll use Codeship as an example, but these instructions should require minimal changes no matter which CI you are using.</p><p>To set build environment variables in Codeship, visit Project Settings → Environment and specify each new variable as a key value pair. Please note that Pronto expects the variable holding our access token to be named PRONTO_GITHUB_ACCESS_TOKEN.</p><p>Next, we need update the setup instructions to install pronto and pronto-undercover gems instead of just undercover. This is how the Setup Commands section should look like:</p><pre># Setup Commands<br>rvm use 3.2.0 --install<br>bundle install<br>gem install pronto<br>gem install pronto-undercover</pre><p>Then, modify the Test Pipeline to run tests and then analyse coverage with pronto. We will use the github_pr formatter option to enable the GitHub integration.</p><pre># Test Pipeline<br>bundle exec rspec --format documentation --color<br># pull origin/master to have a ref to compare against<br>git remote set-branches --add origin master<br>git fetch<br>pronto run -f github_pr -c origin/master</pre><p>Now, when we save our changes, re-run the build and Pronto triggers any coverage warning, it will create comments in GitHub instead of failing the CI build. Yas!</p><figure><img alt="" src="/assets/images/posts/lGVTrJRvPLRkkOPiO22pHw.png" /></figure><h3>Summary</h3><p>After reading this post you should know how to:</p><ol><li>Set up the undercover gem to run locally, so that untested code changes do not escape the development environment</li><li>Configure your CI to do the same thing, but across all commits and remote branches</li><li>Create an automated code coverage feedback loop with Pronto and code review comments that appear directly in a GitHub pull request diff.</li></ol><p>I hope Undercover will help you deliver better and well tested code with more confidence! If you have any feedback or run into issues with any of the presented setups, please leave comments below, create a GitHub issue or get in touch with me.</p><p>Happy Holidays!</p><p>And don’t forget to leave a ⭐️ at <a href="https://github.com/grodowski/undercover">https://github.com/grodowski/undercover</a>. Also make sure to check out the Undercover GitHub App, which is free to use for public repos: <a href="https://undercover-ci.com.">https://undercover-ci.com</a>.</p>]]></content><author><name>Jan Grodowski</name><email>mrgrodo@hey.com</email></author><category term="code-review" /><category term="github" /><category term="ruby" /><summary type="html"><![CDATA[This is part 2 of Stop Shipping Untested Ruby Code with Undercover. If you’re wondering What The Heck™️ all of this is about, you may want to read the first post in a new tab.Ruby’s SimpleCov lets us track test coverage and is common when developing Ruby on Rails apps. Many code review services that rely on SimpleCov allow us to see the coverage delta or diff coverage as a percentage. I’ve been recently talking to Ruby developer friends and gathering feedback about an alternative approach, which is to turn untested code into actionable pull request warnings, because coverage as percentage is insufficient.This post will teach you how to get started with Undercover. We’ll set it up locally, so that commits are blocked until we have tests in place. Then, we’ll learn how to add a code build status check in CI services and finally, how to post automated review comments to GitHub using Pronto.It’s almost Christmas, so let’s get started and give our Ruby projects some love!Getting started with local coverage checksSetupFirst fetch the gem from RubyGems with:gem install undercoverOnce installed, the undercover command should be available to your system. However, to make it fully functional we need to set up test coverage tracking.Update your Gemfile with SimpleCov and an LCOV reporter, which is Undercover’s coverage input file format:gem &#39;simplecov&#39;gem &#39;simplecov-lcov&#39;gem &#39;simplecov-html&#39; # if you&#39;d like classic html coverage reportsAfter installing them with bundle install, we enable coverage tracking at the very top of the test_helper.rb or spec_helper.rb, before any of the application code is required.require &#39;simplecov&#39;require &#39;simplecov-lcov&#39;SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = trueSimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ SimpleCov::Formatter::LcovFormatter, SimpleCov::Formatter::HTMLFormatter,])SimpleCov.start do add_filter(/^\/spec\//) # For RSpec add_filter(/^\/test\//) # For Minitest enable_coverage(:branch) # Enables branch coverageendTo verify the above set up, we can now run specs and see that a nonempty coverage/ directory has appeared in the project root.Using the CLIWhen undercover is invoked with no arguments, the command-line interface inspects the most recent coverage report and looks for untested methods that appear in the current changeset. What are these methods exactly? These are all code blocks that have any source lines appearing in the current git index (staged) and work tree (unstaged). Let’s illustrate that with an example.Assuming no changes have been made since last commit, undercover prints out a message and exits cleanly.$ undercover✅ No reportable changesUndercover finished in 0.0742sThe fun begins when we start making changes to our code and test suite. Let’s assume we change a method, run our test suite and invoke undercover again. Now, we get a different output:Whoops! We need to add tests to the validate instance method to keep Undercover happy. If we add those and run undercover again, we’ll be back to green.Compare to branch or commitWe have analysed test coverage for the current diff, but what about bigger chunks of work? Undercover can perform the same analysis with respect to any branch or commit. This is where the -c or --compare option comes in handy.Try this set of command arguments:undercover --compare masterIt will look at the most recent coverage report and find untested methods in the entire diff between HEAD and what we’ve set in --compare. Comes in handy for various branching workflows!AutomateWe now have what we need to create the beginning of an automated feedback loop. Let’s get started with git hooks.What are they? Git hooks are customisable scripts that live in .git/hooks of every repository and are executed before or after git events. They can be edited manually, but I suggest to start with a helper gem called overcommit, which does an excellent job managing community standard git hooks and custom ones.Follow Overcommit’s readme to install it and place the contents of this suggested config in .overcommit.yml. An starter config should have been created during installation.PrePush: Undercover: enabled: true required: true command: [&#39;undercover&#39;, &#39;--compare&#39;, &#39;master&#39;]This is my config of choice that ensures all tests are in place before code is pushed (PrePush). You might want to change this to suite your preferences and perhaps use a PreCommit variant instead. It’s also good to know that hooks can be disabled in an emergency by setting OVERCOMMIT_DISABLE=1 or using the --no-verify option in commands like commit or push.Set up Continuous IntegrationThe next steps will cover adding Undercover to analyse commits project-wide on the CI server, so that we close the feedback loop and make it part of the code review process.Step 1: Build status checkWe’ll start with a status check for test coverage that runs tests and then Undercover. I have prepared a few starter configs for a few popular CI services: Travis CI, CircleCI, Semaphore and Codeship. They are also stored in the repo in case you need them later.GitHub ActionsThe sample workflow config was taken straight from the undercover repository. It fails the build based on coverage results and failures can be inspected within the output log. This is not ideal, but works just fine.# .github/workflows/ruby.ymlname: Rails Unit Testson: [push, pull_request]jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Ruby 3.2 uses: ruby/setup-ruby@v1 with: ruby-version: 3.2 - name: Build and test with Rake env: RAILS_ENV: test run: | # other pre-requisite setup steps... gem install bundler undercover bundle install --jobs 4 --retry 3 bundle exec rake test undercover --compare origin/masterCircleCISample single-job config for CircleCI that runs specs and validates coverage with Undercover.# .circleci/config.ymlversion: 2jobs: build: docker: - image: circleci/ruby:2.5-browsers steps: - checkout - run: name: Install dependencies command: | sudo apt-get install cmake bundle install - run: name: Run RSpec command: bundle exec rspec - run: name: Check coverage command: | gem install undercover undercover --compare origin/masterWe can go even further and create a separate job for analysing coverage, leveraging features of CircleCI 2.0. That is useful, because we can benefit from workflows and build conditions, but requires a bit more work. Having that in mind, there is another more advanced starter config available on GitHub, where the LCOV report file is shared between multiple CircleCI jobs to create individual build stages for test and coverage.SemaphoreSemaphore pipeline example with a single job running rspec and undercover:version: v1.0name: RSpec + Undercoveragent: machine: type: e1-standard-2 os_image: ubuntu1804blocks: - name: &quot;RSpec&quot; task: jobs: - name: Run Specs and Check Coverage commands: - checkout - bundle install - bundle exec rspec - gem install undercover - undercover --compare origin/masterCodeshipTo run specs and analyse coverage in Codeship, go to Project Settings and place the following instructions in the Setup Commands section:rvm use 2.5.3 --installbundle installgem install undercoverThen, run specs and undercover as part of the Test Pipeline:bundle exec rspec --format documentation --color# pull origin/master to have a ref to compare againstgit remote set-branches --add origin mastergit fetchundercover --compare origin/masterOne caveat is that Codeship uses a shallow git clone, so we need to specifically add the master branch (or any other branch we want to compare with) to the cloned repository. This way we can use undercover --compare origin/master.Merging test results from multiple test runnersAnother common caveat that applies to most CI services supporting parallel execution is about partial test results. Undercover does not currently support partial coverage reports, so we need to merge them into one before running the undercover command. If this applies to you, this gist might be a good starting point.Step 2: Automated GitHub commentsWe have already closed the feedback loop with the build status, but that’s not all we can do. How about not having to inspect the build output every time untested code is detected? We can create automated pull request comments thanks to Pronto and its integration with Undercover!Pronto is an open-source automated code review framework that integrates with multiple tools (Rubocop, Reek, Brakeman included) and offers integrations with SCMs like GitHub, GitLab and Bitbucket.That’s how automated code review looks like at RainforestTo start setting up review comments, we need a GitHub API OAuth token. It’s up to you to create a new bot user account or use a personal one. Either way, please sign in to GitHub and go to Settings → Developer Settings → Personal Access Token. Then click on “Generate New Token” illustrated below.Let’s create a token with repo or public_repo privilege scope and make it available to the CI environment. From now on we’ll use Codeship as an example, but these instructions should require minimal changes no matter which CI you are using.To set build environment variables in Codeship, visit Project Settings → Environment and specify each new variable as a key value pair. Please note that Pronto expects the variable holding our access token to be named PRONTO_GITHUB_ACCESS_TOKEN.Next, we need update the setup instructions to install pronto and pronto-undercover gems instead of just undercover. This is how the Setup Commands section should look like:# Setup Commandsrvm use 3.2.0 --installbundle installgem install prontogem install pronto-undercoverThen, modify the Test Pipeline to run tests and then analyse coverage with pronto. We will use the github_pr formatter option to enable the GitHub integration.# Test Pipelinebundle exec rspec --format documentation --color# pull origin/master to have a ref to compare againstgit remote set-branches --add origin mastergit fetchpronto run -f github_pr -c origin/masterNow, when we save our changes, re-run the build and Pronto triggers any coverage warning, it will create comments in GitHub instead of failing the CI build. Yas!SummaryAfter reading this post you should know how to:Set up the undercover gem to run locally, so that untested code changes do not escape the development environmentConfigure your CI to do the same thing, but across all commits and remote branchesCreate an automated code coverage feedback loop with Pronto and code review comments that appear directly in a GitHub pull request diff.I hope Undercover will help you deliver better and well tested code with more confidence! If you have any feedback or run into issues with any of the presented setups, please leave comments below, create a GitHub issue or get in touch with me.Happy Holidays!And don’t forget to leave a ⭐️ at https://github.com/grodowski/undercover. Also make sure to check out the Undercover GitHub App, which is free to use for public repos: https://undercover-ci.com.]]></summary></entry><entry><title type="html">Stop shipping untested Ruby code with undercover</title><link href="/2018/07/31/stop-shipping-untested-ruby-code-with-undercover.html" rel="alternate" type="text/html" title="Stop shipping untested Ruby code with undercover" /><published>2018-07-31T00:00:00+00:00</published><updated>2018-07-31T00:00:00+00:00</updated><id>/2018/07/31/stop-shipping-untested-ruby-code-with-undercover</id><content type="html" xml:base="/2018/07/31/stop-shipping-untested-ruby-code-with-undercover.html"><![CDATA[<p>Ruby is a productive and fun to work with environment with lots of useful tools and products for managing software quality. Today, I am excited to share one of my recent ideas in that space: <strong>Undercover, </strong>a gem that stops us developers from shipping untested code in CI and in dev: <a href="https://github.com/grodowski/undercover">https://github.com/grodowski/undercover</a>.</p><p>Undercover inspects changed files and creates warnings for blocks of code which have not been tested, similar to a linter. It’s a simple idea I found useful in a large codebase along with existing coverage reporting tools and I hope you’ll see the value as well!</p><h3>Testing large apps</h3><p>Dealing with large inherited (legacy?) codebases carries a risk of breakage. That’s why we write tests in the first place. Every programmer is probably guilty of a bug or service outage caused by that tiny change, because it is so easy to miss a side-effect or edge case especially when we are new to a project.</p><figure><img alt="" src="/assets/images/posts/eTnfRy1UNC0-0_Ag-NJ8Ng.gif" /><figcaption>But the tests, they were green!</figcaption></figure><p>The test suite in place may be subpar. There is no guarantee that the assumptions it makes are correct. Worst case it might not exist or be incomplete.</p><p>I have worked with such projects and used to be an advocate of always aiming for full test coverage. However, reality verified that quickly. Testing code created months or years ago by another person (who might have already left the company 😱) results in brittle tests that usually fail to capture the complexity or all edge scenarios. In other cases, it ends up becoming an endless effort of reverse engineering actual production code that works just fine for its purpose.</p><p>So is 100% test coverage actually a goal worth hitting? No, it’s usually not! I’d like to propose a different approach to measuring and improving coverage for large apps, so that we can live peacefully with legacy code and spend more time working on meaningful features.</p><h3><strong>Write tests at the right moment</strong></h3><p>Undercover is a code review utility that takes the ideas from code quality tools like <a href="https://github.com/rubocop-hq/rubocop">RuboCop</a> or <a href="https://github.com/prontolabs">Pronto</a> and applies them to coverage. If 100% test coverage is not what we want to invest our time in, how about making sure that just the code in scope of our changes is tested well?</p><p>Undercover does that and triggers warnings on untested parts of our code based on a changeset from git and a set of rules. Take a look at a sample output below, where undercover checks a recent change in undercover 🙀.</p><figure><img alt="" src="/assets/images/posts/N_fp_otyETRHNOGfVocmCA.png" /><figcaption>undercover warns on a diff from the undercover repo: some recently changed methods were not covered in the latest rspec run!</figcaption></figure><p>Those warnings are a complementary input to code coverage as percentage or percentage delta in case of pull requests. Why? Firstly because timing matters: Undercover’s goal is to turn code review comments like this…</p><figure><img alt="" src="/assets/images/posts/rWBjfuswxpfmXOqjEWHh3g.png" /><figcaption>Rails pull request <a href="https://github.com/rails/rails/pull/18213#issuecomment-68156588">review comment</a> by <a href="https://medium.com/u/38fe29eb5d42">Sean Griffin</a>, saying “This needs a test case.”</figcaption></figure><p>…into automated feedback that should be addressed before actual code review (involving human) starts. It doesn’t care<strong> </strong>whether you write tests before or after the implementation, but makes sure they are in place before code is merged.</p><p>Additionally, it is less prone to false green results. A false green result is a message like “your code coverage has improved by 1%”, when it’s sometimes enough to add some new well-tested code to skew the result and sneak in some untested methods. This approach is also insensitive to code deletions, which may cause fluctuations in code coverage measured as percentage.</p><h3><strong>How it’s made</strong></h3><p>Undercover fuses data from a git repository (rugged), a coverage report (simplecov) and a parsed representation of your Ruby code.</p><p>Each line taken from a git diff may trigger a warning if it has been added or modified. Then, the selected candidate source lines are matched against the latest coverage report to check if they have been hit at least once in tests. Finally, warnings are generated for respective code blocks (methods, classes, modules and blocks) that encompass untested source lines.</p><h3><strong>There’s more to come </strong>🏗</h3><p>Undercover is still an experiment in its early days. However, we are using it in the CI/CD pipeline at <a href="https://medium.com/u/4d3e94ab63a1">Rainforest QA</a> and it has already given some useful feedback on our pull requests! Now it’s time to explore where to take it next and these are some ideas I would like to work on.</p><h4>Better feedback loop</h4><p>A good workflow integration is a must have and can be achieved through a Pronto plugin as well as a Code Climate engine. Both will have the capability to provide users with faster feedback from CI and are on my list. The pronto runner is already in the works!</p><h4>Run modes</h4><p>Undercover warnings are currently derived from <strong>changed</strong> <strong>source lines</strong>. But what if the line below is still untested and is waiting to cause an error? Or even worse, what if the total coverage of the file I just modified is really poor? My idea for that is called “Run Modes”. They expand the range of candidate source lines to all adequate locations to minimise the error inherent in coverage measurements. Two new modes would report on <strong>changed code blocks </strong>or <strong>changed files </strong>instead of just source lines, hence fostering proactive coverage improvements for methods/blocks/files we touch in our changes.</p><h4><strong>Coverage data quality</strong></h4><p>Branch coverage is a new addition to Ruby available in 2.5+ or through the <a href="https://github.com/deep-cover/deep-cover">DeepCover</a> gem, while the <a href="https://github.com/ioquatix/covered">covered</a> gem can provide coverage warnings for evaluated code like view templates. Incorporating them into the gem would help detect brittle tests and reduce false greens. You might also want to check out this <a href="https://bugs.ruby-lang.org/issues/13901">Ruby core issue</a> with an interesting discussion on where branch coverage is heading.</p><p>I’d love to hear your thoughts about Undercover’s approach to measuring code coverage. Does it fit into your current testing strategy? <strong>If you like the idea, go visit </strong><a href="https://github.com/grodowski/undercover"><strong>undercover on GitHub</strong></a><strong>, press the star button ⭐️ and try it out for your Ruby project. Happy testing!</strong></p><hr><p><a href="/2018/07/31/stop-shipping-untested-ruby-code-with-undercover.html">Stop shipping untested Ruby code with undercover</a> was originally published in <a href="https://medium.com/futuredev">[:] futureDev</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content><author><name>Jan Grodowski</name><email>mrgrodo@hey.com</email></author><category term="ruby" /><category term="code-quality" /><category term="software-development" /><category term="software-testing" /><category term="developer-tools" /><summary type="html"><![CDATA[Ruby is a productive and fun to work with environment with lots of useful tools and products for managing software quality. Today, I am excited to share one of my recent ideas in that space: Undercover, a gem that stops us developers from shipping untested code in CI and in dev: https://github.com/grodowski/undercover.Undercover inspects changed files and creates warnings for blocks of code which have not been tested, similar to a linter. It’s a simple idea I found useful in a large codebase along with existing coverage reporting tools and I hope you’ll see the value as well!Testing large appsDealing with large inherited (legacy?) codebases carries a risk of breakage. That’s why we write tests in the first place. Every programmer is probably guilty of a bug or service outage caused by that tiny change, because it is so easy to miss a side-effect or edge case especially when we are new to a project.But the tests, they were green!The test suite in place may be subpar. There is no guarantee that the assumptions it makes are correct. Worst case it might not exist or be incomplete.I have worked with such projects and used to be an advocate of always aiming for full test coverage. However, reality verified that quickly. Testing code created months or years ago by another person (who might have already left the company 😱) results in brittle tests that usually fail to capture the complexity or all edge scenarios. In other cases, it ends up becoming an endless effort of reverse engineering actual production code that works just fine for its purpose.So is 100% test coverage actually a goal worth hitting? No, it’s usually not! I’d like to propose a different approach to measuring and improving coverage for large apps, so that we can live peacefully with legacy code and spend more time working on meaningful features.Write tests at the right momentUndercover is a code review utility that takes the ideas from code quality tools like RuboCop or Pronto and applies them to coverage. If 100% test coverage is not what we want to invest our time in, how about making sure that just the code in scope of our changes is tested well?Undercover does that and triggers warnings on untested parts of our code based on a changeset from git and a set of rules. Take a look at a sample output below, where undercover checks a recent change in undercover 🙀.undercover warns on a diff from the undercover repo: some recently changed methods were not covered in the latest rspec run!Those warnings are a complementary input to code coverage as percentage or percentage delta in case of pull requests. Why? Firstly because timing matters: Undercover’s goal is to turn code review comments like this…Rails pull request review comment by Sean Griffin, saying “This needs a test case.”…into automated feedback that should be addressed before actual code review (involving human) starts. It doesn’t care whether you write tests before or after the implementation, but makes sure they are in place before code is merged.Additionally, it is less prone to false green results. A false green result is a message like “your code coverage has improved by 1%”, when it’s sometimes enough to add some new well-tested code to skew the result and sneak in some untested methods. This approach is also insensitive to code deletions, which may cause fluctuations in code coverage measured as percentage.How it’s madeUndercover fuses data from a git repository (rugged), a coverage report (simplecov) and a parsed representation of your Ruby code.Each line taken from a git diff may trigger a warning if it has been added or modified. Then, the selected candidate source lines are matched against the latest coverage report to check if they have been hit at least once in tests. Finally, warnings are generated for respective code blocks (methods, classes, modules and blocks) that encompass untested source lines.There’s more to come 🏗Undercover is still an experiment in its early days. However, we are using it in the CI/CD pipeline at Rainforest QA and it has already given some useful feedback on our pull requests! Now it’s time to explore where to take it next and these are some ideas I would like to work on.Better feedback loopA good workflow integration is a must have and can be achieved through a Pronto plugin as well as a Code Climate engine. Both will have the capability to provide users with faster feedback from CI and are on my list. The pronto runner is already in the works!Run modesUndercover warnings are currently derived from changed source lines. But what if the line below is still untested and is waiting to cause an error? Or even worse, what if the total coverage of the file I just modified is really poor? My idea for that is called “Run Modes”. They expand the range of candidate source lines to all adequate locations to minimise the error inherent in coverage measurements. Two new modes would report on changed code blocks or changed files instead of just source lines, hence fostering proactive coverage improvements for methods/blocks/files we touch in our changes.Coverage data qualityBranch coverage is a new addition to Ruby available in 2.5+ or through the DeepCover gem, while the covered gem can provide coverage warnings for evaluated code like view templates. Incorporating them into the gem would help detect brittle tests and reduce false greens. You might also want to check out this Ruby core issue with an interesting discussion on where branch coverage is heading.I’d love to hear your thoughts about Undercover’s approach to measuring code coverage. Does it fit into your current testing strategy? If you like the idea, go visit undercover on GitHub, press the star button ⭐️ and try it out for your Ruby project. Happy testing!Stop shipping untested Ruby code with undercover was originally published in [:] futureDev on Medium, where people are continuing the conversation by highlighting and responding to this story.]]></summary></entry></feed>