עד עכשיו דיברנו לא מעט על ארכיטקטורה נקייה (Clean Architecture), Dependency Injection, שכבות עסקיות, ניהול שגיאות ואירועים. הפעם נדבר על אחד היתרונות הכי גדולים של מימוש ארכיטקטורה נקייה בצורה נכונה: בדיקות אוטומטיות.
רגע לפני שמתחילים – למה בכלל בדיקות אוטומטיות?
אני בטוח שכולנו מכירים את הסיטואציה הזו:
הוספתם פיצ'ר חדש, הכול נראה טוב, אתם מעלים את הקוד לשרת – ופתאום דברים נשברים. הלקוחות מתלוננים, והלחץ עולה, לחילופין, צריך להוסיף פיצר חדש, אבל הקוד ספגטי וכל שינוי עלול להשפיע על עשרות פונקציות אחרות.
בדיקות אוטומטיות הן הדרך שלנו למנוע בדיוק את התרחיש הזה. הן נותנות לנו ביטחון לשנות קוד בלי לפחד, ומבטיחות שהמערכת ממשיכה להתנהג כפי שאנחנו מצפים.
בדיקות אוטומטיות הן הכלי המרכזי שמאפשר לנו:
- לבצע שינויים בלי פחד.
- לתפוס תקלות לפני שהן מגיעות ללקוח.
- להבין במהירות איפה נשבר משהו.
- לעבוד בקצב מהיר עם ביטחון.
שכבות נקיות = בדיקות קלות
הרעיון המרכזי בארכיטקטורה נקייה הוא להפריד בין השכבות בצורה כזו שכל אחת מהן תהיה עצמאית לחלוטין, מנותקת משכבות אחרות, ובעיקר – קלה לבדיקה.
כשאנחנו בונים את המערכת לפי שכבות –
- שכבת Entities ו־Use Cases נקייה לחלוטין מתלויות טכניות
- תשתיות כמו DB או API מרוחק מופרדות לגמרי
- ה־Use Case מקבל הכל בהזרקה (Dependency Injection)
אז מה נשאר לנו לעשות? לבדוק את הלוגיקה העסקית ללא שום תלות במסד נתונים או HTTP.
שכבת הליבה (Entities & Use Cases)
שכבת הליבה היא הלב של הבדיקות שלכם. היא עצמאית לחלוטין מכל תשתית (DB, רשת וכו'). לכן קל מאוד לבדוק אותה:
- בדיקות יחידה (Unit Tests) – פשוטות ומהירות.
- אין צורך ב־Mocks מורכבים של תשתיות.
לדוגמה, בואו נבדוק Use Case פשוט של רישום משתמש חדש:
// useCase/registerUserUseCase.ts
export class RegisterUserUseCase {
constructor(private userRepo: IUserRepository) {}
async execute(user: User) {
const existing = await this.userRepo.findByEmail(user.email);
if (existing) {
throw new Error("User already exists");
}
await this.userRepo.save(user);
}
}
הטסט פשוט ונקי:
// test/registerUserUseCase.test.ts
import { expect } from "chai";
import sinon from "sinon";
import { RegisterUserUseCase } from "../useCase/registerUserUseCase";
describe("RegisterUserUseCase", () => {
it("should throw error if user already exists", async () => {
const user = { id: "123", email: "[email protected]" };
const mockRepo = {
findByEmail: sinon.stub().resolves(user),
save: sinon.stub(),
};
const useCase = new RegisterUserUseCase(mockRepo);
try {
await useCase.execute(user);
throw new Error("Expected error was not thrown");
} catch (err) {
expect(err.message).to.equal("User already exists");
expect(mockRepo.save.called).to.be.false;
}
});
it("should save new user if not exists", async () => {
const user = { id: "456", email: "[email protected]" };
const mockRepo = {
findByEmail: sinon.stub().resolves(null),
save: sinon.stub().resolves(),
};
const useCase = new RegisterUserUseCase(mockRepo);
await useCase.execute(user);
expect(mockRepo.save.calledOnceWith(user)).to.be.true;
});
});
שימו לב כמה הבדיקות ברורות, קצרות ונקיות – בלי חיבור ל־DB, בלי פייקים מוזרים, בלי תלויות מיותרות.
בדיקות אינטגרציה (Integration Tests) – תשתיות אמיתיות, בלי הלוגיקה
בשלב הזה אנחנו רוצים לבדוק אם הרפוזיטורי שלנו באמת עובד מול מסד הנתונים – לדוגמה MongoDB.
// test/userRepository.integration.test.ts
import { expect } from "chai";
import { MongoClient } from "mongodb";
import { MongoUserRepository } from "../infrastructure/mongoUserRepository";
describe("MongoUserRepository", () => {
let client: MongoClient;
let repo: MongoUserRepository;
before(async () => {
client = await MongoClient.connect("DB_CONNECTION_STRING");
repo = new MongoUserRepository(client.db("TEST_DB"));
});
after(async () => {
await client.db("test-db").dropDatabase();
await client.close();
});
it("should save and retrieve a user", async () => {
const user = { id: "789", email: "[email protected]" };
await repo.save(user);
const result = await repo.findByEmail(user.email);
expect(result).to.deep.equal(user);
});
});
בבדיקות כאלה אנחנו לא בודקים לוגיקה עסקית, אלא מוודאים שהתשתית מתפקדת – דהיינו, שה־DB שומר את המידע כמו שצריך, האובייקטים חוזרים בפורמט הנכון, וכו'.
טיפים לבדיקה נכונה ב־Clean Architecture
✅ שמרו על בדיקות יחידה "טהורות" – בלי DB, בלי רשת, בלי תלויות חיצוניות.
✅ השתמשו ב־Mocks פשוטים עם sinon.stub()
ולא ב־Mocking מטורף.
✅ בדיקות אינטגרציה רק איפה שצריך – תשתית, חיבורים חיצוניים.
✅ אל תערבבו שכבות בטסט – כל טסט בודק רק שכבה אחת.
טעויות נפוצות שכדאי להימנע מהן
❌ כתיבה של טסט שמריץ Use Case ובו זמנית מתחבר ל־DB או רשת – תוצאה: בדיקה איטית, שברירית, ומבלבלת.
❌ בדיקות שלא ברורות מה הן בודקות – "עובד" אבל לא ברור מה נבדק באמת.
❌ שימוש ב־Mocks שמתחילים להיראות כמו קוד אמיתי – אם אתם כותבים Class מלא רק בשביל לבדוק – עצרו רגע.
לסיכום
כשמתכננים את המערכת לפי Clean Architecture – הבדיקות הופכות להיות תענוג.
הלוגיקה העסקית שלנו מבודדת, ולכן קל לבדוק אותה.
הרפוזיטוריז מוגדרים בממשקים – אז אפשר לממש אותם עם DB אמיתי או דמה.
והבדיקות שלנו אומרות בדיוק מה צריך לבדוק – לא יותר, לא פחות.
וזה בדיוק מה שאנחנו רוצים:
קוד ברור.
בדיקות ברורות.
ושקט נפשי כשאנחנו עושים שינויים.
רוצה לשתף?
איך אתם ניגשים לבדיקות בפרויקטים שלכם?
עד כמה הבדיקות שלכם מופרדות מהתשתיות?
מה גרם לכם להתחיל לכתוב טסטים כמו שצריך?
כתבו לי בתגובות – תמיד שמח ללמוד מכם גם.