Το πότε, πώς, πού και κυρίως γιατί της αυτοματοποιημένης δοκιμής λογισμικού
Η αυτοματοποιημένη δοκιμή (automated testing) δεν είναι πολυτέλεια, ούτε «nice to have». Είναι μηχανισμός ανάδρασης, εργαλείο σχεδίασης και ασφαλιστική δικλείδα για τη βιωσιμότητα ενός συστήματος.
Στην πράξη, τα tests χωρίζονται συνήθως σε τρία βασικά επίπεδα:
- Unit Tests
- Integration Tests
- End-to-End (E2E) / System Tests
Δεν είναι ανταγωνιστικά· είναι συμπληρωματικά. Το λάθος που γίνεται συχνά είναι είτε:
να επενδύουμε μόνο σε ένα επίπεδο
είτε να μπερδεύουμε τους ρόλους τους
Ας τα δούμε ένα-ένα, με παραδείγματα σε C# (.NET).
1. Unit Tests
Τι ελέγχουν
Τα Unit Tests ελέγχουν τη μικρότερη λογική μονάδα του κώδικα:
- μια μέθοδο
- μια κλάση
- έναν pure function
Χωρίς εξαρτήσεις από:
- βάσεις δεδομένων
- filesystem
- network
- clocks
- random generators
Αν κάτι δεν είναι deterministic, mockάρεται.
Γιατί είναι σημαντικά
- Είναι γρήγορα (milliseconds)
- Τρέχουν συνεχώς (IDE, CI)
- Τεκμηριώνουν συμπεριφορά
- Επιβάλλουν καλό design (SRP, low coupling)
Ένα σύστημα δύσκολο να γίνει unit tested είναι σχεδόν πάντα κακοσχεδιασμένο.
Παράδειγμα domain
Ας πούμε ότι έχουμε ένα απλό domain rule:
public class Order
{
public decimal TotalAmount { get; }
public Order(decimal totalAmount)
{
TotalAmount = totalAmount;
}
public decimal CalculateDiscount()
{
if (TotalAmount >= 1000)
return TotalAmount * 0.10m;
if (TotalAmount >= 500)
return TotalAmount * 0.05m;
return 0;
}
}
Unit Test (xUnit)
public class OrderTests
{
[Fact]
public void Orders_over_1000_get_10_percent_discount()
{
var order = new Order(1200m);
var discount = order.CalculateDiscount();
Assert.Equal(120m, discount);
}
[Fact]
public void Orders_between_500_and_999_get_5_percent_discount()
{
var order = new Order(600m);
var discount = order.CalculateDiscount();
Assert.Equal(30m, discount);
}
[Fact]
public void Orders_below_500_get_no_discount()
{
var order = new Order(300m);
var discount = order.CalculateDiscount();
Assert.Equal(0m, discount);
}
}
Χαρακτηριστικά:
- Καμία εξάρτηση
- Καθαρό Arrange-Act-Assert
- Deterministic αποτέλεσμα
2. Integration Tests
Τι ελέγχουν
Τα Integration Tests ελέγχουν:
- τη συνεργασία πολλών components
- τη σωστή καλωδίωση (DI, mappings, persistence)
- την επικοινωνία με πραγματικές υποδομές
Εδώ ΔΕΝ mockάρουμε τα πάντα.
Αντιθέτως, θέλουμε:
- πραγματική βάση (π.χ. SQLite / Testcontainers)
- πραγματικό EF Core
- πραγματικά repositories
Γιατί είναι σημαντικά
Πιάνουν λάθη που unit tests δεν μπορούν
Εντοπίζουν:
- λάθος mappings
- migrations
- serialization issues
- misconfigured services
“Works on my machine” bugs σκοτώνονται εδώ.
Παράδειγμα: Repository + EF Core
public class OrderEntity
{
public int Id { get; set; }
public decimal TotalAmount { get; set; }
}
public class AppDbContext : DbContext
{
public DbSet<OrderEntity> Orders => Set<OrderEntity>();
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options) { }
}
public class OrderRepository
{
private readonly AppDbContext _context;
public OrderRepository(AppDbContext context)
{
_context = context;
}
public async Task AddAsync(OrderEntity order)
{
_context.Orders.Add(order);
await _context.SaveChangesAsync();
}
public async Task<OrderEntity?> GetByIdAsync(int id)
{
return await _context.Orders.FindAsync(id);
}
}
Integration Test με In-Memory SQLite
public class OrderRepositoryTests
{
[Fact]
public async Task Can_save_and_retrieve_order()
{
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlite("Filename=:memory:")
.Options;
using var context = new AppDbContext(options);
context.Database.OpenConnection();
context.Database.EnsureCreated();
var repository = new OrderRepository(context);
var order = new OrderEntity { TotalAmount = 500m };
await repository.AddAsync(order);
var savedOrder = await repository.GetByIdAsync(order.Id);
Assert.NotNull(savedOrder);
Assert.Equal(500m, savedOrder!.TotalAmount);
}
}
Σημαντικό:
Αυτό ΔΕΝ είναι unit test — και δεν πρέπει να είναι.
3. End-to-End (E2E) / System Tests
Τι ελέγχουν
Τα E2E tests ελέγχουν:
- το σύστημα από άκρη σε άκρη
- όπως το βλέπει ο τελικός χρήστης ή client
Παράδειγμα:
- HTTP request → controller
- business logic
- database
- response
Χωρίς mocks. Όλα αληθινά.
Γιατί είναι σημαντικά (και επικίνδυνα)
✅ Επιβεβαιώνουν ότι:
- το σύστημα δουλεύει πραγματικά
- τα integrations είναι σωστά
❌ Αλλά:
- είναι αργά
- είναι εύθραυστα
- δύσκολα στο debugging
Ένα failing E2E test σου λέει ότι κάτι χάλασε, όχι τι.
Παράδειγμα: ASP.NET Core API
Controller:
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
private readonly AppDbContext _context;
public OrdersController(AppDbContext context)
{
_context = context;
}
[HttpPost]
public async Task<IActionResult> Create(CreateOrderRequest request)
{
var order = new OrderEntity
{
TotalAmount = request.TotalAmount
};
_context.Orders.Add(order);
await _context.SaveChangesAsync();
return Ok(order.Id);
}
}
E2E Test με WebApplicationFactory
public class OrdersApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public OrdersApiTests(WebApplicationFactory<Program> factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task Can_create_order_via_api()
{
var payload = new
{
TotalAmount = 750
};
var response = await _client.PostAsJsonAsync("/api/orders", payload);
response.EnsureSuccessStatusCode();
var orderId = await response.Content.ReadFromJsonAsync<int>();
Assert.True(orderId > 0);
}
}
Πυραμίδα Tests (Testing Pyramid)
Ιδανική κατανομή:
▲
│ E2E (λίγα)
│
│ Integration (αρκετά)
│
│ Unit (πολλά)
▼
Κανόνας εμπειρίας:
~70% Unit
~20% Integration
~10% E2E
Πότε χρησιμοποιούμε τι
| Περίπτωση | Test |
|---|---|
| Business rules | Unit |
| Calculations | Unit |
| Repositories | Integration |
| EF mappings | Integration |
| API wiring | Integration |
| Happy path χρήστη | E2E |
| Smoke tests | E2E |
Συμπέρασμα (senior take)
- Unit tests προστατεύουν το design
- Integration tests προστατεύουν την υποδομή
- E2E tests προστατεύουν την εμπιστοσύνη
Κανένα επίπεδο δεν αντικαθιστά τα άλλα.
Όλα μαζί σχηματίζουν ένα δίχτυ ασφαλείας, όχι μια ψευδαίσθηση ποιότητας.
Top comments (0)