Ponieważ generatywna AI opiera się na kontekście, sprawienie, aby AI generowała testy dla funkcji, które mają błędy, może być trudne. Co jednak, gdybyśmy odwrócili proces do góry nogami i poprosili AI o wygenerowanie działającego kodu na podstawie nieudanych testów? To jest idea stojąca za rozwojem sterowanym testami i może być najlepszym sposobem, aby asystent AI napisał dokładnie taki kod, jakiego potrzebujesz. Rozwój sterowany testami (TDD) to praktyka tworzenia oprogramowania, która koncentruje się na tworzeniu testów dla funkcjonalności, która jeszcze nie istnieje. W rozwoju sterowanym testami programista pisze test dla pojedynczej jednostki kodu, a następnie pisze kod, aby test przeszedł. Żaden fragment kodu nie jest pisany bez wcześniejszego napisania testu, więc TDD (wykonane poprawnie) skutkuje 100-procentowym pokryciem kodu i kodem wyższej jakości. Proces TDD obejmuje trzy kroki, zwane cyklem TDD, które są powtarzane tak często, jak to konieczne, aby zbudować pożądane oprogramowanie:
»»Napisz test. Napisz test, który dokładnie opisuje pojedynczy element funkcjonalności, a następnie uruchom test, aby potwierdzić, że się nie powiedzie (ponieważ funkcja jeszcze nie istnieje). Znany również jako etap czerwony.
»»Napisz kod, aby przejść test. Napisz minimalny kod niezbędny do przejścia testu. Znany również jako etap zielony.
»»Refaktoryzuj. Przebuduj zarówno kod, jak i test, aby je ulepszyć.
Po etapie refaktoryzacji cykl zaczyna się od nowego testu. Celem programisty podczas praktykowania TDD jest tworzenie testów i oprogramowania w małych i przetestowanych fragmentach kodu. Zamiast skupiać się na poprawnym pisaniu kodu, a następnie testowaniu, czy działa, TDD kładzie nacisk na pisanie dobrych testów, a następnie pisanie minimalnej ilości kodu, aby przejść testy, nawet jeśli kod, który piszesz, nie jest idealny. Po przejściu testu przez kod, ulepszasz kod na etapie refaktoryzacji. Ponieważ AI jest dobra w pisaniu małych ilości kodu, gdy otrzymuje dobre instrukcje, TDD wydaje się idealnym dopasowaniem do pracy z asystentem AI. Jest jednak pewien haczyk — musisz sam napisać testy. Jeśli masz AI piszącą zarówno testy, jak i kod, jest wysoce prawdopodobne, że oba będą błędne. Pisanie testów w TDD to sposób, w jaki projektujesz oprogramowanie, więc ważne jest, aby testy były poprawne. Technicznie rzecz biorąc, nie musisz pisać testów całkowicie bez pomocy AI. Korzystanie z funkcji uzupełniania Copilot podczas kodowania jest ogólnie bezpieczne, o ile weryfikujesz i zatwierdzasz każde automatyczne uzupełnianie. Aby znaleźć funkcję, która byłaby dobrym kandydatem do rozwinięcia za pomocą TDD, przyjrzałem się niedokończonym funkcjom w SRS gry w kółko i krzyżyk, które wygenerowałem za pomocą ChatGPT w rozdziale 5. Największa funkcja, której jeszcze nie stworzyłem, jest określona w nagłówku Historia i statystyki gry:
– Gra będzie śledzić statystyki gracza, takie jak:
– Całkowita liczba rozegranych gier.
– Liczba wygranych gier.
– Liczba przegranych gier.
Na początek napisałem test sprawdzający, czy zmienna totalGamesPlayed zwiększa się po rozegraniu gry. Oto, co wymyśliłem:
describe(‘Game stats’, () => {
let game;
beforeEach(() => {
game = new TicTacToeGame();
});
test(‘increments total games played after a game ends’,
() => {
expect(game.totalGamesPlayed).toBe(0);
game.isGameOver = true;
game.updateGameStats();
expect(game.totalGamesPlayed).toBe(1);
});
});
Po napisaniu testu uruchomiłem Jest, aby potwierdzić, że test się nie powiódł. I oczywiście tak się stało, jak pokazano na rysunku

Następnie otworzyłem pliki script.js, script.test.js i srs.md i wyświetliłem Copilot Chatowi następujący komunikat:
@workspace Napisz kod, aby przejść ten test:
Po wyświetleniu monitu wkleiłem swój test w oknie czatu. Zauważ, że użyłem polecenia @workspace, aby powiedzieć Copilotowi, aby uznał wszystkie pliki w moim projekcie za odniesienia do swojej odpowiedzi, co pokazano na rysunku

Wdrożyłem sugerowane rozwiązanie i ponownie uruchomiłem testy. Wszystko przeszło, więc byłem gotowy przejść do refaktoryzacji. Jednym ze sposobów ulepszenia testu byłoby symulowanie zakończenia gry, zamiast ręcznego ustawiania isGameOver na true. To ulepszenie sprawiłoby, że test byłby bardziej reprezentatywny dla rzeczywistego użycia klasy.
Zmieniłem test na następujący:
test(‘increments total games played after a game ends’, () => {
expect(game.totalGamesPlayed).toBe(0);
game.endGame()
expect(game.totalGamesPlayed).toBe(1);
});
Ponownie uruchomiłem test i nie powiódł się. Zapytałem Copilota, jak zmienić program, aby zaliczyć test, a on odpowiedział tak, jak pokazano na rysunku

Ta sugestia zadziałała i mój test zaliczył. Byłem całkiem zadowolony z testu i rozwiązania, więc przeszedłem do refaktoryzacji. Dokładnie wszystko sprawdziłem, a następnie napisałem kolejny test. Po zmodyfikowaniu testu i zaimplementowaniu kodu, aby go zaliczyć, sprawdź, czy nie ma zbędnego kodu. Jak dowiesz się w rozdziale 6, kod, który mógł mieć kiedyś jakiś cel, ale nie jest już używany przez program, jest znany jako martwy kod. Powinieneś usunąć martwy kod podczas etapu refaktoryzacji TDD.
W tym momencie metoda endGame() nigdy nie jest wywoływana podczas normalnego grania w grę, więc postanowiłem, że zakończenie gry będzie kolejnym elementem funkcjonalności do napisania. Aby zobaczyć, jak Copilot napisze test, wywołałem go następującym poleceniem:
@workspace Jak mogę napisać test jednostkowy, aby sprawdzić, czy metoda endGame jest uruchamiana, gdy gracz wygrywa?
Z nieznanego powodu Copilot odpowiedział, że może pomóc tylko w przypadku pytań związanych z programowaniem, jak pokazano na rysunku

Jestem pewien, że mógłbym doprecyzować swój monit, aby Copilot zrozumiał, o czym mówię, ale uznałem, że mój argument o nieproszeniu AI o generowanie testów podczas wykonywania TDD został udowodniony, więc napisałem następujący zestaw testów:
describe(‘Game end’, () => {
let game;
beforeEach(() => {
game = new TicTacToeGame();
});
test(‘sets isGameOver to true after a player wins’, () => {
expect(game.isGameOver).toBe(false);
game.board = [‘X’, ‘X’, ‘X’, ‘O’, ‘O’, ”, ”, ”, ”];
game.checkWin();
expect(game.isGameOver).toBe(true);
});
});
Kilkakrotnie próbowałem zmusić Copilot Chat do wygenerowania kodu, którego chciałem. Przedstawiłem monit @workspace, aby przeanalizował wszystkie pliki w moim projekcie. Dałem mu tylko script.js jako odniesienie, a następnie dałem mu tylko script.test.js. Za każdym razem uparcie odmawiał podania tego, co uważałem za akceptowalną odpowiedź. Zamiast dalej kłócić się z Copilot Chat, postanowiłem, że szybciej będzie po prostu napisać ten kod samemu. Prawidłowa odpowiedź to wywołanie funkcji endGame(), gdy jeden z graczy wygrywa. Oto poprawiona funkcja checkWin(), z kodem, który napisałem, aby przejść ten test, pokazanym pogrubieniem:
/**
* Check whether someone has won the game
* @returns {boolean}
*/
checkWin() {
const winCombos = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
const win = winCombos.some((combo) => {
return (
this.board[combo[0]] &&
this.board[combo[0]] === this.board[combo[1]] &&
this.board[combo[0]] === this.board[combo[2]]
);
});
win ? this.endGame() : null;
return win;
}
Wdrożyłem swoje rozwiązanie i test przeszedł. Prowadzi to do najważniejszej lekcji tego rozdziału: w TDD wspomaganym przez AI, jeśli znasz właściwy sposób (lub nawet łatwy sposób), aby zaliczyć test, nie proś o pomoc AI. Zaufaj sobie. Trzeba zrobić o wiele więcej, aby ukończyć funkcjonalność opisaną w sekcji Game History and Stats w SRS.











