Tests as a First Open Source Contribution

Leonid Bugaev
|
(Updated September 1, 2026)
|
6 min read
testing beginners contributing good-first-issue

This URL is pick a test issue as your first merge: a ticket labeled tests, coverage, missing-test, or good first issue that asks for a case, not a rewrite of the suite.

It is not how to discover the test runner in an unknown repo. That is how to run tests in an unfamiliar repo. It is not how to land a specified non-test issue without reading the whole architecture. That is contribute without knowing the codebase. It is not a11y. It is not writing the issue.

A missing assertion is a real first contribution. Maintainers merge a table case that names the bug. They close a drive-by refactor of every spec file.

What the ticket has to say

The issue already names the hole.

That is the PR. One behavior, one file if you can, one new case.

Walk if the ticket is tests need love, increase coverage, or rewrite the suite. That is a wishlist. Walk if they want a new framework. Walk if they want you to delete flaky tests without a replacement.

Labels that often mean this job: tests, testing, coverage, good first issue plus a sentence about a missing case. Read the body. A good first issue that says fix the crash is a product patch with tests, not this page.

Find those issues on Help Wanted, including Python, TypeScript, and JavaScript. Filter for test-shaped labels, then read the issue before you claim it. What is a good first issue and how to pick an issue that will get merged still apply. How to claim a GitHub issue is the comment, not the patch.

What a small test PR looks like

Open the existing test file next to the production code, or the file the issue names. Copy the shape of a nearby case: same helpers, same fixture style, same assertion library. Do not introduce a new runner.

Name the test after the behavior, not after yourself. parseConfig returns error on empty file is a title. my first test is not.

If they use table tests, add a row. If they use one function per case, add one function. If they use fixtures on disk, add one fixture and the case that reads it. Do not reformat the file. Do not rename unrelated cases.

The production change, if any, is the minimum to make the new case true. Prefer no production change when the issue is only coverage. If you must change production code to make the behavior testable, keep it in the same PR only when the issue asked for that. Otherwise open a question on the ticket.

Do not add a linter config, a coverage badge, or a CI workflow. That is a different job.

Bound the read

You need the function under test, the existing tests, and the issue. You do not need the whole service graph.

Read the function. Read two neighboring tests. Copy the import list they already use. If you cannot see how they construct a fake clock or a fake HTTP client, search the test directory for that helper name. Stop when you can write the case.

If the case needs a private helper, do not make it public for the test unless the project already tests through that surface. Prefer the public API the other tests use.

Run only what proves the case

How to find the command is the sibling page. Once you have it, run the single file or the name filter for your new case, before and after. Put that command in the PR description with Closes and the issue number.

If the new case is red because the production code is wrong, that is a bug ticket, not a coverage ticket. Comment on the issue with the failure. Do not silently ship a production fix the maintainer did not ask for, unless the issue said add a test and fix the bug.

Review comments you should expect

Use our helper, not a one-off mock. Match the style of test_foo, not a new pattern. This case duplicates test_bar; extend that table instead. We do not assert on log lines. Those are normal. They are cheaper to fix than a 20-file suite rewrite.

If they ask you to drop the case, drop it. A first merge is a specified test, not a debate about coverage philosophy.

CTA

Open Help Wanted and look for test-shaped good first issues. Language hubs: Python, TypeScript, JavaScript. Then claim, add the case, run the small suite.

FAQ

Is adding tests a real first contribution?

Yes, when the issue asked for that case. It is not real when you spam coverage on files nobody named.

Can I add tests and also clean up the module?

No. The PR is the case. Style and refactors belong in a ticket they opened, or not at all.

The issue says good first issue and increase coverage. Is that enough?

No. You need a named function, file, or behavior. Ask which case they want. If nobody answers, pick another issue.

Do I need to understand the whole codebase?

No. That question is contribute without knowing the codebase. For this page you need the function under test and the existing test style.

Should I start a new test framework because I know it better?

No. Use theirs.

What if I cannot run the suite?

Stop and follow how to run tests in an unfamiliar repo. Do not open a test PR you never executed.

Good vs bad

Same first-timer, generic parser, issue asked for empty-input coverage.

Bad. New runner config, reformatted every test file, three production refactors, title first contribution.

Good. One table row in parse_config_test, name empty file returns error, command run on that package, Closes in the body. Boring on purpose.

Back to all articles Find projects that need your help