Using Github Copilot for unit testing
GitHub Copilot is an excellent tool that can save a lot of time. While there may be some controversy surrounding it, I find it extremely useful.
Yes, it makes mistakes sometimes. Yes, this is basically a tool that replaces some googling and stack-overfwlowing. And yes, we must carefully analyze the provided solutions, which may solve a problem in a sub-optimal way.
Still, in my honest opinion, it's a game-changing technology.
How I use Copilot
There's one use case that I find very well suited for Copilot, which spares me tons of tedious work - unit testing.
Imagine we have a book that has a title and an optional subtitle. We need to write a function that takes a book and returns the full title.
// utils.ts
interface Book {
id: number;
title: string;
subtitle?: string;
}
export function fullTitle(book: Book): string {
// if no subtitle, return title
// otherwise concatenate title and subtitle with a colon
return ""
}
So far, it does nothing. But I added some comments to specify what exactly it should do.
Then I create a new file with a test stub.
// utils.test.ts
import {describe, expect, test} from '@jest/globals';
import { fullTitle } from './utils';
describe('fullTitle', () => {
});
And then the AI magic happens.
This is a screenshot from my editor. The gray text is what Copilot suggested.
As you can see, the suggestion is quite accurate. It adds two tests. The first one tests a book with no subtitle, and the second one tests a case where the subtitle is present.
Incredibly, it even understands the domain area. It suggests an actual book, "The Hobbit", with a real subtitle. How cool is that?
That's a very good start.
Because I'm using TypeScript there's not a lot we should test here. For example, there's no reason to test nulls, as the type system prevents such use.
But I'd like to test that when the subtitle is present but is an empty string, we still want to return only the title and not something like "The Hobbit: "
.
I add one test manually, and...
Boom! Ready.
Let's complicate things a bit and test a case when the title is empty. This is an unexpected data state, and the function should throw an exception.
Done.
Note I'm not really writing tests. I only write the test title, in human-readable form, and let the AI to do the heavy-lifting. Thank god there's no such thing as AI trade unions. Yet.
I'm happy with those four tests for my small function, and I can move on to the fun part.
If I run npm run test
, my tests will fail. This is expected. The fullTitle
function is not implemented yet.
But now I can quickly implement it myself (you can rest a bit, Copilot).
// utils.ts
export function fullTitle(book: Book): string {
if (book.title === "") {
throw new Error("Book title cannot be empty");
}
if (book.subtitle) {
return `${book.title}: ${book.subtitle}`;
}
return book.title;
}
You might not like this particular implementation, and there are many other ways to write it. To be completely fair, I haven't even written it myself, but let the Copilot do it.
The thing is, now that I have my test suit passing, I don't really care all that much. I can refactor it anytime. And I have tests to cover my back.
The example shown in this article is quite simplistic. In my experience, Copilot is able to handle much more complicated cases. Maybe with less grace, sometimes it needs more directions, but it does the job.
The exiting times we're living in...
What's next
- Try Copilot here.
- There's a plugin for Visual Code and one for Vim.
How to set up a project
To try the example from this article, you can quickly set up a project with typescript and jest.
npm init --yes
npm add --save-dev typescript jest ts-jest @jest/globals
./node_modules/.bin/tsc --init
npx ts-jest config:init