Türkçe

Sağlam ve güvenilir yazılım geliştirmek için test stratejinizde mock fonksiyonlarını etkili bir şekilde kullanmayı öğrenin. Bu kılavuz, pratik örneklerle mock'ların ne zaman, neden ve nasıl uygulanacağını kapsar.

Mock Fonksiyonlar: Geliştiriciler için Kapsamlı Bir Rehber

Yazılım geliştirme dünyasında, sağlam ve güvenilir kod yazmak her şeyden önemlidir. Bu hedefe ulaşmak için kapsamlı testler hayati önem taşır. Özellikle birim testi, bireysel bileşenleri veya fonksiyonları izolasyon içinde test etmeye odaklanır. Ancak, gerçek dünya uygulamaları genellikle karmaşık bağımlılıklar içerir ve bu da birimleri tamamen izole bir şekilde test etmeyi zorlaştırır. İşte bu noktada mock fonksiyonlar devreye girer.

Mock Fonksiyonlar Nedir?

Bir mock fonksiyon, testlerinizde kullanabileceğiniz gerçek bir fonksiyonun simüle edilmiş bir versiyonudur. Gerçek fonksiyonun mantığını yürütmek yerine, bir mock fonksiyon, davranışını kontrol etmenize, nasıl çağrıldığını gözlemlemenize ve geri dönüş değerlerini tanımlamanıza olanak tanır. Bunlar bir tür test ikamesidir (test double).

Şöyle düşünün: bir arabanın motorunu (test edilen birim) test ettiğinizi hayal edin. Motor, yakıt enjeksiyon sistemi ve soğutma sistemi gibi çeşitli diğer bileşenlere dayanır. Motor testi sırasında gerçek yakıt enjeksiyon ve soğutma sistemlerini çalıştırmak yerine, davranışlarını simüle eden mock sistemler kullanabilirsiniz. Bu, motoru izole etmenize ve özellikle performansına odaklanmanıza olanak tanır.

Mock fonksiyonlar şu konularda güçlü araçlardır:

Mock Fonksiyonlar Ne Zaman Kullanılır?

Mocks en çok şu durumlarda kullanışlıdır:

1. Harici Bağımlılıklara Sahip Birimleri İzole Etme

Test ettiğiniz birim harici servislere, veritabanlarına, API'lere veya diğer bileşenlere bağımlı olduğunda, test sırasında gerçek bağımlılıkları kullanmak birkaç soruna yol açabilir:

Örnek: Uzak bir API'den kullanıcı verilerini alan bir fonksiyonu test ettiğinizi düşünün. Test sırasında gerçek API çağrıları yapmak yerine, API yanıtını simüle etmek için bir mock fonksiyon kullanabilirsiniz. Bu, harici API'nin kullanılabilirliğine veya performansına güvenmeden fonksiyonun mantığını test etmenize olanak tanır. Bu, özellikle API'nin her istek için oran sınırları veya ilişkili maliyetleri olduğunda önemlidir.

2. Karmaşık Etkileşimleri Test Etme

Bazı durumlarda, test ettiğiniz birim diğer bileşenlerle karmaşık şekillerde etkileşime girebilir. Mock fonksiyonlar, bu etkileşimleri gözlemlemenize ve doğrulamanıza olanak tanır.

Örnek: Ödeme işlemlerini yürüten bir fonksiyon düşünün. Bu fonksiyon bir ödeme ağ geçidi, bir veritabanı ve bir bildirim servisi ile etkileşime girebilir. Mock fonksiyonları kullanarak, fonksiyonun ödeme ağ geçidini doğru işlem detaylarıyla çağırdığını, veritabanını işlem durumuyla güncellediğini ve kullanıcıya bir bildirim gönderdiğini doğrulayabilirsiniz.

3. Hata Koşullarını Simüle Etme

Hata yönetimini test etmek, uygulamanızın sağlamlığını sağlamak için çok önemlidir. Mock fonksiyonlar, gerçek bir ortamda yeniden üretilmesi zor veya imkansız olan hata koşullarını simüle etmeyi kolaylaştırır.

Örnek: Bir bulut depolama servisine dosya yükleyen bir fonksiyonu test ettiğinizi varsayalım. Yükleme işlemi sırasında bir ağ hatasını simüle etmek için bir mock fonksiyon kullanabilirsiniz. Bu, fonksiyonun hatayı doğru bir şekilde ele aldığını, yüklemeyi yeniden denediğini veya kullanıcıyı bilgilendirdiğini doğrulamanıza olanak tanır.

4. Asenkron Kodu Test Etme

Geri çağırmalar (callback), vaatler (promise) veya async/await kullanan kod gibi asenkron kodları test etmek zor olabilir. Mock fonksiyonlar, asenkron işlemlerin zamanlamasını ve davranışını kontrol etmenize yardımcı olabilir.

Örnek: Asenkron bir istekle sunucudan veri alan bir fonksiyonu test ettiğinizi düşünün. Sunucu yanıtını simüle etmek ve yanıtın ne zaman döndürüleceğini kontrol etmek için bir mock fonksiyon kullanabilirsiniz. Bu, fonksiyonun farklı yanıt senaryolarını ve zaman aşımlarını nasıl ele aldığını test etmenize olanak tanır.

5. İstenmeyen Yan Etkileri Önleme

Bazen, test sırasında gerçek bir fonksiyonu çağırmak, bir veritabanını değiştirmek, e-posta göndermek veya harici işlemleri tetiklemek gibi istenmeyen yan etkilere neden olabilir. Mock fonksiyonlar, gerçek fonksiyonu kontrollü bir simülasyonla değiştirmenize izin vererek bu yan etkileri önler.

Örnek: Yeni kullanıcılara hoş geldin e-postaları gönderen bir fonksiyonu test ediyorsunuz. Bir mock e-posta servisi kullanarak, e-posta gönderme işlevinin test paketiniz çalışırken gerçek kullanıcılara e-posta göndermemesini sağlayabilirsiniz. Bunun yerine, fonksiyonun doğru bilgilerle e-postayı göndermeye çalıştığını doğrulayabilirsiniz.

Mock Fonksiyonlar Nasıl Kullanılır?

Mock fonksiyonları kullanmak için izlenecek özel adımlar, kullandığınız programlama diline ve test çerçevesine bağlıdır. Ancak, genel süreç tipik olarak aşağıdaki adımları içerir:

  1. Bağımlılıkları Belirleme: Hangi harici bağımlılıkları mock'lamanız gerektiğini belirleyin.
  2. Mock Nesneleri Oluşturma: Gerçek bağımlılıkları değiştirmek için mock nesneleri veya fonksiyonları oluşturun. Bu mock'lar genellikle `called`, `returnValue` ve `callArguments` gibi özelliklere sahip olacaktır.
  3. Mock Davranışını Yapılandırma: Mock fonksiyonlarının geri dönüş değerleri, hata koşulları ve çağrı sayısı gibi davranışlarını tanımlayın.
  4. Mock'ları Enjekte Etme: Test ettiğiniz birimdeki gerçek bağımlılıkları mock nesneleriyle değiştirin. Bu genellikle bağımlılık enjeksiyonu kullanılarak yapılır.
  5. Testi Yürütme: Testinizi çalıştırın ve test edilen birimin mock fonksiyonlarla nasıl etkileşime girdiğini gözlemleyin.
  6. Etkileşimleri Doğrulama: Mock fonksiyonların beklenen argümanlarla, geri dönüş değerleriyle ve sayıda çağrıldığını doğrulayın.
  7. Orijinal İşlevselliği Geri Yükleme: Testten sonra, mock nesnelerini kaldırarak ve gerçek bağımlılıklara geri dönerek orijinal işlevselliği geri yükleyin. Bu, diğer testler üzerindeki yan etkileri önlemeye yardımcı olur.

Farklı Dillerde Mock Fonksiyon Örnekleri

İşte popüler programlama dillerinde ve test çerçevelerinde mock fonksiyonları kullanma örnekleri:

Jest ile JavaScript

Jest, mock fonksiyonlar için yerleşik destek sağlayan popüler bir JavaScript test çerçevesidir.

// Test edilecek fonksiyon
function fetchData(callback) {
  setTimeout(() => {
    callback('Sunucudan gelen veri');
  }, 100);
}

// Test senaryosu
test('fetchData geri çağırma fonksiyonunu doğru veriyle çağırır', (done) => {
  const mockCallback = jest.fn();
  fetchData(mockCallback);

  setTimeout(() => {
    expect(mockCallback).toHaveBeenCalledWith('Sunucudan gelen veri');
    done();
  }, 200);
});

Bu örnekte, `jest.fn()` gerçek geri çağırma fonksiyonunun yerini alan bir mock fonksiyon oluşturur. Test, mock fonksiyonunun `toHaveBeenCalledWith()` kullanarak doğru veriyle çağrıldığını doğrular.

Modülleri kullanan daha gelişmiş bir örnek:

// user.js
import { getUserDataFromAPI } from './api';

export async function displayUserName(userId) {
  const userData = await getUserDataFromAPI(userId);
  return userData.name;
}

// api.js
export async function getUserDataFromAPI(userId) {
  // API çağrısını simüle et
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({ id: userId, name: 'John Doe' });
    }, 50);
  });
}

// user.test.js
import { displayUserName } from './user';
import * as api from './api';

describe('displayUserName', () => {
  it('kullanıcı adını göstermelidir', async () => {
    // getUserDataFromAPI fonksiyonunu mock'la
    const mockGetUserData = jest.spyOn(api, 'getUserDataFromAPI');
    mockGetUserData.mockResolvedValue({ id: 123, name: 'Mocklanmış İsim' });

    const userName = await displayUserName(123);
    expect(userName).toBe('Mocklanmış İsim');

    // Orijinal fonksiyonu geri yükle
    mockGetUserData.mockRestore();
  });
});

Burada, `jest.spyOn`, `./api` modülünden içe aktarılan `getUserDataFromAPI` fonksiyonu için bir mock fonksiyonu oluşturmak üzere kullanılır. `mockResolvedValue`, mock'un geri dönüş değerini belirtmek için kullanılır. `mockRestore`, diğer testlerin yanlışlıkla mock'lanmış sürümü kullanmamasını sağlamak için önemlidir.

pytest ve unittest.mock ile Python

Python, `unittest.mock` (yerleşik) ve pytest ile basitleştirilmiş kullanım için `pytest-mock` gibi kütüphaneler de dahil olmak üzere mock'lama için çeşitli kütüphaneler sunar.

# Test edilecek fonksiyon
def get_data_from_api(url):
    # Gerçek bir senaryoda bu, bir API çağrısı yapardı
    # Basitlik adına, bir API çağrısını simüle ediyoruz
    if url == "https://example.com/api":
        return {"data": "API verisi"}
    else:
        return None

def process_data(url):
    data = get_data_from_api(url)
    if data:
        return data["data"]
    else:
        return "Veri bulunamadı"

# unittest.mock kullanarak test senaryosu
import unittest
from unittest.mock import patch

class TestProcessData(unittest.TestCase):
    @patch('__main__.get_data_from_api') # Ana modüldeki get_data_from_api'yi değiştir
    def test_process_data_success(self, mock_get_data_from_api):
        # Mock'u yapılandır
        mock_get_data_from_api.return_value = {"data": "Mocklanmış veri"}

        # Test edilen fonksiyonu çağır
        result = process_data("https://example.com/api")

        # Sonucu doğrula
        self.assertEqual(result, "Mocklanmış veri")
        mock_get_data_from_api.assert_called_once_with("https://example.com/api")

    @patch('__main__.get_data_from_api')
    def test_process_data_failure(self, mock_get_data_from_api):
        mock_get_data_from_api.return_value = None
        result = process_data("https://example.com/api")
        self.assertEqual(result, "Veri bulunamadı")

if __name__ == '__main__':
    unittest.main()

Bu örnek, `get_data_from_api` fonksiyonunu bir mock ile değiştirmek için `unittest.mock.patch` kullanır. Test, mock'u belirli bir değer döndürecek şekilde yapılandırır ve ardından `process_data` fonksiyonunun beklenen sonucu döndürdüğünü doğrular.

İşte `pytest-mock` kullanarak aynı örnek:

# pytest versiyonu
import pytest

def get_data_from_api(url):
    # Gerçek bir senaryoda bu, bir API çağrısı yapardı
    # Basitlik adına, bir API çağrısını simüle ediyoruz
    if url == "https://example.com/api":
        return {"data": "API verisi"}
    else:
        return None

def process_data(url):
    data = get_data_from_api(url)
    if data:
        return data["data"]
    else:
        return "Veri bulunamadı"


def test_process_data_success(mocker):
    mocker.patch('__main__.get_data_from_api', return_value={"data": "Mocklanmış veri"})
    result = process_data("https://example.com/api")
    assert result == "Mocklanmış veri"


def test_process_data_failure(mocker):
    mocker.patch('__main__.get_data_from_api', return_value=None)
    result = process_data("https://example.com/api")
    assert result == "Veri bulunamadı"

`pytest-mock` kütüphanesi, pytest testleri içinde mock'ların oluşturulmasını ve yapılandırılmasını basitleştiren bir `mocker` fikstürü sağlar.

Mockito ile Java

Mockito, Java için popüler bir mock'lama çerçevesidir.

import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

interface DataFetcher {
    String fetchData(String url);
}

class DataProcessor {
    private final DataFetcher dataFetcher;

    public DataProcessor(DataFetcher dataFetcher) {
        this.dataFetcher = dataFetcher;
    }

    public String processData(String url) {
        String data = dataFetcher.fetchData(url);
        if (data != null) {
            return "İşlendi: " + data;
        } else {
            return "Veri yok";
        }
    }
}

public class DataProcessorTest {

    @Test
    public void testProcessDataSuccess() {
        // Bir mock DataFetcher oluştur
        DataFetcher mockDataFetcher = mock(DataFetcher.class);

        // Mock'u yapılandır
        when(mockDataFetcher.fetchData("https://example.com/api")).thenReturn("API Verisi");

        // DataProcessor'ı mock ile oluştur
        DataProcessor dataProcessor = new DataProcessor(mockDataFetcher);

        // Test edilen fonksiyonu çağır
        String result = dataProcessor.processData("https://example.com/api");

        // Sonucu doğrula
        assertEquals("İşlendi: API Verisi", result);

        // Mock'un çağrıldığını doğrula
        verify(mockDataFetcher).fetchData("https://example.com/api");
    }

    @Test
    public void testProcessDataFailure() {
        DataFetcher mockDataFetcher = mock(DataFetcher.class);
        when(mockDataFetcher.fetchData("https://example.com/api")).thenReturn(null);

        DataProcessor dataProcessor = new DataProcessor(mockDataFetcher);
        String result = dataProcessor.processData("https://example.com/api");
        assertEquals("Veri yok", result);
        verify(mockDataFetcher).fetchData("https://example.com/api");
    }
}

Bu örnekte, `Mockito.mock()`, `DataFetcher` arayüzü için bir mock nesnesi oluşturur. `when()`, mock'un geri dönüş değerini yapılandırmak için kullanılır ve `verify()`, mock'un beklenen argümanlarla çağrıldığını doğrulamak için kullanılır.

Mock Fonksiyonları Kullanmak için En İyi Uygulamalar

Mock Fonksiyonlarına Alternatifler

Mock fonksiyonlar güçlü bir araç olsa da, her zaman en iyi çözüm değildir. Bazı durumlarda, diğer teknikler daha uygun olabilir:

Sonuç

Mock fonksiyonlar, birimleri izole etmenize, davranışı kontrol etmenize, hata koşullarını simüle etmenize ve asenkron kodu test etmenize olanak tanıyarak etkili birim testleri yazmak için temel bir araçtır. En iyi uygulamaları takip ederek ve alternatifleri anlayarak, daha sağlam, güvenilir ve sürdürülebilir yazılımlar oluşturmak için mock fonksiyonlardan yararlanabilirsiniz. Kapsamlı ve etkili bir test stratejisi oluşturmak için her durum için doğru test tekniğini seçmeyi ve ödünleşimleri göz önünde bulundurmayı unutmayın, dünyanın neresinde geliştirme yapıyor olursanız olun.