Vue.js Uygulamalarında Test Stratejileri ve Best Practices
Modern web uygulamalarında test yazma, kodun güvenilirliğini ve sürdürülebilirliğini sağlamak için kritik bir öneme sahiptir. Vue.js ekosisteminde test yazma, zengin araç seti ve framework desteği sayesinde oldukça etkili bir şekilde yapılabilmektedir.
Test Piramidi ve Vue.js'de Test Türleri
Test piramidi, farklı test türlerinin ideal dağılımını gösteren bir modeldir. Vue.js uygulamalarında üç temel test türü bulunur:
- Unit Tests: Bileşenlerin ve fonksiyonların izole edilmiş testleri
- Component Tests: Bileşenlerin entegrasyon testleri
- End-to-End Tests: Tüm uygulamanın gerçek kullanıcı senaryolarıyla test edilmesi
Unit Testing ile Başlayalım
Unit testler, kodunuzun en küçük parçalarını test etmenizi sağlar. Vue.js'de Vitest kullanarak etkili unit testler yazabilirsiniz.
// composables/useCounter.ts import { ref } from 'vue' export function useCounter(initialValue = 0) { const count = ref(initialValue) const increment = () => { count.value++ } const decrement = () => { count.value-- } const reset = () => { count.value = initialValue } return { count, increment, decrement, reset } } // tests/composables/useCounter.test.ts import { describe, it, expect } from 'vitest' import { useCounter } from '@/composables/useCounter' describe('useCounter', () => { it('should initialize with default value', () => { const { count } = useCounter() expect(count.value).toBe(0) }) it('should initialize with provided value', () => { const { count } = useCounter(10) expect(count.value).toBe(10) }) it('should increment counter', () => { const { count, increment } = useCounter(0) increment() expect(count.value).toBe(1) }) it('should decrement counter', () => { const { count, decrement } = useCounter(0) decrement() expect(count.value).toBe(-1) }) it('should reset counter', () => { const { count, increment, reset } = useCounter(0) increment() increment() reset() expect(count.value).toBe(0) }) })
Component Testing: Vue Test Utils ile Bileşen Testleri
Vue Test Utils, Vue bileşenlerini test etmek için resmi test yardımcı kütüphanesidir.
// components/TodoItem.vue <script setup lang="ts"> interface Props { todo: { id: number text: string completed: boolean } } const props = defineProps<Props>() const emit = defineEmits<{ (e: 'toggle', id: number): void (e: 'delete', id: number): void }>() </script> <template> <div class="todo-item" :class="{ completed: todo.completed }"> <input type="checkbox" :checked="todo.completed" @change="emit('toggle', todo.id)" /> <span>{{ todo.text }}</span> <button @click="emit('delete', todo.id)">Sil</button> </div> </template> // tests/components/TodoItem.test.ts import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import TodoItem from '@/components/TodoItem.vue' describe('TodoItem.vue', () => { const todo = { id: 1, text: 'Test Todo', completed: false } it('renders todo text', () => { const wrapper = mount(TodoItem, { props: { todo } }) expect(wrapper.text()).toContain('Test Todo') }) it('emits toggle event when checkbox is clicked', async () => { const wrapper = mount(TodoItem, { props: { todo } }) await wrapper.find('input[type="checkbox"]').trigger('change') expect(wrapper.emitted('toggle')).toBeTruthy() expect(wrapper.emitted('toggle')?.[0]).toEqual([1]) }) it('emits delete event when delete button is clicked', async () => { const wrapper = mount(TodoItem, { props: { todo } }) await wrapper.find('button').trigger('click') expect(wrapper.emitted('delete')).toBeTruthy() expect(wrapper.emitted('delete')?.[0]).toEqual([1]) }) it('applies completed class when todo is completed', () => { const completedTodo = { ...todo, completed: true } const wrapper = mount(TodoItem, { props: { todo: completedTodo } }) expect(wrapper.classes()).toContain('completed') }) })
End-to-End Testing: Cypress ile Gerçek Kullanıcı Senaryoları
E2E testler, uygulamanızı gerçek bir tarayıcıda test etmenizi sağlar.
// cypress/e2e/todo.cy.ts describe('Todo App', () => { beforeEach(() => { cy.visit('/') }) it('should add new todo', () => { const todoText = 'Buy groceries' cy.get('[data-test="new-todo"]') .type(`${todoText}{enter}`) cy.get('[data-test="todo-list"]') .should('contain', todoText) }) it('should toggle todo completion', () => { // Yeni todo ekle cy.get('[data-test="new-todo"]') .type('Test todo{enter}') // Checkbox'ı işaretle cy.get('[data-test="todo-item"]') .first() .find('input[type="checkbox"]') .click() // Completed class'ının eklendiğini kontrol et cy.get('[data-test="todo-item"]') .first() .should('have.class', 'completed') }) it('should delete todo', () => { // Yeni todo ekle const todoText = 'Delete me' cy.get('[data-test="new-todo"]') .type(`${todoText}{enter}`) // Silme butonuna tıkla cy.get('[data-test="todo-item"]') .first() .find('button') .click() // Todo'nun silindiğini kontrol et cy.get('[data-test="todo-list"]') .should('not.contain', todoText) }) })
Test Best Practices
1. Test Organizasyonu
Dosya Yapısı:
tests/ ├── unit/ │ ├── composables/ │ └── utils/ ├── components/ └── e2e/
Naming Conventions:
- Test dosyaları:
*.test.ts
veya*.spec.ts
- Test grupları: Anlamlı describe blokları
- Test case'leri: Açıklayıcı it/test blokları
- Test dosyaları:
2. Test Coverage
Coverage Hedefleri:
- Unit Tests: %80+
- Component Tests: %60+
- E2E Tests: Kritik kullanıcı yolları
Coverage Raporlama:
{ "scripts": { "test:coverage": "vitest run --coverage" }, "vitest": { "coverage": { "reporter": ["text", "json", "html"], "exclude": ["node_modules/", "dist/"] } } }
3. Mocking ve Stubbing
// services/api.ts import { vi } from 'vitest' // API servisini mock'la vi.mock('@/services/api', () => ({ fetchTodos: vi.fn().mockResolvedValue([ { id: 1, text: 'Mock Todo', completed: false } ]) })) // Component testi it('fetches and displays todos', async () => { const wrapper = mount(TodoList) // API çağrısının tamamlanmasını bekle await flushPromises() expect(wrapper.text()).toContain('Mock Todo') })
4. Test Driven Development (TDD)
TDD yaklaşımı ile test yazma:
- Red: Önce başarısız test yaz
- Green: Minimum kod ile testi geçir
- Refactor: Kodu iyileştir, testlerin geçtiğinden emin ol
// Red: Başarısız test describe('useAuth', () => { it('should authenticate user with valid credentials', async () => { const { login, user, error } = useAuth() await login('test@example.com', 'password123') expect(user.value).toBeTruthy() expect(error.value).toBeNull() }) }) // Green: Minimum implementasyon export function useAuth() { const user = ref(null) const error = ref(null) const login = async (email: string, password: string) => { user.value = { email } } return { login, user, error } } // Refactor: İyileştirme export function useAuth() { const user = ref<User | null>(null) const error = ref<Error | null>(null) const login = async (email: string, password: string) => { try { const response = await api.login(email, password) user.value = response.data error.value = null } catch (e) { user.value = null error.value = e instanceof Error ? e : new Error('Login failed') } } return { login, user, error } }
Performans ve Test Optimizasyonu
1. Test Hızını Artırma
Paralel Test Çalıştırma:
{ "vitest": { "threads": true, "maxThreads": 4, "minThreads": 1 } }
Test İzolasyonu:
beforeEach(() => { // Her test öncesi state'i sıfırla vi.clearAllMocks() localStorage.clear() })
2. CI/CD Entegrasyonu
# .github/workflows/test.yml name: Test on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup Node.js uses: actions/setup-node@v2 with: node-version: '18' - name: Install dependencies run: npm ci - name: Run unit tests run: npm run test:unit - name: Run e2e tests run: npm run test:e2e - name: Upload coverage uses: codecov/codecov-action@v2
Sonuç
Vue.js uygulamalarında test yazma, uygulamanızın kalitesini ve güvenilirliğini artıran önemli bir pratiktir. Bu rehberde öğrendiğimiz:
Test Türleri ve Araçlar:
- Unit Tests (Vitest)
- Component Tests (Vue Test Utils)
- E2E Tests (Cypress)
Best Practices:
- Test organizasyonu
- Coverage hedefleri
- Mocking stratejileri
- TDD yaklaşımı
Optimizasyon:
- Performans iyileştirmeleri
- CI/CD entegrasyonu
sayesinde daha güvenilir ve sürdürülebilir Vue.js uygulamaları geliştirebilirsiniz.