Provocări Write-tests
Într-o provocare de cod clasică, cursantul scrie implementarea, iar testele tale o verifică. O provocare de tip Write-tests inversează rolurile: tu oferi o implementare corectă, iar cursantul scrie testele unitare pentru aceasta. 🧪
Pentru a demonstra că testele cursantului sunt într-adevăr bune, DojoCode le rulează de două ori:
- În raport cu soluția ta de referință, implementarea corectă. Fiecare test trebuie să treacă.
- În raport cu fiecare mutant, o copie a soluției de referință cu un mic bug realist integrat intenționat. Cel puțin un test trebuie să eșueze pentru fiecare mutant.
Un mutant care face ca cel puțin un test să eșueze este prins. Cursantul finalizează provocarea atunci când testele sale trec pe soluția de referință și prind fiecare mutant. Această tehnică este cunoscută sub numele de mutation testing și este cel mai onest mod de a măsura dacă o suită de teste fixează cu adevărat comportamentul.
Când să folosești acest mod
Provocările de tip Write-tests sunt excelente pentru predarea testării în sine: cazuri limită, granițe, gestionarea erorilor și testarea componentelor UI sau a endpoint-urilor HTTP. Cursanții citesc cod real și trebuie să se gândească la modul în care s-ar putea strica.
Șabloane acceptate
Modul Write tests este disponibil pentru aceste șabloane:
| Grup | Șabloane | Test runner |
|---|---|---|
| Terminal | Node.js, Node.js cu TypeScript | Jest |
| Terminal | Python | pytest |
| Backend cu API tester | NestJS | Vitest + @nestjs/testing |
| Backend cu API tester | Fastify, Hono | Vitest |
| Browser | Svelte, Vanilla JavaScript, Vanilla TypeScript, Angular, React, React cu TypeScript, Vue, Vue cu TypeScript | Jest + Testing Library |
| Browser | SolidJS, SolidJS cu TypeScript | Vitest + Testing Library |
Un exemplu complet și verificat pentru fiecare șablon este disponibil în Exemple Write-tests după șablon.
Crearea unei ciorne Write-tests
- Deschide My Challenges și dă clic pe NEW DRAFT.
- Introdu un Title și selectează un șablon care suportă modul.
- Alege Write tests din selectorul de mod de sub titlu (Fig. 1). Selectorul apare doar pentru șabloanele suportate. Selectarea unui alt șablon comută ciorna înapoi la Write Solution.
- Dă clic pe Create draft.

Fig. 1 - Selectorul de mod din modalul Create Code Challenge, cu Write tests selectat pentru NodeJS
Modul este ales o singură dată
Modul este stabilit la crearea ciornei și nu poate fi modificat ulterior. Pentru a transforma o provocare clasică într-o provocare de tip Write-tests, creează o ciornă nouă în modul Write tests.
Noua ciornă nu începe goală. Fiecare șablon suportat vine cu un mic exemplu funcțional: un test de pornire, o soluție de referință, o suită de referință a autorului și doi mutanți. Îl poți rula imediat și apoi îl poți înlocui cu propria provocare.
Pagina de editare
O ciornă de tip Write-tests folosește aceeași pagină de editare ca o provocare clasică, având categorii de fișiere diferite în panoul Files (Fig. 2).

Fig. 2 - Pagina de editare a provocării Node.js Add Numbers: Write the Tests după o rulare cu succes a opțiunii Test hidden bugs
Categoriile sunt enumerate în ordinea în care construiești provocarea:
| Categorie | Cine o vede | Ce conține |
|---|---|---|
| Soluția de referință (vizibilă celui care rezolvă) | Cursantul, doar în citire | Implementarea corectă. Cursantul o citește pentru a înțelege ce trebuie să testeze. |
| Fișiere de test inițiale | Cursantul, editabil | Fișierul de test de la care pornește cursantul, plus fișiere ajutătoare, cum ar fi punctul de intrare pentru Run (main.js, main.py) sau fișierele de intrare web ale șabloanelor de browser. |
| Bug-uri ascunse (mutanți) | Nimeni | Copiile cu bug-uri ale soluției de referință, câte un folder pentru fiecare mutant, plus mutants.json. Cursanții văd doar eticheta și descrierea unui mutant, așa cum se descrie în Ce vede cursantul. |
| Testele de referință ale autorului (verificate la publicare) | Doar tu | Suita ta proprie de teste. Aceasta dovedește că provocarea poate fi rezolvată: trebuie să treacă pe soluția de referință și să prindă fiecare mutant. |
| Teste inițiale | Nimeni | Nu este folosită în modul write-tests. Apare doar pe ciornele unde șablonul inițial a completat-o și o poți lăsa așa cum este. |
Marcajele de fișiere din provocările clasice se aplică în continuare pentru Fișiere de test inițiale: hidden, read-only, initially opened și main file. Vezi Creează o provocare de cod pentru semnificația lor.
Soluția de referință are întotdeauna prioritate
Când spațiul de lucru al unui cursant conține un fișier pe aceeași cale ca un fișier din soluția de referință, platforma ignoră copia cursantului și rulează soluția de referință reală. Cursantul nu poate modifica implementarea testată.
Mutanți
Fiecare folder de nivel superior din Bug-uri ascunse (mutanți) reprezintă un mutant. Fișierele sale înlocuiesc fișierele soluției de referință care au aceeași cale relativă. De exemplu, plus-one/add.js din Bug-uri ascunse (mutanți) înlocuiește add.js din soluția de referință în timp ce rulează acel mutant. Toate celelalte fișiere ale soluției de referință rămân neschimbate.

Fig. 3 - Trei foldere de mutanți și fișierul mutants.json care le etichetează
Un fișier mutant este o copie completă a fișierului de referință, cu o singură modificare subtilă (Fig. 4):

Fig. 4 - Mutantul negatives-as-positive păstrează același API și aceeași validare și adaugă doar valori absolute la rezultat
Pentru șabloanele cu un folder src/, folderul mutantului îl reflectă: percent-over-100/src/discount.service.ts din Bug-uri ascunse (mutanți) înlocuiește src/discount.service.ts.
Fișierul mutants.json
mutants.json se află în rădăcina Bug-uri ascunse (mutanți) și oferă fiecărui mutant o etichetă și o descriere:
{
"plus-one": {
"label": "Result value",
"description": "The result is one more than it should be."
},
"negatives-as-positive": {
"label": "Negative numbers",
"description": "Negative numbers are added as if they were positive."
},
"strings-converted": {
"label": "Non-number input",
"description": "A string is converted to a number instead of being rejected."
}
}Cheile sunt numele folderelor mutanților.
- label este indiciul. Denumește comportamentul pe care cursantul ar trebui să îl testeze (
Negative numbers), niciodată bug-ul în sine:Negatives handled wrongar divulga răspunsul. - description specifică bug-ul exact. Cursantul o citește doar după ce a prins mutantul, ca explicație a ceea ce protejează testul său.
Un mutant fără o intrare folosește numele folderului său drept etichetă, așadar adaugă întotdeauna o intrare pentru fiecare mutant.
Ce vede cursantul
Cursanții nu văd niciodată folderele mutanților sau fișierele acestora. În panoul de rezultate pentru Verify tests și Submit, un mutant este afișat diferit în funcție de faptul dacă testele lor l-au prins sau nu:
| Mutant ratat | Mutant prins | |
|---|---|---|
| Nume | Hidden bug #N | Eticheta |
| Detalii | Un memento că toate testele lor încă trec pe această copie cu buguri și eticheta ca Hint (indiciu) | Descrierea și Prins de cu numele testelor lor care au eșuat pe acesta |
Părțile ascunse nu sunt trimise niciodată în browserul cursantului, așa că nu pot fi citite nici din cererile de rețea. În calitate de autor, vezi întotdeauna fiecare etichetă și descriere atunci când rulezi Test hidden bugs pe pagina de editare.
Reguli pentru mutanți
Platforma verifică structura Bug-uri ascunse (mutanți) înainte de a rula orice:
- Există cel puțin un folder de mutant, iar fiecare folder de mutant conține cel puțin un fișier.
- Singurul fișier permis direct în rădăcina Bug-uri ascunse (mutanți) este
mutants.json. - Fiecare fișier mutant trebuie să înlocuiască un fișier existent din soluția de referință. Un mutant nu poate adăuga fișiere noi.
Scrierea unor mutanți buni
- Un singur bug realist per mutant. Limite de tip off-by-one, un operator greșit, o validare lipsă, ramuri inversate, un cod de stare greșit, o stare
disabledlipsă. Fiecare mutant ar trebui să predea o lecție de testare. - Păstrează API-ul public identic. Aceleași exporturi, aceleași semnături de funcții, aceeași structură de markup a componentei. Un mutant care nu reușește să se compileze sau crapă la import este prins de orice test, așa că nu învață nimic.
- Evită buclele infinite. O rulare a unui mutant care expiră ca timp nu este niciodată considerată ca fiind prinsă.
- Fă fiecare mutant să poată fi prins. Un mutant care se comportă exact ca soluția de referință pentru orice intrare (un mutant echivalent) nu va putea fi prins niciodată, iar provocarea nu va putea fi finalizată. Testele tale de referință de autor îl vor dezvălui înainte de a publicar.
- Începe cu 2 până la 4 mutanți. Aceștia ar trebui să acopere comportamente diferite, cum ar fi cazul ideal (happy path), o limită și un caz de eroare.
Testarea provocării tale
Bara de acțiuni a unei provocări de tip scriere de teste are două butoane de test. Ambele rulează suita din Testele de referință ale autorului (verificate la publicare) împreună cu Fișiere de test inițiale, exact același spațiu de lucru pe care îl trimite un cursant, și ambele folosesc fișierele deschise în prezent în editor, inclusiv modificările nesalvate:
| Buton | Ce rulează |
|---|---|
| Test | Testele tale doar pe soluția de referință. Toate testele trebuie să treacă. |
| Test hidden bugs (Testează bug-urile ascunse) | Testele tale pe fiecare mutant, unul după altul. Fiecare mutant este raportat ca fiind prins sau ratat. |
Apasă mai întâi pe Test. Panoul de rezultate arată rezultatul fiecărui test, ca la o provocare clasică (Fig. 5).

Fig. 5 - Test rulează testul de pornire și suita de referință a autorului pe soluția de referință: toate cele patru teste trec
După ce trec toate testele, apasă pe Test hidden bugs. Panoul de rezultate arată același raport pe care îl vede un cursant: rezumatul, verdictul și câte o linie pentru fiecare mutant, cu numele testelor care l-au prins (Fig. 6). Testele rulează în continuare mai întâi pe soluția de referință, așa că atunci când unul dintre ele eșuează acolo, rularea se oprește și afișează în schimb testele care au eșuat.

Fig. 6 - O rulare reușită de Test hidden bugs: fiecare mutant este prins, cu testul care l-a prins
Butonul Test All al provocărilor clasice nu există în acest mod.
Păstrează testul de pornire onest
Deoarece testul de pornire rulează alături de suita ta de referință, acesta trebuie să treacă și pe soluția de referință. Un test de pornire bun acoperă un caz evident și lasă cei mai mulți mutanți pe seama cursantului pentru a-i prinde.
Publicarea
Înainte de publicare, provocarea are nevoie de un fișier de test de pornire, o soluție de referință, o suită de referință a autorului și cel puțin un mutant care conține un fișier.
Când apeși pe Publish, fiecare variație de șablon a provocării este validată în fundal:
- Regulile pentru mutanți de mai sus sunt verificate.
- Testele tale de referință de autor (împreună cu fișierele de pornire) rulează pe soluția de referință și pe fiecare mutant al acelei variații.
Provocarea este publicată numai dacă, pentru fiecare variație, rularea de referință este verde și fiecare mutant este prins. În caz contrar, fereastra de validare arată variația care a eșuat cu același raport ca butonul Test hidden bugs.
Adăugarea altor șabloane
Folosește butonul + de lângă selectorul de șablon pentru a adăuga o variație într-o altă limbă. Pot fi adăugate doar șabloanele care suportă provocări de tip scriere de teste. Noua variație începe de la exemplul de scriere de teste al acelui șablon, cu propriul test de pornire, soluție de referință, suită de referință a autorului și mutanți.
Lasă asistentul AI să te ajute
AI Chat de pe pagina de editare cunoaște convențiile de scriere a testelor pentru fiecare șablon suportat. Roagă-l să genereze o schiță de mutanți pentru soluția ta de referință sau să îți consolideze testele de referință de autor, apoi rulează Test și Test hidden bugs pentru a verifica rezultatul.
Ghid pas cu pas: Node.js
Provocarea Add Numbers: Write the Tests este un exemplu mic și complet. Funcția este trivială, așa că întreaga provocare este despre teste: numere negative, rezultatul în sine și date de intrare nevalide.
Soluția de referință (vizibilă celui care rezolvă)
function add(a, b) {
if (typeof a !== "number" || typeof b !== "number") {
throw new TypeError("add expects two numbers");
}
return a + b;
}
module.exports = { add };Testul de pornire, fișierul pe care cursantul îl deschide mai întâi. Acesta acoperă cazul evident și lasă restul pe seama cursantului.
const { describe, it, expect } = require('@jest/globals');
const { add } = require('./add');
describe('add', () => {
it('adds two positive numbers', () => {
expect(add(2, 3)).toBe(5);
});
// Add tests until every hidden bug is caught.
});Testele de referință ale autorului (verificate la publicare). Câte un test pentru fiecare comportament pe care îl strică un mutant:
const { describe, it, expect } = require('@jest/globals');
const { add } = require('./add');
describe('add (reference suite)', () => {
it('adds two positive numbers', () => {
expect(add(1, 2)).toBe(3);
});
it('adds negative numbers correctly', () => {
expect(add(-2, -3)).toBe(-5);
expect(add(-2, 5)).toBe(3);
});
it('throws a TypeError when an argument is not a number', () => {
expect(() => add('1', 2)).toThrow(TypeError);
expect(() => add(1, undefined)).toThrow(TypeError);
});
});Mutanți. Fiecare folder conține o copie cu buguri a add.js cu o singură modificare:
function add(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('add expects two numbers');
}
return a + b + 1;
}
module.exports = { add };function add(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('add expects two numbers');
}
return Math.abs(a) + Math.abs(b);
}
module.exports = { add };function add(a, b) {
return Number(a) + Number(b);
}
module.exports = { add };Observă cum fiecare mutant se asociază unui test din suita de referință:
| Mutant | Ce se modifică | Prins de |
|---|---|---|
plus-one | Fiecare rezultat este cu unu prea mare | Orice sumă exactă, chiar și testul de pornire add(2, 3) |
negatives-as-positive | add(-2, -3) returnează 5 | O sumă cu un operand negativ |
strings-converted | add('1', 2) returnează 3 în loc să arunce o eroare | expect(() => add('1', 2)).toThrow(TypeError) |
Apasă pe Test: toate testele trec pe soluția corectă (Fig. 5). Apoi apasă pe Test hidden bugs: toți cei trei mutanți sunt prinși (Fig. 6). Testul de pornire singur prinde doar plus-one, care este exact decalajul pe care cursantul trebuie să îl acopere.
Ghid pas cu pas: Python
Variația Python a aceleiași provocări are aceeași structură, cu pytest (Fig. 7).

Fig. 7 - Variația Python cu mutantul negatives-as-positive deschis și toți cei 3 mutanți prinși
Soluția de referință (vizibilă celui care rezolvă)
def add(a, b):
"""Return a + b. Both arguments must be numbers."""
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
raise TypeError("add expects two numbers")
return a + bTest de pornire. Testele importă soluția prin pachetul challenge:
from pytest import mark as m
from challenge.add import add
@m.describe("add")
class TestAdd:
@m.it("Adds two positive numbers")
def test_positive(self):
assert add(2, 3) == 5
# Add tests until every hidden bug is caught.Testele de referință ale autorului (verificate la publicare)
import pytest
from pytest import mark as m
from challenge.add import add
@m.describe("add (reference suite)")
class TestAddReference:
@m.it("Adds two positive numbers")
def test_positive(self):
assert add(1, 2) == 3
@m.it("Adds negative numbers correctly")
def test_negative(self):
assert add(-2, -3) == -5
assert add(-2, 5) == 3
@m.it("Raises a TypeError when an argument is not a number")
def test_not_a_number(self):
with pytest.raises(TypeError):
add("1", 2)
with pytest.raises(TypeError):
add(1, None)Mutanți. Aceleași trei erori, scrise în Python. Variația reutilizează același mutants.json.
def add(a, b):
"""Return a + b. Both arguments must be numbers."""
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
raise TypeError("add expects two numbers")
return a + b + 1def add(a, b):
"""Return a + b. Both arguments must be numbers."""
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
raise TypeError("add expects two numbers")
return abs(a) + abs(b)def add(a, b):
"""Return a + b. Both arguments must be numbers."""
return float(a) + float(b)main.py rămâne punctul de intrare Run. Importă fără prefixul pachetului (from add import add), astfel încât cursanții să poată afișa valori în timp ce explorează codul.
Pagini conexe
- 📚 Exemple de scriere a testelor după șablon: un exemplu complet pentru toate cele 16 șabloane acceptate
- 🧪 Rezolvarea unei provocări de scriere a testelor: ce vede cursantul și cum sunt punctate rezultatele
- 🛠️ Crearea unei provocări de cod: pagina de editare, marcajele fișierelor și publicarea
- 📋 Ghid de creare a provocărilor