testing-plan.md
8,702 bytes
| 1 | # Testing — Implemented State |
|---|---|
| 2 | |
| 3 | Aligned with the Akaver testing lecture (`courses.taltech.akaver.com/web-applications-with-csharp/lectures/testing`). |
| 4 | |
| 5 | **Status:** ✅ 44 tests, all green. Local-only via `dotnet test`. **No CI/CD changes** — VPS pipeline untouched. |
| 6 | |
| 7 | --- |
| 8 | |
| 9 | ## Stack (loengust) |
| 10 | |
| 11 | | Tool | Version | Roll | |
| 12 | |---|---|---| |
| 13 | | **xUnit** | 2.9.3 | Test framework | |
| 14 | | **Moq** | 4.20.72 | `IAppUnitOfWork` + repo mocking BLL teenuste jaoks | |
| 15 | | **FluentAssertions** | 6.12.2 | Loetavad väited (`result.Should().NotBeNull()`) — pinnitud v6 (täielikult tasuta) | |
| 16 | | **Microsoft.EntityFrameworkCore.Sqlite** | 10.0.7 | SQLite in-memory DB DAL testidele (loeng eelistab seda EF InMemory provideri ees) | |
| 17 | | **coverlet.collector** | 6.0.4 | Code coverage (vaikimisi mitteaktiivne) | |
| 18 | |
| 19 | --- |
| 20 | |
| 21 | ## Project structure |
| 22 | |
| 23 | ``` |
| 24 | SplitApp/ |
| 25 | └── App.Tests/ ← üks test projekt, lisatud SplitApp.sln-i |
| 26 | ├── App.Tests.csproj ← references App.DAL.EF, App.BLL, App.Domain |
| 27 | ├── RepositoryTestBase.cs ← SQLite in-memory baasklass + SeedTripAsync helper |
| 28 | ├── SanityTest.cs ← 1 test |
| 29 | ├── Domain/ ← 8 testi |
| 30 | │ ├── LangStrTests.cs ← 7 (sealhulgas [Theory] 3 keelega) |
| 31 | │ └── TripValidationTests.cs ← 3 (IValidatableObject) |
| 32 | ├── DAL/ ← 9 testi (SQLite in-memory) |
| 33 | │ ├── TripRepositoryTests.cs ← 2 |
| 34 | │ ├── ExpenseRepositoryTests.cs ← 1 |
| 35 | │ ├── BudgetCategoryRepositoryTests.cs ← 1 |
| 36 | │ ├── TripParticipantRepositoryTests.cs ← 3 (IDOR-relevantne) |
| 37 | │ └── TripInvitationRepositoryTests.cs ← 2 |
| 38 | ├── BLL/ ← 13 testi (mocked UoW + helper) |
| 39 | │ ├── TripServiceTests.cs ← 5 (3 happy + 2 sad path) |
| 40 | │ ├── BudgetCategoryServiceTests.cs ← 2 (1 happy + 1 sad) |
| 41 | │ ├── ExpenseServiceTests.cs ← 2 (happy + edge case) |
| 42 | │ ├── SettlementServiceTests.cs ← 2 (edge cases) |
| 43 | │ ├── WishlistServiceTests.cs ← 2 (sad + happy) |
| 44 | │ └── CurrencyConverterTests.cs ← 5 (sealhulgas [Theory] 3 valuutapaariga) |
| 45 | └── Mappers/ ← 6 testi |
| 46 | ├── TripBllDtoFactoryTests.cs ← 2 (Create + round-trip) |
| 47 | ├── ExpenseBllDtoFactoryTests.cs ← 1 |
| 48 | ├── BudgetCategoryBllDtoFactoryTests.cs ← 2 |
| 49 | └── CurrencyBllDtoFactoryTests.cs ← 1 (LangStr säilumine) |
| 50 | ``` |
| 51 | |
| 52 | --- |
| 53 | |
| 54 | ## Test count summary |
| 55 | |
| 56 | | Kiht | Testid | Mida testib | |
| 57 | |---|---|---| |
| 58 | | Sanity | 1 | xUnit infra töötab | |
| 59 | | **Domain** | 8 | LangStr i18n value object + Trip `IValidatableObject` | |
| 60 | | **DAL** | 9 | Repositoorid päris EF + SQLite vastu | |
| 61 | | **BLL** | 13 | Teenuste äriloogika + IDOR + edge case-id + helper | |
| 62 | | **Mappers** | 6 | Domain ↔ BllDto skalaarsete väljade säilumine | |
| 63 | | **KOKKU** | **44** | | |
| 64 | |
| 65 | --- |
| 66 | |
| 67 | ## Coverage matrix — kõik Onion-i ringid testitud |
| 68 | |
| 69 | ``` |
| 70 | WebApp ──┐ |
| 71 | ├──► App.BLL ────────────► App.Domain ──► Base.Domain |
| 72 | App.DTO ─┘ ✓ TripService ✓ Trip + Validate() ✓ LangStr |
| 73 | ✓ ExpenseService ✓ Expense entity ✓ BaseEntity |
| 74 | ✓ BudgetCategoryService ✓ BudgetCategory |
| 75 | ✓ SettlementService ✓ TripParticipant |
| 76 | ✓ WishlistService ✓ TripInvitation |
| 77 | ✓ CurrencyConverter ✓ Currency |
| 78 | ✓ Mappers (4) |
| 79 | ▲ |
| 80 | │ |
| 81 | App.DAL.EF ──────┘ |
| 82 | ✓ TripRepository (2) |
| 83 | ✓ ExpenseRepository (1) |
| 84 | ✓ BudgetCategoryRepository (1) |
| 85 | ✓ TripParticipantRepository (3) |
| 86 | ✓ TripInvitationRepository (2) |
| 87 | ``` |
| 88 | |
| 89 | --- |
| 90 | |
| 91 | ## Mitmekesisuse maatriks (loengu tehnikad) |
| 92 | |
| 93 | | Tehnika | Kus kasutatud | |
| 94 | |---|---| |
| 95 | | **`[Fact]`** | Kõik klassikalised testid | |
| 96 | | **`[Theory]` + `[InlineData]`** (loengust!) | `CurrencyConverterTests.Convert_KnownCurrencies` (3 valuutapaari), `LangStrTests.Translate_ReturnsCorrectValueForKnownCulture` (3 keelt) | |
| 97 | | **AAA pattern** | Kõikides testides `// Arrange / Act / Assert` kommentaarid | |
| 98 | | **Moq `Setup` + `Returns`** | `_participants.Setup(r => r.IsOrganizerAsync(...)).ReturnsAsync(true)` | |
| 99 | | **Moq `Verify` + `Times`** | `_uow.Verify(u => u.SaveChangesAsync(), Times.Never)` (kontrollib, et ei kutsutud) | |
| 100 | | **Moq `Callback`** | `WishlistService` test salvestab loodud entity koopia | |
| 101 | | **SQLite in-memory** | `RepositoryTestBase` SqliteConnection-iga — FK-d kehtivad | |
| 102 | | **FluentAssertions chain** | `.Should().ContainSingle().Which.Id.Should().Be(...)` | |
| 103 | | **`BeEquivalentTo` + `Excluding`** | Round-trip testides (mapper) | |
| 104 | | **Domain validation** | `Trip.Validate()` `IValidatableObject` | |
| 105 | | **i18n value object** | LangStr `Translate` fallback chain (regional → neutral → default) | |
| 106 | |
| 107 | --- |
| 108 | |
| 109 | ## Sad path / IDOR / edge case katvus |
| 110 | |
| 111 | | Kategooria | Test | |
| 112 | |---|---| |
| 113 | | **IDOR negatiivne** | `TripServiceTests.GetByIdAsync_WhenUserIsNotParticipant_ReturnsNull` | |
| 114 | | **IDOR negatiivne** | `TripServiceTests.DeleteAsync_WhenUserIsNotOrganizer_ReturnsFalseAndDoesNotDelete` | |
| 115 | | **IDOR negatiivne** | `BudgetCategoryServiceTests.CreateAsync_WhenUserIsNotOrganizer_ReturnsForbiddenAndDoesNotAdd` | |
| 116 | | **IDOR negatiivne** | `WishlistServiceTests.GetByTripIdAsync_WhenUserIsNotParticipant_ReturnsEmptyList` | |
| 117 | | **IDOR DAL-tasandil** | `TripParticipantRepositoryTests.IsParticipantAsync_WhenUserHasLeft_ReturnsFalse` (IsActive=false) | |
| 118 | | **Edge case** | `ExpenseServiceTests.CreateExpenseWithSplitsAsync_EqualAll_WhenNoParticipants_CreatesExpenseButNoSplits` | |
| 119 | | **Edge case** | `SettlementServiceTests.CalculateBalancesAsync_WhenNoExpenses_ReturnsZeroBalanceForEachParticipant` | |
| 120 | | **Edge case** | `TripValidationTests.Validate_WhenEndDateEqualsStartDate_YieldsNoErrors` (sama päev OK) | |
| 121 | | **Edge case** | `CurrencyConverterTests.Convert_UnknownCurrency_FallsBackToOneToOne` | |
| 122 | | **Sad path** | `SettlementServiceTests.CalculateBalancesAsync_WhenTripDoesNotExist_ReturnsEmptyList` | |
| 123 | | **Sad path** | `TripInvitationRepositoryTests.GetByTokenAsync_WhenTokenDoesNotExist_ReturnsNull` | |
| 124 | |
| 125 | --- |
| 126 | |
| 127 | ## Run commands |
| 128 | |
| 129 | ```bash |
| 130 | cd SplitApp |
| 131 | |
| 132 | # Kõik testid |
| 133 | dotnet test |
| 134 | |
| 135 | # Kihtide kaupa |
| 136 | dotnet test --filter "FullyQualifiedName~Domain" # 8 |
| 137 | dotnet test --filter "FullyQualifiedName~DAL" # 9 |
| 138 | dotnet test --filter "FullyQualifiedName~BLL" # 13 |
| 139 | dotnet test --filter "FullyQualifiedName~Mappers" # 6 |
| 140 | |
| 141 | # Üks konkreetne klass |
| 142 | dotnet test --filter "FullyQualifiedName~TripServiceTests" |
| 143 | |
| 144 | # Watch mode (uuesti käivitub failimuutusel) |
| 145 | dotnet watch test --project SplitApp/App.Tests |
| 146 | ``` |
| 147 | |
| 148 | --- |
| 149 | |
| 150 | ## What is NOT tested (deliberate) |
| 151 | |
| 152 | - **HTTP/REST integration** (`WebApplicationFactory`) — JWT/HTTP testimine toimub frondist |
| 153 | - **`IdentityService`** — `UserManager<AppUser>` mock-imine on tülikas, vähene kasum |
| 154 | - **Admin-teenused** — CRUD passthrough, vähene kasum |
| 155 | - **EF Core ennast** — Microsoft-i kood, mitte sinu loogika |
| 156 | - **Scaffolded Identity Razor pages** — auto-generated |
| 157 | - **100% coverage** — loeng hoiatab selle eest, fookus äriloogikal |
| 158 | |
| 159 | --- |
| 160 | |
| 161 | ## Defense-valmidus — õpetaja võimalikud küsimused |
| 162 | |
| 163 | | Küsimus | Vastus | |
| 164 | |---|---| |
| 165 | | **"Miks SQLite in-memory mitte EF InMemory provider?"** | Loeng eelistab seda — enforcer FK-d ja käitub nagu päris RDBMS | |
| 166 | | **"Miks Moq ainult interface-de jaoks?"** | DI muster — concrete klasside mock-imine on anti-pattern | |
| 167 | | **"Miks happy path + sad path mitte ainult happy?"** | Testimispüramiidi alus + IDOR on phase 2 nõue, mida tuleb tõestada | |
| 168 | | **"Miks `[Theory]` mitte ainult `[Fact]`?"** | Sama loogika, erinevad sisendid → DRY (lecture: parameterized tests) | |
| 169 | | **"Mis on AAA pattern?"** | Arrange (setup), Act (kutsu meetodit), Assert (kontrolli) — iga test järgib seda | |
| 170 | | **"Miks ei kasuta WebApplicationFactory?"** | HTTP-tasandi testid frondist; backend kontrollib äriloogikat unit-tasemel | |
| 171 | | **"Mis on round-trip test?"** | DTO → entity → DTO peab andma sama tulemuse — kontrollib mapperite sümmeetriat | |
| 172 | | **"Miks BLL testid kasutavad `Verify(..., Times.Never)`?"** | IDOR-i tõestamiseks — kui kasutajal pole õigust, ei tohi DB-sse Add-i kutsuda | |
| 173 | |
| 174 | --- |
| 175 | |
| 176 | ## CI/CD note (deferred) |
| 177 | |
| 178 | `.gitlab-ci.yml` ei muudeta. Kui hiljem otsustad VPS-i ressursi anda: |
| 179 | |
| 180 | ```yaml |
| 181 | test: |
| 182 | stage: test |
| 183 | image: mcr.microsoft.com/dotnet/sdk:10.0 |
| 184 | script: |
| 185 | - cd SplitApp && dotnet test --no-restore |
| 186 | ``` |
| 187 | |
| 188 | Aga see on eraldi otsus, mitte selle töö osa. |
| 189 | |