Sam Na writes practical guides on AI-assisted productivity, reliable automation, and testing workflows that make digital systems easier to maintain. RoutineOS focuses on turning AI capabilities into useful processes that readers can understand, run, and verify.
You can generate unit tests with AI in minutes, but a file full of passing tests does not automatically mean your code is reliable. ChatGPT, Claude, Gemini, and GitHub Copilot can help you build pytest and Jest test cases for ordinary inputs, empty data, invalid values, and unexpected conditions. The important part is making sure those tests check the behavior your program actually needs, rather than simply repeating what the existing code already does.
Imagine using AI to build a small automation that cleans up your task list every morning. It works when you try three sample tasks, so you connect it to your regular workflow. A few days later, an imported task has no title, another contains unexpected spacing, and a third has a missing field. Your automation produces incomplete results even though the original demonstration looked perfect.
You could ask an AI assistant to repair each problem as it appears. A better approach is to test the rules before those unusual inputs reach your daily routine. A unit test checks one small piece of program behavior against a result you have already defined. It can tell you whether a function returns the expected value, raises the right error, or calls a dependency with the correct information.
An effective AI unit test generator should produce test cases that challenge your code. A valuable test is not the one that merely passes today; it is the one that would reveal a meaningful mistake if the implementation changed.
AI is useful here because it can suggest scenarios you might overlook, translate plain-language requirements into test functions, and adapt tests to a framework you already use. The limitation is that an assistant may also invent requirements, write assertions that are too weak, or generate tests that cannot run in your project. Those mistakes can create false confidence unless you inspect the results.
This guide uses two practical tools: pytest for Python and Jest for JavaScript. We will create small, independent examples, write prompts that ask for meaningful tests, run the generated code, and examine the quality of the assertions. You can apply the same method whether your code comes from an AI assistant, an online tutorial, or your own work.
A test should describe what correct behavior looks like, not merely confirm that the current implementation behaves consistently.
Define what your tests should prove
The most common weakness in AI-generated unit tests appears before the first line of test code. You give an assistant a function and ask it to produce tests without explaining what that function is supposed to accomplish. The assistant reads the implementation and often assumes that its current behavior is correct.
That approach can preserve a bug instead of exposing it. Suppose a function accidentally treats a blank task title as valid. An assistant that uses the function itself as the specification may generate a test that expects the blank title to be accepted. The test passes, but it protects the wrong rule.
Start with behavior, not the current output
Before asking AI to generate tests, write down the function's purpose in ordinary language. Identify the inputs it accepts, the output it should return, and the situations in which it should refuse to continue. These statements form a behavior contract: a short description of what the function promises to do.
Consider a function that cleans task titles imported from a spreadsheet. You want it to accept text, remove extra spacing, and return a readable title. A title containing only whitespace should be rejected. A missing value or a number should also be rejected because those values are not valid titles in this example.
These rules are easier to test than a vague instruction such as "make sure the task cleaner works." Each rule can become an independent test case with an input and a clear expected outcome.
A valid title such as "Plan weekly review" should retain its words and return the same text.
Text containing repeated spaces or line breaks should become a title with single spaces between words.
An empty string or a string containing only whitespace should produce a clear validation error.
Values such as None, an integer, or a list should not be silently converted into task titles.
Separate happy paths from edge cases
The happy path is the ordinary situation in which a user supplies valid input and receives the intended result. For the task-title function, the happy path is a normal string with readable words. It is the simplest place to start, but testing only that case leaves several important assumptions untouched.
Edge cases describe valid or invalid inputs near the limits of the function's behavior. These might include an empty string, a very long title, repeated spaces, different character sets, or a missing value. You do not need to test every imaginable string. Focus on conditions that could realistically appear in the data your automation processes.
Think about where the information comes from. A text field might return an empty string. A spreadsheet import might contain blank cells. An API might omit a field. A pasted title might contain tabs or line breaks. Each source introduces different risks, and the tests should reflect those risks.
Give each test one meaningful reason to exist
A test is more useful when its name explains the behavior it checks. Compare "test_function_3" with "test_rejects_blank_task_title." The second name immediately tells you what failed if the test turns red. It also helps someone reviewing your code understand the requirement without opening a long conversation with an AI assistant.
Avoid generating dozens of cases that all exercise the same branch with nearly identical inputs. Ten tests using different ordinary task titles may provide less protection than four carefully chosen tests covering a normal title, unusual whitespace, blank input, and an invalid type.
A useful way to judge a proposed test is to ask: what realistic mistake would cause this test to fail? If you cannot name one, the test may add more maintenance work than protection.
Define the intended behavior before generating test code. Include ordinary inputs, realistic boundaries, and invalid values, then make every test responsible for a specific rule.
Prepare your project and write better AI test prompts
Once you know what the code should do, give your AI assistant enough context to produce tests that fit your actual project. A test generated for the wrong language version, module format, or framework may look reasonable but fail before it reaches the function you want to check.
You can use ChatGPT, Claude, Gemini, or GitHub Copilot to draft unit tests. The general approach is similar across these tools: provide the relevant code, specify the testing framework, describe expected behavior, and ask for cases that challenge the implementation. Where an IDE assistant can see the current file, confirm that it has the correct context rather than assuming it inspected your entire project.
Prepare a clean testing environment
Keep application code and test code in separate files. This allows you to review the tests without mixing them into the function being tested. It also makes it easier to run the suite repeatedly whenever the implementation changes.
For Python, pytest can discover test functions in appropriately named test files and evaluate ordinary assert statements. The official pytest getting-started guide provides installation and first-test instructions. A virtual environment is a practical way to keep testing dependencies separate from other Python projects.
python -m venv .venv
# Activate the environment for your shell,
# then install pytest:
python -m pip install -U pytest
# Verify that pytest is available:
python -m pytest --version
For JavaScript, Jest is commonly installed as a development dependency in an existing Node.js project. The official Jest getting-started guide covers installation, test files, and the commands used to run them. The examples in this article use CommonJS modules to avoid requiring additional module or transpilation configuration.
# Run inside an existing Node.js project:
npm install --save-dev jest
# Run Jest directly:
npx jest --runInBand
If you are starting an entirely new JavaScript folder, initialize the project with npm before installing packages. If your application uses ECMAScript modules, TypeScript, or a framework with its own test configuration, tell the AI about that environment and follow the framework's supported setup. Do not copy a CommonJS example into an incompatible module configuration without adapting it.
Give AI the code, the rules, and the framework
A weak request is "write tests for this code." It leaves the assistant to infer the contract, choose its own framework, and decide how much of the project to change. You may receive a long suite containing incorrect import paths, invented dependencies, or assumptions that were never part of your requirements.
A stronger request limits the scope. Specify pytest or Jest, identify the function and file name, describe the accepted inputs, and provide the failure conditions. Ask the assistant not to modify the production function while generating its first set of tests. That separation makes the resulting test suite easier to evaluate independently.
Use AI suggestions to expand coverage, not invent requirements
After generating the first suite, ask the assistant what conditions are still missing. This second pass is often more valuable than asking for a larger batch of tests immediately. The model can compare the written requirements with the existing test cases and suggest gaps without rewriting everything.
Review the suggestions before converting them into executable tests. For example, AI might assume task titles have a maximum length, cannot contain emoji, or must be written in English. Those restrictions may sound reasonable, but they are not part of our example's contract. Adding tests for unsupported assumptions would change the product's intended behavior.
Do not give an AI assistant real passwords, private customer records, production API credentials, or unnecessary personal information merely to generate unit tests. Use synthetic test inputs and sanitized code.
Good AI-generated tests begin with correct project context and a written behavior contract. Specify the framework, preserve the source code, and reject test cases based on invented product rules.
Generate Python unit tests with pytest
Python is a practical language for personal automations because it can process files, manipulate text, organize data, and communicate with other services. Those everyday tasks often depend on input quality. A function may work with the author's original sample but behave differently when a spreadsheet cell is blank or a file contains an unexpected value.
Our first example is a deliberately small task-title cleaner. Keeping the production function simple makes it easier to see which behaviors the generated tests cover and which assumptions remain untested.
Create a small function with a clear contract
Save the following function in a file named
task_labels.py.
It accepts a string, collapses repeated whitespace into single
spaces, and returns the cleaned title. A non-string input produces
TypeError, while a string that becomes empty after whitespace removal
produces ValueError.
# task_labels.py
def clean_task_label(raw):
if not isinstance(raw, str):
raise TypeError("Task label must be text")
label = " ".join(raw.split())
if not label:
raise ValueError("Task label cannot be blank")
return label
Notice that this function does not remove punctuation, change capitalization, or impose a title-length limit. Those behaviors were never requested. The tests should therefore focus on the rules that actually exist: valid text, normalized spacing, blank inputs, and unsupported types.
If an AI assistant proposes additional cases involving forbidden punctuation or automatic capitalization, you can discuss those as possible requirements. Do not add them as assertions until the application's intended behavior has been decided.
Generate parameterized tests for valid inputs
One normal input cannot represent every useful variation. You also want to know whether the function handles leading spaces, repeated spaces, and embedded line breaks. These cases share the same basic expectation: return the normalized title.
pytest provides @pytest.mark.parametrize,
which lets one test function run with several separate input and
expected-output pairs. Its behavior is described in the
official pytest parametrization documentation.
This is especially useful when AI produces many related examples
that should follow the same assertion.
# test_task_labels.py
import pytest
from task_labels import clean_task_label
@pytest.mark.parametrize(
"raw, expected",
[
(
"Plan weekly review",
"Plan weekly review",
),
(
" Review weekly plan ",
"Review weekly plan",
),
(
"Send\nreport",
"Send report",
),
],
)
def test_valid_task_labels(raw, expected):
assert clean_task_label(raw) == expected
Each input has an expected result written directly in the test. The function must return that result for the test to pass. If the implementation stops collapsing line breaks, for example, the third case will reveal the difference between actual and expected output.
The expected values are important. An AI-generated test that simply asserts the result is not None would be far weaker. It could pass even if the function returned the wrong title. Whenever an exact output is part of the contract, test the exact output rather than an unrelated property.
Add exception tests and run the complete file
The same function also needs tests for invalid data. An empty string, spaces, and tabs should all fail because they do not contain a usable task title. Values such as None and integers should fail for a different reason: they are not text.
pytest offers pytest.raises to verify
that a specific exception is produced. You can also check the
relevant portion of the error message. This helps distinguish
deliberate validation from an unrelated exception that happens
elsewhere in the code.
# Add these tests to test_task_labels.py
@pytest.mark.parametrize(
"raw",
["", " ", "\t\n"],
)
def test_blank_task_labels(raw):
with pytest.raises(
ValueError,
match="cannot be blank",
):
clean_task_label(raw)
@pytest.mark.parametrize(
"raw",
[None, 42, ["Plan"]],
)
def test_non_text_task_labels(raw):
with pytest.raises(
TypeError,
match="must be text",
):
clean_task_label(raw)
Place the source file and the test file in the same small project
directory for this example. From that directory, run pytest with
your selected Python interpreter. Using
python -m pytest
helps ensure the command runs through that interpreter rather than
another pytest executable elsewhere on your machine.
python -m pytest -q test_task_labels.py
The combined example contains three valid-input cases, three blank-input cases, and three invalid-type cases. The code shown here was checked with pytest, and all nine parameterized cases passed in that run. This result applies to the displayed implementation and tests, not to a different function that an AI assistant might generate for your project.
When you adapt these tests to an existing project, pay attention to the import path and file layout. A generated test may refer to a module name that does not exist in your repository. An import error is not proof that the function failed its behavior contract; it may mean the test environment or file structure needs correction.
pytest makes it straightforward to turn a behavior contract into executable checks. Use parameterized tests for related inputs, verify intended exceptions, and confirm exact outputs rather than relying on broad assertions.
Generate JavaScript unit tests with Jest
JavaScript is commonly used in browser interfaces, Node.js scripts, and automations that process information from web services. Even small functions can have several behavioral branches. A function that builds a list of completed tasks, for example, must decide how to handle unfinished records, empty arrays, and incomplete task objects.
Jest gives you readable assertions and a convenient way to group related tests. The most useful AI-generated tests make those assertions reflect the intended data rules, not assumptions borrowed from unrelated applications.
Create a function that filters completed tasks
Our JavaScript example receives an array of task objects and returns the trimmed titles of tasks marked as completed. Unfinished tasks are ignored. An empty array returns an empty array. A completed task without a usable title produces an error, because the automation should not silently publish an incomplete summary.
Save the function below as tasks.js.
The example uses CommonJS exports so it can be imported directly
from the Jest test file in a basic Node.js project.
// tasks.js
function completedTaskTitles(tasks) {
if (!Array.isArray(tasks)) {
throw new TypeError("tasks must be an array");
}
return tasks
.filter(task => task && task.done === true)
.map(task => {
if (
typeof task.title !== "string" ||
!task.title.trim()
) {
throw new TypeError(
"completed tasks need a non-empty title"
);
}
return task.title.trim();
});
}
module.exports = { completedTaskTitles };
The distinction between unfinished and completed records matters. An unfinished task does not need to appear in the completed-task summary. A completed task, however, must contain a readable title because its title will be used in the resulting list.
These rules give the AI clear expectations. Instead of asking whether the function runs, ask it to verify selection, ordering, trimming, empty results, invalid inputs, and preservation of the source data.
Use Jest assertions for observable results
Jest's toEqual matcher is useful
when comparing arrays and objects by their contents. By contrast,
toBe is generally used for
primitive equality or reference identity. Choosing the appropriate
matcher matters because a test can fail for reasons unrelated to
the function's actual behavior.
Save these examples in
tasks.test.js.
The first case checks both task selection and whitespace trimming.
The second and third cases verify that the function returns an
empty array when nothing is completed.
// tasks.test.js
const { completedTaskTitles } = require("./tasks");
describe("completedTaskTitles", () => {
test("returns trimmed titles of completed tasks", () => {
const tasks = [
{ title: " Write outline ", done: true },
{ title: "Draft article", done: false },
{ title: " Review notes ", done: true },
];
expect(completedTaskTitles(tasks)).toEqual([
"Write outline",
"Review notes",
]);
});
test("returns an empty array for no tasks", () => {
expect(completedTaskTitles([])).toEqual([]);
});
test("ignores tasks that are not completed", () => {
const tasks = [
{ title: "Draft article", done: false },
];
expect(completedTaskTitles(tasks)).toEqual([]);
});
});
Notice that the first test checks the order of the output as well. The function preserves the order of completed tasks in the input. If someone later changes the implementation to sort titles alphabetically, this test can reveal the change when the expected order differs from the sorted order.
The tests do not need an actual task-management account or live API. They use small artificial inputs that represent the behavior the function promises. That makes them fast to run and easier to understand when one fails.
Test invalid inputs and avoid accidental mutation
A useful unit test suite should also check what happens when the caller supplies something other than an array. It should confirm that a completed record without a valid title does not quietly disappear from the summary.
Another useful requirement is that filtering the task list must not modify the original data. This matters when other parts of an automation reuse the same array. A helper that sorts or modifies records in place could create surprising behavior elsewhere.
// Add these tests inside the same describe block.
test("rejects a non-array input", () => {
expect(() => completedTaskTitles("tasks"))
.toThrow(TypeError);
});
test("rejects a completed task without a title", () => {
expect(() => completedTaskTitles([
{ done: true },
])).toThrow("non-empty title");
});
test("does not change the original task objects", () => {
const originalTask = Object.freeze({
title: " Read report ",
done: true,
});
const tasks = Object.freeze([originalTask]);
expect(completedTaskTitles(tasks)).toEqual([
"Read report",
]);
expect(originalTask.title).toBe(" Read report ");
});
For this example, the additional tests belong inside the existing
describe block in tasks.test.js.
The first three tests and these additional tests can then be run
together. Alternatively, Jest also supports top-level test calls
if you choose to place a test outside the group.
npx jest tasks.test.js --runInBand
The core function's output, empty-array behavior, error paths, and non-mutation behavior were checked separately using Node.js assertions. The Jest examples use documented Jest syntax, but their output should still be verified in the reader's own installed Jest environment. Do not present a predicted green result as a test run that has already happened.
An assertion such as
expect(result).toBeDefined()
may pass even when the completed-task list is wrong.
toEqual verifies the exact
expected titles, their order, and the absence of unfinished tasks.
Use Jest to verify meaningful JavaScript behavior, including array contents, error conditions, and unintended data changes. Strong assertions protect the function's contract, not merely its ability to return a value.
Test edge cases, exceptions, and asynchronous behavior
Once the ordinary inputs are covered, AI can help you identify scenarios that are easy to overlook. This is where test generation becomes more useful than producing repetitive examples. The goal is to expose assumptions about data shape, validation, timing, external services, and failure handling.
Not every function needs every category of test. A pure text-cleaning function does not need network mocks. A script that sends a reminder, however, needs more than a correct message string; it should also confirm what information is sent and how failures are handled.
Ask AI for cases that could change the outcome
Begin with the source of your data. If information comes from forms, consider blank fields, invalid formats, and values outside allowed limits. If it comes from a CSV file, consider missing columns and unexpected encodings. If it comes from an API, consider missing properties, null values, and failed requests.
Boundary conditions should follow the actual requirement. If a function accepts between one and five items, tests for zero, one, five, and six items are more valuable than dozens of random mid-range values. The same principle applies to dates, text length, numerical limits, and allowed categories.
Confirm the normal result before testing unusual conditions. The baseline makes later failures easier to interpret.
Decide whether an empty list should return an empty result, skip an action, or produce a validation error.
Test a missing field, unsupported type, or malformed value according to the function's explicit contract.
Check the exact boundary and one value outside it to detect incorrect comparisons or off-by-one mistakes.
Isolate network calls, files, and other side effects
A unit test should usually avoid sending real messages, changing production files, or updating live accounts. Those operations introduce external conditions that make the result harder to interpret. A failing network request could break the test even when the local function behaves correctly.
For JavaScript, Jest provides mock functions through
jest.fn(). They can record how a
dependency was called and return controlled results without
contacting a real service. The
official Jest mock-functions guide
explains this approach.
Imagine a function that sends a prepared weekly summary through a supplied messaging function. The example below separates the action from the actual delivery service, so a test can provide a fake sender and inspect its calls.
// digest.js
async function sendWeeklyDigest(send, message) {
if (typeof message !== "string" || !message.trim()) {
throw new TypeError("message must contain text");
}
await send(message);
return true;
}
module.exports = { sendWeeklyDigest };
Instead of using a real email or messaging account, the test creates a mock sender that resolves successfully. It then checks whether the intended message was passed to the dependency. The test remains local and produces no real notification.
// digest.test.js
const { sendWeeklyDigest } = require("./digest");
test("sends the requested digest message", async () => {
const send = jest.fn().mockResolvedValue({
id: "fake-message-1",
});
const result = await sendWeeklyDigest(
send,
"Weekly planning summary"
);
expect(result).toBe(true);
expect(send).toHaveBeenCalledTimes(1);
expect(send).toHaveBeenCalledWith(
"Weekly planning summary"
);
});
This checks the local function's interaction with its dependency, not the performance of a real messaging provider. A live-service integration test would answer different questions, such as whether authentication works or the message actually reaches the intended destination. Keep those purposes distinct.
Test rejected promises and temporary files safely
Asynchronous code introduces another requirement: the test must wait for the operation to finish. If an AI-generated Jest test starts a promise without awaiting or returning it, the test might finish before the assertion or rejection occurs. That can produce a misleading pass.
For a messaging function, you may want to verify that a delivery failure is not silently reported as success. Jest provides rejected-promise matchers and mock rejection helpers, making it possible to test this condition without causing a real outage.
// Add this test to digest.test.js
test("reports a failed delivery", async () => {
const send = jest.fn().mockRejectedValue(
new Error("Delivery unavailable")
);
await expect(
sendWeeklyDigest(send, "Weekly summary")
).rejects.toThrow("Delivery unavailable");
expect(send).toHaveBeenCalledTimes(1);
});
Python file-based automations need a similar separation from
production data. pytest provides a
tmp_path fixture that supplies a
temporary directory for a test. A file-writing function can use
that directory rather than a real documents folder. You can also
use controlled fixtures or mocks for other dependencies.
Ask the AI to generate tests around the dependency boundary: what arguments are passed, how success is recognized, and what happens if the dependency fails. Avoid asking it to create real service accounts or use personal credentials simply to prove that a small function behaves correctly.
Never treat a test that sends a real message, deletes a user file, charges an account, or changes a production record as a harmless unit test. Replace external actions with controlled substitutes unless a separate, authorized integration test specifically requires the real service.
The best additional tests target realistic boundaries and failure conditions. Use mocks and temporary resources to keep unit tests independent of live systems, and make asynchronous tests wait for the operation they are checking.
Check whether AI-generated tests are trustworthy
Generating tests is only the first half of the work. The second half is deciding whether those tests provide useful evidence. AI can produce a large suite with attractive names and technically valid syntax while leaving important behavior unchecked.
A passing test means that the implementation satisfied the assertions it actually executed. It does not mean the assertions represented the correct requirements, covered every important branch, or exercised the code you thought they exercised.
Inspect assertions instead of counting passing tests
Read every generated assertion and ask what it establishes. A test that only checks whether a function returns an array can pass while the array contains the wrong items. A test that merely checks whether a promise resolves can pass even if the message contents are incorrect.
A strong assertion checks the relevant result or interaction. For a title cleaner, compare the exact cleaned string. For a completed-task selector, compare the expected array contents. For a sender, check both the call count and the argument sent through the mocked dependency.
Be suspicious of tests that repeat values taken from the current output without comparing them with the written contract. Those tests can turn an implementation mistake into an apparently official requirement.
The test checks that a result exists or that no exception occurred, even when the function has an exact expected result.
The test compares the actual output with an independently defined expectation and fails when that rule is violated.
Check whether tests can detect a deliberately introduced mistake
A practical way to challenge a test suite is to make a controlled, temporary mistake in a disposable copy of the production code. Change one behavior that the tests are supposed to protect, run the tests, and confirm that at least one relevant assertion fails. Then restore the correct implementation.
For the task-title function, you could temporarily remove whitespace normalization. The test using repeated spaces should then fail. If it still passes, inspect whether the test executed the correct function and whether its expected value was meaningful.
This idea resembles mutation testing, where small code changes are used to evaluate the sensitivity of a test suite. You do not need a specialized mutation-testing package to understand the basic principle. For personal projects, one carefully chosen temporary change can reveal a weak assertion.
Treat coverage reports as evidence, not a quality certificate
Test coverage measures which parts of the code were exercised during a test run, according to the coverage tool's configuration. It can reveal untested lines and branches, but it cannot determine whether your expected outputs are correct.
Jest can collect coverage information with its coverage option. For the simple JavaScript project, you can run the tests with a coverage report after installing Jest and configuring the project appropriately.
npx jest --coverage --runInBand
For Python, coverage reporting can be added with a compatible coverage tool such as pytest-cov. It is a separate plugin rather than a built-in replacement for pytest. Install and configure it only when that extra reporting is useful for your project.
A high coverage percentage can coexist with weak assertions. A test might execute every branch of a function while checking only that the function returns without crashing. Conversely, a small suite with clear assertions may protect the most consequential behavior even before coverage is extensive.
Also watch for tests that pass only under one machine or execution order. A test depending on the current time, random values, shared state, or a live service may produce inconsistent results. AI-generated tests should control those conditions whenever possible so that failures have a clear and reproducible cause.
Do not ask AI to make a failing test pass by changing the expected result until it matches the current implementation. Determine whether the requirement, the implementation, or the test setup is wrong before accepting any change.
Test quality depends on meaningful assertions, independent expectations, and the ability to detect real mistakes. Passing counts and coverage percentages are useful signals, but neither proves that the underlying behavior is correct.
Build a practical unit testing routine
Once you have a useful set of tests, make running them a normal part of working on your scripts. You do not need an elaborate development process to benefit. A small personal automation can become much easier to maintain when its important behaviors are checked whenever you change the code.
The most manageable approach is to begin with the functions that matter most: the ones that transform data, decide whether an action should happen, or prepare information for external services. These functions often have clear inputs and outputs, making them suitable starting points for unit tests.
Run focused tests while editing and the full suite before reuse
While working on one function, running its specific test file provides fast feedback. After the change works, run the broader relevant suite. This helps catch unexpected effects in other parts of the project without requiring a long manual review after every small edit.
Keep test commands easy to find. A short project README or a package script can record how to execute them. If several people or multiple AI tools contribute code, the commands should not depend on remembering a particular chat conversation.
Use python -m pytest -q
with the test file path to check the function you changed.
Use npx jest
with the test file name to check the corresponding module.
Check that the change does not conflict with other verified behaviors in the same project.
Modify tests when the approved behavior changes, not merely because the implementation produces a different result.
Use GitHub Copilot or another assistant to extend an existing suite
AI becomes more useful for testing when it can see your existing conventions. If you already have pytest fixtures, Jest mocks, established naming patterns, or utility helpers, include those in the relevant context. The assistant is more likely to generate tests that fit the project instead of introducing another style or dependency.
GitHub's official guide to writing tests with Copilot describes generating unit and integration tests and reviewing the resulting suggestions. Depending on the supported editor and current interface, test-generation actions may also be available from the active file or selected code.
You can use a similar review process with ChatGPT, Claude, or other code-capable assistants. Provide the function, the existing tests, and the missing behavior. Request a small, reviewable addition rather than an entirely regenerated test folder.
Decide which tests deserve permanent maintenance
More tests are not always more useful. A personal automation with a hundred repetitive assertions can be harder to maintain than a compact suite that protects the important rules. Keep tests that detect realistic failures, clarify requirements, or guard behaviors you are likely to change.
Remove unnecessary duplication when several tests exercise exactly the same behavior. Use parameterization to combine related examples without losing their individual expected results. Keep exceptional cases separate when they represent different rules or need a different explanation.
When a requirement genuinely changes, update the contract first. Then adjust the tests to describe the newly approved behavior and change the implementation accordingly. This order prevents your test suite from becoming a collection of historical assumptions that no longer match the workflow.
Make unit testing a repeatable habit rather than a one-time AI generation task. Keep the suite focused, extend it when requirements change, and run the actual tests before relying on updated automation code.
Frequently asked questions
Conclusion: make AI-generated tests prove something useful
AI can make it much easier to begin testing a Python script or JavaScript function. You can turn a written behavior contract into pytest and Jest examples, cover unusual inputs that did not appear during development, and build a reusable set of checks without starting every test from a blank file.
The value comes from the decisions behind those tests. You need to know which outcomes are correct, which inputs are invalid, and which external actions must remain isolated. When those rules are clear, an AI assistant can help translate them into executable assertions. When the rules are missing, a longer generated suite may simply reinforce assumptions that were never verified.
Begin with one function from an automation you already use. Write down its expected behavior, generate a small pytest or Jest suite, and run that suite yourself. Examine each assertion and try one controlled change that should cause a test to fail. Once the tests demonstrate that they can detect a meaningful mistake, you have a stronger foundation for making future code changes safely.
Choose a Python or JavaScript function that handles data you regularly depend on. Define its normal result, one empty-input case, and one invalid-input case. Ask an AI assistant to generate tests using pytest or Jest, then run the tests in your own environment. Keep the cases that genuinely protect the function's intended behavior.
Sam Na writes RoutineOS guides about practical AI workflows, digital productivity, and personal automation. His articles focus on helping readers understand the behavior of their tools, evaluate AI-generated output, and build systems that remain useful beyond the first successful demonstration.
This guide provides general information and educational code examples. The right testing approach depends on your project, dependencies, data, and the consequences of running the code. Test examples in a safe environment before using them in an important automation. For consequential deployments, check current official documentation and seek guidance from a qualified developer or security professional when appropriate.
