A TDD (Test-Driven Development) és a BDD (Behavior-Driven Development) két népszerű fejlesztési módszer, amelyek tesztelési folyamatokra építenek, de eltérő szemlélettel és célkitűzésekkel.
Fő különbségek:
Given the user is on the login page
When they enter valid credentials
Then they should be logged in successfully
A nulláról indulunk, lépésenként bemutatjuk a módszert.
Először hozzunk létre egy új projekt könyvtárat, és inicializáld a Node.js projektet.
mkdir tdd-project cd tdd-project npm init -y
Ez létrehoz egy alap package.json fájlt.
Telepítsük a szükséges fejlesztői függőségeket: Mocha a teszteléshez, Chai az asszertálásokhoz, és Sinon a mockoláshoz és stuboláshoz. Mivel a projektben jelszó hash-elésre is szükség lesz, telepítük a bcrypt könyvtárat is.
npm install mocha chai sinon bcrypt --save-dev
Hozzuk létre a szükséges mappákat és fájlokat a projekt szerkezetéhez.
mkdir test mkdir services mkdir repositories ni ./test/userService.test.js ni ./services/userService.js ni ./repositories/userRepository.jsmegjegyzés: az
ni parancs powershell-ben a linuxos touch parancs megfelelője.
Most a projekt struktúrája így néz majd ki:
tdd-project/ │ ├── test/ │ └── userService.test.js // Tesztek a UserService-hez │ ├── services/ │ └── userService.js // UserService osztály │ ├── repositories/ │ └── userRepository.js // UserRepository osztály │ └── package.json // Node.js projekt leíró fájl
Létrejött három üres állomány.
A Mocha futtatásához a package.json fájlban hozzá kell adni egy részt, amely a mocha parancsot futtatja a test mappában:
Nyissuk meg a package.json fájlt, és addjuk hozzá a scripts részhez a következőt:
"type": "module",
"scripts": {
"test": "mocha"
}
Most kezdhetjük a TDD folyamatot: először a teszteket írdjuk meg. Például a userRepository.test.js fájlba írjuk a következő teszteket:
import assert from 'assert';
import UserRepository from '../repositories/userRepository.js';
describe('UserRepository', function() {
let userRepository;
beforeEach(function() {
userRepository = new UserRepository();
});
it('should return null if user is not found by email', async function() {
const user = await userRepository.findUserByEmail('notfound@example.com');
assert.strictEqual(user, null);
});
it('should save a user and retrieve it by email', async function() {
const newUser = { email: 'test@example.com', password: 'hashedPassword' };
await userRepository.saveUser(newUser);
const foundUser = await userRepository.findUserByEmail('test@example.com');
assert.strictEqual(foundUser.email, 'test@example.com');
assert.strictEqual(foundUser.password, 'hashedPassword');
});
it('should handle saving multiple users', async function() {
const user1 = { email: 'user1@example.com', password: 'password1' };
const user2 = { email: 'user2@example.com', password: 'password2' };
await userRepository.saveUser(user1);
await userRepository.saveUser(user2);
const foundUser1 = await userRepository.findUserByEmail('user1@example.com');
const foundUser2 = await userRepository.findUserByEmail('user2@example.com');
assert.strictEqual(foundUser1.email, 'user1@example.com');
assert.strictEqual(foundUser2.email, 'user2@example.com');
});
});
Futtasd a Mocha teszteket, hogy megbizonyosodj arról, hogy a tesztek elbuknak (mivel még nem írtad meg a tényleges implementációt).
npm test
Ez a parancs futtatja a mocha parancsot, amely végigmegy a test mappában lévő teszteken. Mivel a UserRepository még nem implementált, a tesztek elbuknak, ami a TDD módszer lényege: először a tesztek buknak el, majd az implementáció következik.
Most írd meg a tényleges kódot a tesztek sikeressé tételéhez.
userRepository.js:
// repositories/userRepository.js
class UserRepository {
constructor() {
this.users = []; // Szimulált adatbázis tömbként
}
// Felhasználó keresése e-mail alapján
async findUserByEmail(email) {
const user = this.users.find(user => user.email === email);
return user || null;
}
// Új felhasználó mentése az adatbázisba
async saveUser(user) {
this.users.push(user);
return user;
}
}
export default UserRepository;
Most futtassuk újra a teszteket:
npm test
Most a teszteknek sikeresen át kell menniük, mivel az implementáció megfelel a tesztek elvárásainak.
Hozzuk létre a test/userRepository.test.js-t az alábbi tartalommal.
import { expect } from 'chai';
import sinon from 'sinon';
import bcrypt from 'bcrypt';
import UserRepository from '../repositories/userRepository.js';
import UserService from '../services/userService.js';
describe('UserService', function() {
let userService;
let userRepositoryStub;
beforeEach(function() {
// Mockoljuk az adatbázis hívásokat
userRepositoryStub = sinon.stub(UserRepository.prototype, 'findUserByEmail');
userService = new UserService(new UserRepository());
});
afterEach(function() {
// Restore minden stubolt funkciót a tesztek után
sinon.restore();
});
it('should return an error if the email is already in use', async function() {
// Szimuláljuk, hogy az email már létezik
userRepositoryStub.resolves({ email: 'existing@example.com' });
const result = await userService.registerUser('existing@example.com', 'password123');
expect(result.success).to.be.false;
expect(result.message).to.equal('Email already in use');
});
it('should hash the password and register the user if the email is not in use', async function() {
// Szimuláljuk, hogy az email nem létezik
userRepositoryStub.resolves(null);
// Mockoljuk a bcrypt hash funkciót
const bcryptStub = sinon.stub(bcrypt, 'hash').resolves('hashedPassword');
const result = await userService.registerUser('newuser@example.com', 'plainPassword');
// Ellenőrizzük, hogy a bcrypt hash funkciót hívták
expect(bcryptStub.calledOnce).to.be.true;
expect(bcryptStub.calledWith('plainPassword')).to.be.true;
expect(result.success).to.be.true;
expect(result.message).to.equal('User registered successfully');
});
it('should not call bcrypt hash if the email already exists', async function() {
// Szimuláljuk, hogy az email már létezik
userRepositoryStub.resolves({ email: 'existing@example.com' });
const bcryptStub = sinon.stub(bcrypt, 'hash');
const result = await userService.registerUser('existing@example.com', 'plainPassword');
// Ellenőrizzük, hogy a bcrypt hash nem lett meghívva
expect(bcryptStub.called).to.be.false;
expect(result.success).to.be.false;
expect(result.message).to.equal('Email already in use');
});
});
A teszt futtatás természetesen hibát ad.
Hozzuk létre az services/userService.js implementációt az alábbiak szerint:
import bcrypt from 'bcrypt';
class UserService {
constructor(userRepository) {
this.userRepository = userRepository;
}
async registerUser(email, password) {
const existingUser = await this.userRepository.findUserByEmail(email);
if (existingUser) {
return { success: false, message: 'Email already in use' };
}
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = { email, password: hashedPassword };
await this.userRepository.saveUser(newUser);
return { success: true, message: 'User registered successfully' };
}
}
export default UserService; // CommonJS helyett exportáljuk ESM szintaxissal