Előadást letölteni
Az előadás letöltése folymat van. Kérjük, várjon
1
Web programozás és haladó fejlesztési technikák
Tesztelés a fejlesztői munkában NUnit Unit teszt Dependency Injection Tesztduplikátumok, Mock-ok, Fake-ek 1
2
„Tesztelés = kipróbálom kézzel”
3
Tesztelés szükségessége
Tesztesetek száma? > 2^n if (condition1) { // something happens if (condition2) if (condition3) } …? Random exception befolyásolja condition3-at fájlhoz nyúl user inputot használ adatbáziselérés Órai munka ( ); féléves beadandó (CarShop 4841); 1 fős kisprojekt (n*10e); avg projekt (n*100e); nagy projekt Mekkora egy projekt?
4
Tesztelés szükségessége
„B” Team „A” Team Forrás:
5
„Majd a tesztelő leteszteli”
6
Tesztelés a fejlesztői munkában
A tesztelés közelebb került az implementációhoz, mert: Korai feedbacket ad Védi a lefedett kódot a későbbi véletlen hibák ellen Követelmények tisztázását segíti Tiszta, jól strukturált kód írását kényszeríti ki (Bizonyos módszertanokban a tesztelés az implementáció elé került) „Agilis csapat” A termék/funkció fejlesztésének összes aspektusáért – tervezésért, implementációjáért, minőségellenőrzéséért, teszteléséért felel Egyre inkább a fenntartásért / supportért is A régi szerepek elmosódnak vagy szándékosan hiányoznak – „crossfunctional team”
7
Tesztelés szükségessége
static void Main(string[] args) { Logger l = new Logger(); l.AddLogMethod(Console.WriteLine); l.AddLogMethod( m => using (StreamWriter writer = new StreamWriter("log.txt", true)) writer.WriteLine(m); } }); // ... l.Log("Program elindult"); l.Log("Adatbáziskapcsolat felépült"); Console.ReadLine();
8
Tesztelés szintjei DoSomething() DoSomething() DoSomething() UI teszt
Integrációs teszt Komponensteszt Unit teszt (Az elnevezések fejlesztői közösségenként változhatnak) DoSomething() DoSomething() DoSomething() Forrás:
9
NUnit Teszt keretrendszer .NET nyelvekhez (Java: jUnit)
Nagyobb programcsalád része Unitteszt: NUnit (vs. Visual Studio Unit Testing Framework) Mocking: NSubstitute (vs. Moq, Rhino Mocks) IoC container: NInject (vs. Spring.Net, Castle Windsor, Unity) Tesztlefedettség: NCover (vs. dotCover) Unit teszttől integrációs tesztig minden tesztszinthez jó Saját GUI, command line, vagy Studióban Test Explorer (NUnit3TestAdapter kell hozzá) Félkövér, amit megnézünk. Moq a következő órán. Általában a fejlesztői közösség összeválogatja a toolkészletet, nem feltétlenül együtt használják az N... családot NUnit nemigen különbözik az összes hasonló célú keretrendszertől, igazából tökmindegy, melyiket választják.
10
Egyszerű tesztelt projekt
Éles projekt(ek) + tesztprojekt(ek) (Class Library) Tesztprojekt referenciái: Tesztelt projekt Tesztkeretrendszer A tesztprojektben a tesztelt projekt publikus részei tesztelhetők Internal részek: [InternalsVisibleTo(„ConsoleApplication1Tests”)] Class Library
11
Egyszerű tesztelt projekt
Test / Windows / Test Explorer NUnit3TestAdapter kell hozzá! (Nuget) Fejlesztői közösség szokásától és tesztektől függően folyamatosan, vagy lényeges kódváltoztatás után, merge előtt, merge után, push előtt mindenképpen futtatják. Piros teszttel nem pusholnak kódot „ConsoleApplication1Tests” projektben, „Class1Tests” osztályban van ez a teszt.
12
Egyszerű tesztelt projekt
13
Hogyan működik? Reflection Studio Test Explorer => NUnit
NUnit reflexióval végigkeresi a solution(ból keletkező exék és dll-ek) összes elérhető osztályát [TestFixture] attribútum után [TestFixture] osztály [Test] attribútummal ellátott függvényei a tesztek, ezeket is megkeresi Értelmezi a tesztek működését/inputjait/stb. befolyásoló egyéb attribútumokat ([TestCaseSource], [Explicit], ) Reflexióval futtatja a teszteket
14
NUnit Gyakori felépítés („AAA”) Given…When…Then()
Arrange: előkészítés, objektumok létrehozása, beállítása stb. Act: tesztelendő egyetlen lépés végrehajtása Assert: az eredmény, vagy ami a tesztelt tevékenység hatására történt, az az elvárásnak megfelelő-e? Given…When…Then()
15
NUnit Rengeteg lehetőség // Elvárások :
Assert.That(result, Is.EqualTo("MyResult")); Assert.That(result, Is.Null); Assert.That(result, Is.LessThan(2)); Assert.That(result, Is.SameAs(otherReferenceToTheSameObject)); Assert.That(result, Is.Not.Null); // tagadás // Exception-ellenőrzések: Assert.That(() => t.MyTestedMethod(), Throws.TypeOf<NullReferenceException>()); Assert.That(() => t.MyTestedMethod(), Throws.Nothing); // régi szintaktika: (ugyanaz, mint az Is.EqualTo) Assert.AreEqual(result, 42); Assert.That után a második paraméterben mondatszerűen olvasható/írható („fluent”) szintaktika. Volt egy régi szintaktika is, régi NUnit még csak ezt tudta (utolsó sor). Assert.AreEqual, Assert.AreSame, Assert.IsNull, Assert.IsNotNull ... stb.
16
NUnit Akárhány input paraméter lehet a tesztmetódusban – ugyanennyit kell megadni a TestCase attribútumban Attribútum csak konstans értékeket kaphat -> TestCaseSource! Hasonló lehetőségek: [Sequential] – az input paraméterek megadott értékek közül sorra kapnak értékeket [Combinatorial] – az input paraméterek számára megadott értékek összes lehetséges kombinációjával meghívódik a teszt [Pairwise] – az előzőhöz képest optimalizált (kevesebb tesztesetet csinál)
17
NUnit TestCaseSource Dinamikusan készített v. nem konstans értékekkel dolgozó tesztesetekhez – akár saját osztályok átadása értékekként Sokféle formában használható: object[] helyett TestCaseData utód, property helyett metódus, osztály... public static IEnumerable<TestCaseData> MyTestCases { get List<TestCaseData> testCases = new List<TestCaseData>(); for (int i = 0; i < 10; i++) testCases.Add(new TestCaseData(new object[] { i, i * 2 })); } return testCases; [TestCaseSource("MyTestCases")] public void Class1_DoSomethingWithInput_ResultIsAsExpected(int input, int expected) // ... mint előbb
18
NUnit [Setup] [TearDown] [TestFixtureSetUp] / [OneTimeSetUp]
Az így jelölt metódus minden egyes teszt vagy teszteset előtt le fog futni [TearDown] Az így jelölt metódus minden egyes teszt vagy teszteset futása után lefut [TestFixtureSetUp] / [OneTimeSetUp] Az így jelölt metódus az adott [TestFixture] tesztjeinek futtatása előtt fut, egyszer Pl. az összes teszt által használt objektumok hozhatók létre így – de vigyázzunk, a tesztek ideális esetben nem befolyásolhatják egymást! [TestFixtureTearDown] / [OneTimeTearDown] Az így jelölt metódus az adott [TestFixture] tesztjeinek futtatása után fut, egyszer [SetUpFixture] Osztályra rakható, namespace szintű setup/teardown OneTime... : NUnit 3.0-tól kezdve ez a neve
19
Jól tesztelni nehéz Legyenek a tesztek gyorsak
Lassú tesztek folyamatos futtatása nem lehetséges => hibák késői kiderülését okozhatja Legyenek lehetőleg egymástól függetlenek Sorrend, időzítés stb. nem hathat ki az eredményre Ránézésre olvasható nevekkel A jól elkészített tesztlista dokumentálja a kód képességeit (requirement-listának fogható fel) Nem kell és nem is szabad minden inputlehetőséget lefedni Sokszor példákkal tesztelnek Corner case-ek megtalálása fontosabb Egyszerre egyetlen művelet, és egy osztály tesztelése Mindig függetlenül az éles adattól, ami változhat – tehát nem olvasunk éles adatbázist, éles settingsfájlt, stb. Osztályok függőségeit is helyettesítjük: Dependency Injection + tesztduplikátumok (test doubles) Ránézésre olvasható nevekkel: + ránézésre olvasható teszt is legyen Az egymástól való függetlenítés miatt akár kódduplikáció is simán megengedett
20
Tesztesetek – egyszerű kód
if (max_x < 0 || max_y < 0) throw new ArgumentException("...");
21
Tesztesetek – egyszerű kód
??? if (max_x <= 0 || max_y <= 0) throw new ArgumentException("..."); if (max_x < 0 || max_y < 0) throw new ArgumentException("...");
22
Tesztesetek – komplex(ebb) kód
…? // Warning - nem unittest! // Warning - nem unittest! // Warning - nem unittest! if (max_x <= 0 || max_y <= 0) throw new ArgumentException("...");
23
Warning – nem unittest!
24
Mit tehetünk?
25
Refaktorálás Tight coupling
Refaktorálás = a kód struktúrájának módosítása a viselkedés módosítása nélkül Refactoring for testability
26
Refaktorált példa – NewsletterService
27
Refaktorált példa – NewsletterService
28
Refaktorált példa – NewsletterService
29
Refaktorált példa – NewsletterService
Loose coupling
30
Dependency Injection Az osztályok függőségeit kívülről biztosítjuk – interfészek formájában A DI lehetővé teszi a fejlesztés előrehaladtával a függőség lecserélését – az interfész megtartása mellett Lehetővé teszi a „valódi” unit- („egység-”) tesztelést is Tesztelésnél szándékosan cseréljük a függőséget egy ismert, egyszerű, e célra létrehozott objektumra Osztály függetlenítése a függőségeitől („Mindig függetlenül az éles adattól” ... „Egyszerre egyetlen művelet, és egy osztály tesztelése”) Gyorsaság Komplex osztály helyett nagyon egyszerű csereobjektum, csak azokkal az aspektusokkal, amelyekről a teszt szól Függetlenség 1 teszt => 1 csereobjektum Ha a tesztet osztály esetleg írni akar az objektumba, az se gond Technikái: fake függőségek, mockolás, stb. A DI nem (elsősorban) teszt célú dolog, de a tesztelhetőség egy lényeges előny. A DI-t teljesen betartó kód írása bonyolult, a Main()-nek ismernie kéne minden függőséget? => IoC containerek
31
Constructor Injection
interface IMyDependency { string DoSomething(); } class MyClass private IMyDependency dependency; public MyClass(IMyDependency dependency) this.dependency = dependency; // metódusaiban: dependency.DoSomething() // használata IMyDependency myDependency; // értékadás... MyClass myClass = new MyClass(myDependency);
32
Method Injection, Setter Injection
class MyClass { public void DoSomethingUsingDependency(IMyDependency dependency) // ... dependency.DoSomething() ... } class MyClass { public IMyDependency Dependency { get; set; } // metódusaiban: dependency.DoSomething() } Constructor Injection – gyakori, ha sok helyen kell a függőség Method Injection – gyakori, ha egy helyen kell a függőség Setter Injection – ritka Mindenhol ellenőrizni kell, hogy be lett-e már állítva Többszálúság még több gondot okoz (nem lett-e újra null?) Most már használhatunk test double-öket!
33
Fake-ek Azonos interfészt implementál a helyettesítendő objektummal, de semmilyen, vagy csak szándékosan egyszerűsítő, tesztcélú logikát tartalmaz
34
Fake-ek
35
Mock-ok Azonos interfészt implementál a helyettesítendő objektummal – nincs logika, fix adat Általában lehetőséget ad a végrehajtódó logika megfigyelésére is Expectation-ök: „a DoSomething() függvény pontosan egyszer hívódott” Mocking framework segítségével szokás készíteni Mocking framework képességeitől függően a mock, fake, stub stb. különbség elmosódhat Nagyon gyakori, hogy egyszerűen fake-nek használják a mockokat (és nincs expectation-ellenőrzés) Az egyik legelterjedtebb .NET mocking framework: Moq Mock<I Sender> mockMailSender = new Mock<I Sender>(); // mock elvárt viselkedésének beállítása... NewsletterService service = new NewsletterService(mockMailSender.Object);
36
Mock elvárt viselkedésének beállítása
Egy mock objektum bármely függvénye bármilyen hívást enged, nem dob exceptiont, és default értéket ad vissza Setup által módosítható ez a működés Onnantól csak az így meghatározott hívások megengedettek! Egy mock objektum propertyje nem jegyzi meg az értékét, és default értéket ad vissza Setup/SetupAllProperties által módosítható ez a működés Mock<ISubscriberRepository> repositoryMock = new Mock<ISubscriberRepository>(); Mock<I Sender> senderMock = new Mock<I Sender>(); // különféle elvárt viselkedések beállítása, paraméterektől függően repositoryMock.Setup(m => m.Subscribers).Returns(expectedSubscribers); repositoryMock.Setup(m => m.Subscribers).Returns(Enumerable.Empty<Subscriber>()); repositoryMock.Setup(m => m.Subscribers).Throws<NullReferenceException>(); senderMock.Setup( m => "to", "subject", "body")).Returns(true); m => m.Send (null, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())) .Throws<NullReferenceException>(); ARRANGE fázisban csinálod ezt. A fenti Setup-ok egyszerre nyilván nem fognak működni, csak példák. Ha egyszer setupoltál, akkor csak a konkrétan megadott módokon tudod hívni a függvényt, különben exception!!!
37
Logika ellenőrzése mockkal (csak Setup után)
// ARRANGE Mock<IEmployeeBusinessLogic> mbl = new Mock<IEmployeeBusinessLogic>(); // ... Setup a később ellenőrzendő függvényekre, propertykre... // ACT ... // ASSERT // 1x hívódott senderMock.Verify( m => It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once); // sosem hívódott nullal m => m.Send (null, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()), Times.Never); // 0x volt 1-nél nagyobb log loggerMock.Verify( m => m.Log(It.Is<int>(i => i > 1), It.IsAny<string>()), Times.Never); ASSERT fázisban csinálod ezt
38
Példa
39
Test doubles Forrás:
40
Test doubles Astels = David Astels: Test-Driven Development - A practical guide Beck = Kent Beck: Test-Driven Development By Example Feathers = Michael Feathers: Working Effectively with Legacy Code Fowler = Martin Fowler: Mocks are Stubs jMock = Steve Freeman, Tim Mackinnon, Nat Pryce, Joe Walnes: Mock Roles, Not Objects UTWJ = Morgan Kaufmann: Unit Testing With Java OMG = Object Management Group's CORBA specs Pragmatic = Andy Hunt, Dave Thomas: Pragmatic Unit Testing with Nunit Recipes = J.B.Rainsberger: JUnit Recipes Forrás:
41
Testability Code Smells
Referenciák osztály és nem interfész típusú változókban Refaktorálás interfész típusú változókká new() Refaktorálás DI irányba (gyakran Factory pattern) DI: túl sok paraméter Túl sok dologért felelős az osztály, refaktorálás / bontás Esetleg összefoglalás közös objektumba Statikus osztályok és változók használata (pl. Singleton pattern) Refaktorálás példányok használatára Sok/hosszú statikus helper metódus Strukturált programozás OO helyett, kiszervezés nemstatikus osztályba Felhasználói interakciótól függő részek, ablakok Kiszervezés külön osztályba (Humble Object pattern) Adatbázis, fájlírás, egyéb külső erőforrás Kiszervezés külön osztályba (Repository, Humble Object pattern) Túl sokat kell mockolni “A code smell is a surface indication that usually corresponds to a deeper problem in the system.” ~ Martin Fowler
42
Módszertanok Először kód, utána tesztek
Nehéz, nem hatékony! Test First: a fejlesztő a teszteket készíti el először, utána a kódot Előre leírja a kívánt követelményeket – eközben valószínűleg többet is tisztázni kell! Vagy biztosítja egy létező kód jelenlegi működésének megtartását átírás előtt TDD (Test-Driven Development): Sokszor pair programming-ben csinálják A kód ~100%-ban tesztelhetőre „íródik” meg Fejlesztési idő kb. duplázódik Nem mindent érdemes így tesztelni (elsősorban „algoritmikusabb” feladatokhoz ideális) BDD, ATDD...
43
Lefedettség dotCover NCover OpenCover
Code coverage is collected by using a specialized tool to instrument the binaries to add tracing calls and run a full set of automated tests against the instrumented product. A good tool will give you not only the percentage of the code that is executed, but also will allow you to drill into the data and see exactly which lines of code were executed during a particular test. dotCover NCover OpenCover
44
Lefedettség Gyakran van alsó határ, de nem érdemes 100%-ra hajtani
Nagyon sokféleképp lehet mérni – vs Use Case Coverage… Statement Coverage: minden utasítás lefutott (if, ciklus nincs benne) Branch Coverage: minden döntési ág lefutott (if, else) Condition Coverage: minden boolean kifejezés igaz és hamis értéket is kapott Loop Coverage: minden ciklus futott 0x, 1x és >1x, illetve ha lehetséges, a maximum limittel és annál eggyel nagyobbal. Parameter Value Coverage: paraméterek összes jellegzetes értéke tesztelendő (pl. string null, üres, whitespace…) Inheritance Coverage: visszaadott objektumtípus tesztelése … ust remember, having "100% code-coverage" doesn't mean everything is tested completely - while it means every line of code is tested, it doesn't mean they are tested under every (common) situation.. I would use code-coverage to highlight bits of code that I should probably write tests for. For example, if whatever code-coverage tool shows myImportantFunction() isn't executed while running my current unit-tests, they should probably be improved. Basically, 100% code-coverage doesn't mean your code is perfect. Use it as a guide to write more comprehensive (unit-)tests. Condition coverage: All boolean expressions to be evaluated for true and false. Decision coverage: Not just boolean expressions to be evaluated for true and false once, but to cover all subsequent if-elseif-else body. Loop Coverage: means, has every possible loop been executed one time, more than once and zero time. Also, if we have assumption on max limit, then, if feasible, test maximum limit times and, one more than maximum limit times. Entry and Exit Coverage: Test for all possible call and its return value. Parameter Value Coverage (PVC). To check if all possible values for a parameter are tested. For example, a string could be any of these commonly: a) null, b) empty, c) whitespace (space, tabs, new line), d) valid string, e) invalid string, f) single-byte string, g) double-byte string. Failure to test each possible parameter value may leave a bug. Testing only one of these could result in 100% code coverage as each line is covered, but as only one of seven options are tested, means, only 14.2% coverage of parameter value. Inheritance Coverage: In case of object oriented source, when returning a derived object referred by base class, coverage to evaluate, if sibling object is returned, should be tested.
Hasonló előadás
© 2024 SlidePlayer.hu Inc.
All rights reserved.