<![CDATA[why not make it?]]>https://whynotmake.it/https://whynotmake.it/favicon.pngwhy not make it?https://whynotmake.it/Ghost 5.75Fri, 20 Mar 2026 02:43:40 GMT60<![CDATA[You too can Enjoy Writing Tests for Your Apps!]]>https://whynotmake.it/you-too-can-enjoy-testing/6630092ef631ae0001aec8acMon, 29 Apr 2024 20:59:32 GMT
After reading this...

💡 you might gain a new perspective on unit testing
⏱️ you will know not just how, but when to write unit tests
You too can Enjoy Writing Tests for Your Apps!

Spend any amount of time with software developers online or offline, and at some point someone is likely going to start talking about how important it is to write tests. You will hear people talk about how it is important to reach metrics like a 100% test coverage and you might even start to feel a bit inadequate for how you write code; Did you just write a function without first writing a bunch of failing unit tests for it? A cardinal sin if you listen to Test-Driven-Development missionaries.

Strategies like enforcing test coverage or Test-Driven-Development can work well for larger teams with sufficient time and financial budgets, but they fall apart in the fast-moving environment of smaller, more dynamic projects. When it comes to the teams that I have worked with in my career so far, I have seen very few people actually write tests for their code and even fewer seemed to have a real strategy for when to write them. I can only speculate about how many single developers write tests for their apps, but I suspect the number is shockingly low.

I learned how to write tests way too late in my life, but when I did, I felt how they gave me a sense of security and made me sleep better, how they made me think about my software's architecture in a new way and how they helped me write more maintainable code. All this, however, still didn't get me to test regularly.

That is because, to write tests reliably and reap all their benefits, developers don't just need to know how to test, but also when to test.

You hear that you should reach a certain test coverage, or write tests before every bit of code that you write, but when you realise that this approach is not feasible for your project, you are suddenly back in the wild west, where you don't have any rules or structure for what to test and when to do it. And in the absence of clear guidelines, people often revert to the easiest solution, which is writing no tests at all. Luckily, I found a simple shift in perspective could help me make sense of it all. Not only are you going to find yourself writing more tests, through small, cumulative change you are going to end up with a more robust codebase. I am even going to go out there and say that you are going to have some fun while doing it.

Use Tests to Try Out Your Code.

Don't be fooled by how obvious this sounds. During the same years I resisted writing tests because it felt too cumbersome, I still put dummy buttons in my UI to call functions with dummy parameters, just to see if they do what they should all. the. time.

Let's think about this for a second, because I bet most of you have had this experience. You just wrote some nice business logic, maybe an API call or two somewhere, and of course you want to try out if it works. But you don't have any way to interact with this code yet, so to verify that it does its thing, you would need to wait until everything is hooked up to UI. Lots of times, this is what I did, and by that time I had forgotten half of the cases I wanted to test, and if something went wrong it there were already ten different places it could've happened in.

To mitigate that, I often did what many developers do. I just hooked up my code to some random button, to call it with some made up dummy arguments. A couple prints here, a breakpoint there, just to verify the output was what I wanted, and I was satisfied. Time to undo everything again, and hope that I don't forget to clean up behind me.

What I described just now is basically writing a test, but I'm the manual test runner! Writing a unit test would've accomplished the same, but repeatable, automatic, and future-proof. This gives rise to a simple strategy.

💡
Write a test every time you feel the urge to try out some code you wrote.

This also includes times when you receive a bug report and you're trying to reproduce the error. If you manage to write a failing test, you'll make your life so much easier And believe me, if you're scared that writing the test would've been more effort, a little bit of learning will take you a huge way

I've seen people try out their business logic by hand so much, that I believe it is a deeply engrained habit for many, if not most developers like it was for me. And who can blame us? By the time I finished my Bachelor's, I had written multiple apps for uni. Someone had to test all of this code? Nobody told me I could let the computer do it for me, much less how to do it. But once this framework clicked for me, I started enjoying the satisfaction so much, that some of my side project even sport the mythical 100% coverage now. And I have to admit, that does feel pretty great, so maybe they weren't all wrong after all.


Of course, the tests you wrote are only going to unfold their full potential once you have a set-up that runs them against your codebase regularly. In other words, you will want at least a simple DevOps setup. If you don't have that yet, and you're curious about how to set it up for GitHub, consider checking out our Flutter CI Tour.

]]>
<![CDATA[Your One-Stop-Shop for Flutter CI on GitHub]]>https://whynotmake.it/flutter-ci-tour/65022c373703180001e939b8Wed, 13 Sep 2023 21:40:31 GMT
After reading this...

💡 you will know how to create a basic CI setup
🔰 you can add coverage badges and reports to your repo automatically
Your One-Stop-Shop for Flutter CI on GitHub

In today's world of software development, Continuous Integration (CI) is an essential part of the development process for most fancy-pants software development teams. However, many hobbyists and small Flutter shops still don't enjoy even the most basic benefits of a CI setup, maybe because they feel overwhelmed, or don't really know where to start. If you don't use CI on even your most niche side projects, I promise you, there is a better way. We'll provide you with a CI starter kit (batteries included) and introduce you to a free tool we built called Dart Coverage Assistant that you will (learn to) love! Keep reading, and within 10 minutes, you can have a GitHub workflow running that

  • 🧪 Tests your code
  • 🔎 Checks your formatting
  • 📝 Reports code coverage on your Pull Requests
  • 🔰 Creates coverage badges for your READMEs
  • 😴 Improves your sleep through peace of mind (citation needed)
🤔
If your first thought is "Why would I need coverage, I don't even have any tests in my codebase?", this article might not be for you. But please hang tight, we have another post coming that you will find interesting!

If all of this sounds attractive to you, let's dive in!

Flutter CI Starter Pack

If you are starting from zero, there are a few things your CI workflow should do for you. I know you're smart, but why not let the computer do some of the dumb stuff for you, so you can build and ship more?

The most basic CI workflow should accomplish a few things for you:

  • Check your code for compile-time errors
  • Run your unit tests (even if you don't have any yet)

For a Flutter Repository, this can quickly be achieved by adding the following file:

name: Continuous Integration

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  flutter-check:
    name: Build Check
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: 📚 Checkout
        uses: actions/checkout@v4

      - name: 🐦 Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          sdk: 'stable'
          
      - name: 🔎 Check for compile-time errors
        run: dart analyze .
      
      - name: 🧪 Run Tests
        run: flutter test --coverage

The absolute most basic workflow

This should be good-to-go out of the box for most repositories, but a few things are notable. If you take a look at the on section, the workflow will run on every Pull Request, and every push to the main branch. Using the Pull Request runs is a great way to make sure everything still works before you push to main. And the concurrency section will make sure that subsequent pushes cancel ongoing runs and save you a bunch of action minutes.

The next building block - Fancy Coverage Badges

After using your CI for a while, you might desire a bit more, and maybe you want to show off your progress and have some fancy-looking badges in your PRs and READMEs, to show off your 2% test coverage (🥲). We built a GitHub Action called Dart Coverage Assistant, that adds coverage reports to your repository without any extra setup.

GitHub - whynotmake-it/dart-coverage-assistant: Magically generates code coverage reports from your dart projects
Magically generates code coverage reports from your dart projects - whynotmake-it/dart-coverage-assistant
Your One-Stop-Shop for Flutter CI on GitHub

Dart Coverage Assistant Repository

Our recommended Level 2 CI workflow looks as follows:

name: Continuous Integration

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  flutter-check:
    name: Build Check
    runs-on: ubuntu-latest
    timeout-minutes: 10
    permissions:
      pull-requests: write
    steps:
      - name: 📚 Checkout
        uses: actions/checkout@v4

      - name: 🐦 Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          sdk: 'stable'
          
      - name: 🔎 Check for compile-time errors and warnings
        run: dart analyze . --fatal-infos
      
      - name: 🧪 Run Tests
        run: flutter test --coverage

      - name: 📊 Generate Coverage
        id: coverage-report
        uses: whynotmake-it/dart-coverage-assistant@v1
        with:
          generate_badges: pr

As you can see, not that much has actually changed from the Level 1 workflow. The main two differences, are that the third step in the workflow now also fails when any analyser infos have not been dealt with, and the aforementioned dart-coverage-assistant action has been added. This will

  • comment with a coverage report on every PR you open (it will even show you the difference in coverage that that PR makes)
  • open a PR with svg coverage badges every time your coverage changes on main
ℹ️
To enable PRs with coverage badges, you need to allow Actions to open Pull Requests in your GitHub Repository settings. If you don't want to do that, set generate_badges: none

Dart Coverage Assistant also seamlessly handles monorepos, so if you manage multiple Dart projects from one repository, you won't even have to set anything up for that to work, as long as you ran your tests with --coverage in all of the projects. And if you want to enforce a certain minimum coverage, or disallow PRs that decrease the coverage, that's possible too.

Your One-Stop-Shop for Flutter CI on GitHub
An example coverage Report generated by Dart Coverage Assistant

Bonus Level – Check Code Generation

Many Dart and Flutter projects rely on Code Generation through packages like freezed, json_annotation, and many more. This houses the danger that you push a change to a class, but forget to run the code generation, so it's associated generated files weren't updated. In some cases, this will be detected already, since it can cause compile-time errors, however, it makes a lot of sense to check it separately.

Thus, if your project uses code generation, add the following code under the Level 2 workflow:

   check_generation:
    name: Check Code Generation
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - name: 📚 Checkout
        uses: actions/checkout@v4

      - name: 🎯 Setup Dart
        uses: dart-lang/[email protected]
        with:
          sdk: "stable"

      - name: 🔨 Generate
        run: dart run build_runner build --delete-conflicting-outputs

      - name: 🔎 Check there are no uncommitted changes
        run: git diff --exit-code

This job will now fail if you committed something where the generated files are in a different state than they should be.

]]>