🧪 Testing — Öğrenme & Mülakat Rehberi
Unit/Integration/Contract/Performance testleri, Testcontainers ve WireMock ile gerçekçi izolasyon; coverage ve mutation ile kalite ölçümü. Hem sahada kullan hem de mülakatta ışılda.
🔎 Genel Bakış
- Hızlı geri bildirim için unit test, gerçekçi güven için integration.
- Dış servis bağımlılıklarını stub (WireMock) veya contract ile yönet.
- DB/Queue gibi gerçek bağımlılıklar için Testcontainers.
- Kaliteyi coverage + mutation (pitest) ile izle.
- Performans için Gatling/JMeter ve SLO/SLA metrikleri belirle.
🧱 Test Piramidi
| Katman | Hedef | Özellik | Örnek |
|---|---|---|---|
| Unit | Fonksiyonel doğruluk | Milisaniye, izole | JUnit5 + Mockito |
| Service/Slice | Katman doğrulama | Hızlı, kısmi Spring konteyner | @DataJpaTest, @WebMvcTest |
| Integration | Gerçek bağımlılıklar | Dakika, daha yavaş | SpringBootTest + Testcontainers |
| Contract | API uyumu | Tüketici/sağlayıcı anlaşması | Pact / Spring Cloud Contract |
| Performance | Kapsite/latency | Yük/stress/soak | Gatling, JMeter |
🧩 Unit Test (JUnit 5, AssertJ, Mockito)
İyi Uygulamalar
- Hızlı, deterministik, izole. Random varsa
seedsabitle. - Test isimleri davranış anlatmalı:
shouldCalculateTotal_whenItemsGiven() - Arrange–Act–Assert (AAA), tek assert konusu.
class PriceCalculatorTest {
private final PriceCalculator calc = new PriceCalculator(new TaxService());
@Test
void shouldCalculateTotal_whenItemsGiven() {
var items = List.of(new Item("A", 100), new Item("B", 50));
var total = calc.total(items);
assertThat(total).isEqualTo(159.0); // 150 + %6 vergi + yuvarlama
}
@Test
void shouldThrow_whenEmptyItems() {
assertThatThrownBy(() -> calc.total(List.of()))
.isInstanceOf(IllegalArgumentException.class);
}
}
Mockito ile İzolasyon
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock PaymentGateway gateway;
@InjectMocks OrderService service;
@Test
void shouldMarkPaid_whenGatewayOk() {
when(gateway.pay(any())).thenReturn(PaymentResult.ok());
var status = service.pay(123L);
verify(gateway).pay(any());
assertThat(status).isEqualTo(Status.PAID);
}
}
🍰 Spring Slice Testleri
@WebMvcTest: Sadece MVC katmanı (Controller, Advice). Service/Repo mocklanır.@DataJpaTest: JPA/Hibernate + in-memory DB (H2 tuzaklarına dikkat), veya Testcontainers ile gerçek sürüm.
@WebMvcTest(ProductController.class)
class ProductControllerWebTest {
@Autowired MockMvc mvc;
@MockBean ProductService service;
@Test
void shouldReturn200_withList() throws Exception {
when(service.list()).thenReturn(List.of(new Product(1L,"Pen")));
mvc.perform(get("/products"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].name").value("Pen"));
}
}
@DataJpaTest
class ProductRepositoryTest {
@Autowired TestEntityManager em;
@Autowired ProductRepository repo;
@Test
void shouldFindByName() {
em.persist(new Product(null,"Pen"));
var p = repo.findByName("Pen");
assertThat(p).isPresent();
}
}
🔗 Integration Test (SpringBootTest)
- Tüm konteyner (port açmadan
webEnvironment = MOCKhız kazandırır). - Gerçek DB/Queue/HTTP için Testcontainers & WireMock.
- Test verisi için
@Sql/ Factory/Builder pattern.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class OrderFlowIT {
@Autowired MockMvc mvc;
@Test
void endToEnd_shouldWork() throws Exception {
mvc.perform(post("/orders").contentType("application/json")
.content("{\"sku\":\"A1\",\"qty\":2}"))
.andExpect(status().isCreated());
mvc.perform(get("/orders/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("CREATED"));
}
}
🐳 Testcontainers
PostgreSQL ve Kafka Örneği
@Testcontainers
@SpringBootTest
class BillingIT {
@Container static PostgreSQLContainer<?> pg =
new PostgreSQLContainer<>("postgres:16-alpine")
.withDatabaseName("appdb")
.withUsername("app")
.withPassword("app");
@Container static KafkaContainer kafka =
new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.6.1"));
@DynamicPropertySource
static void register(DynamicPropertyRegistry r){
r.add("spring.datasource.url", pg::getJdbcUrl);
r.add("spring.datasource.username", pg::getUsername);
r.add("spring.datasource.password", pg::getPassword);
r.add("spring.kafka.bootstrap-servers", kafka::getBootstrapServers);
}
@Test void contextLoads(){ /* ... */ }
}
- Reusable containers için
TESTCONTAINERS_REUSE_ENABLE=trueve~/.testcontainers.properties. - CI’da Docker runner gerekir.
🧵 WireMock (HTTP Stub/Mock)
Standalone (JUnit) Kullanım
@ExtendWith(WireMockExtension.class)
@WireMockSettings(port = 8089)
class ExternalClientTest {
@RegisterExtension static WireMockExtension wm =
WireMockExtension.newInstance().options(wireMockConfig().port(8089)).build();
@Test
void shouldCallStub() {
wm.stubFor(get(urlEqualTo("/rates?base=USD"))
.willReturn(okJson("{\"TRY\": 33.2}")));
var rate = new RatesClient("http://localhost:8089").getTryRate("USD");
assertThat(rate).isEqualTo(33.2);
}
}
JSON Mapping Örneği
{
"request": { "method": "GET", "urlPath": "/rates", "queryParameters": { "base": { "equalTo": "USD" } } },
"response": { "status": 200, "headers": { "Content-Type": "application/json" }, "jsonBody": {"TRY":33.2} }
}
fixedDelayMilliseconds ile gecikme simüle et; hata senaryolarını (503, timeout) mutlaka test et.
📜 Contract Testing (Pact / Spring Cloud Contract)
Consumer-Driven Contract — Pact
@Pact(consumer = "cart-service")
public RequestResponsePact getProductPact(PactDslWithProvider p) {
return p.given("product exists")
.uponReceiving("get product by id")
.path("/products/42").method("GET")
.willRespondWith().status(200)
.headers(Map.of("Content-Type","application/json"))
.body("{\"id\":42,\"name\":\"Pen\"}")
.toPact();
}
@PactTestFor(providerName="product-service", port="9090")
@Test void verifyContract(MockServer server){
ProductClient c = new ProductClient(server.getUrl());
Product p = c.get(42L);
assertThat(p.getName()).isEqualTo("Pen");
}
Spring Cloud Contract — Producer
Contract.make {
request {
method 'GET'
url '/products/42'
}
response {
status 200
headers { contentType(applicationJson()) }
body(id: 42, name: 'Pen')
}
}
Producer tarafı sözleşmeden stub üretir; consumer testlerinde bu stub kullanılır. CI’da sözleşme doğrulaması kırmızıya düşürür.
🚀 Performance Test (Gatling / JMeter)
Hedefler
- Latency: p50/p95/p99; Throughput: RPS; Error Rate & Resource: CPU/RAM/GC/IO.
- SLA/SLO tanımla (örn.
p95 < 300ms).
Gatling Örneği
class ProductSimulation extends Simulation {
val httpConf = http.baseUrl("http://localhost:8080")
val scn = scenario("List products")
.exec(http("GET /products").get("/products").check(status.is(200)))
setUp(
scn.inject(
rampUsersPerSec(0) to 200 during (60 seconds),
constantUsersPerSec(200) during (120 seconds)
)
).protocols(httpConf)
}
📊 Coverage & Mutation
JaCoCo (Line/Branch)
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
<executions>
<execution>
<goals><goal>prepare-agent</goal></goals>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals><goal>report</goal></goals>
</execution>
</executions>
</plugin>
Mutation Testing (PIT)
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.15.8</version>
<configuration>
<targetClasses><param>com.acme.*</param></targetClasses>
<targetTests><param>com.acme.*</param></targetTests>
<mutators><mutator>STRONGER</mutator></mutators>
<threads>4</threads>
</configuration>
</plugin>
Not: Yüksek coverage, iyi test demek değildir; mutation score gerçek kaliteyi gösterir.
🤖 CI Entegrasyonu
- Stages: unit → slice → integration → contract → performance (opsiyonel nightly).
- Raporlar: JUnit XML, JaCoCo HTML/XML, PIT raporu, Gatling grafikleri.
name: build
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
docker: { }
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with: { distribution: temurin, java-version: '21' }
- name: Run tests
run: mvn -B -DskipITs=false verify
- name: Upload reports
uses: actions/upload-artifact@v4
with:
name: reports
path: |
**/target/site/jacoco/*
**/target/pit-reports/*
**/target/gatling/*
❓ Mülakat Soruları & Kısa Yanıtlar
1) Unit vs Integration farkı?
Unit izole ve hızlı; integration gerçek bağımlılıklarla sistem davranışını doğrular.
2) H2 yerine neden Testcontainers?
Prod DB davranış farklılıklarını (tip, index, fonksiyon, SQL dialekt) yakalamak için.
3) WireMock vs Contract Testing?
WireMock lokal stub; contract (Pact/SCC) iki tarafın anlaştığı API sözleşmesini CI'da garanti eder.
4) Mutation testing neyi ölçer?
Testlerin hataları yakalama gücünü (öldürülen mutant oranı); satır kapsamından daha anlamlıdır.
5) Flaky test nasıl azaltılır?
Deterministik saat/tarih, sabit seed, async beklemelerde timeouts/polling, izolasyon ve temiz test verisi.
6) Performance testte hangi metrikler?
p50/p95/p99 latency, throughput (RPS), error rate, CPU/RAM/GC, DB bekleme süreleri.