Компонентно тестване

Компонентното тестване (на английски: unit testing) е процес в програмирането, чрез който се тестват отделни единици (компоненти) от сорс код – един или повече програмни модула и техните контролни данни, процедури за използване, оперативни процедури с цел да се установи дали работят правилно. [1] Под компонент се разбира най-малката част на едно програмно приложение, която може да бъде самостоятелно тествана. В т.нар. процедурно програмиране компонент би могъл да бъде целия програмен модул, но в по-честите случаи е отделна функция или процедура. В случая на обектно ориентирано програмиране компонент е най-често цял клас, но в някои случаи може и да е отделен метод[1]. Компонентните тестове се създават от програмист или в някои случаи от white box тестери по време на процеса на разработка, за да се гарантира правилното поведение на софтуера и дали той покрива всички първоначално поставени изисквания.

Предимства редактиране

Целта на компонентното тестване е да се изолира всяка част от програмата и да се покаже, че отделните части работят правилно. Компонентният тест предоставя строго определени правила, които частите от кода трябва да удовлетворяват. От това произтичат серия предимства:

Ранно откриване на проблеми редактиране

Чрез компонентните тестове могат да се откриват проблеми в цикъла на разработка на софтуер на ранен етап.

В test-driven разработката, която често се използва в т.нар. екстремно програмиране и Scrum, компонентните тестове се създават още преди кодът да бъде завършен. Той става такъв едва след преминаването на тестовете. Едни и същи компонентни тестове се повтарят когато базата на кода се разширява или когато се правят промени по кода. Ако някой тест се провали, това се счита за програмна грешка при промяната на кода или в грешка в самия тест. По този начин тестовете позволяват лесно да се открие къде точно се появява грешката още в процеса на разработка.

Улеснение на промените редактиране

Компонентните тестове позволяват на програмиста да рефакторира кода в по-късен етап и да се увери, че промененият модул все още работи коректно. Процедурата се състои в това да се напишат тестови случаи за всички методи, така че ако някоя от промените предизвика грешка, тя да бъде идентифицирана бързо.

Опростяване на интеграцията редактиране

Компонентното тестване може да намали несигурността в отделните компоненти и може да се използва за bottom-up подход на разработка и тестване. Този подход се изразява в тестване първо на отделни части от програмата, а след това в тестване на всички части заедно, което значително улеснява интеграцията на софтуера.

Основната цел на компонентното тестване е да вземе най-малкото самостоятелно парче от код в софтуерното приложение, това парче да се изолира от останалата част от кода и да се определи дали тази част работи точно по начина, по който се очаква от нея да работи. Всяка единица се тества поотделно преди интеграцията на всички единици в модул, за да се тестват интерфейсите между модулите. [2]

Документация редактиране

Компонентното тестване представлява „живата“ софтуерна документация на системата. Разработчиците, които искат да разберат какви са функционалностите на някой компонент и как могат да ги използват, преглеждат компонентните тестове, за да разберат приложно-програмния интерфейс.

Характеристиките на компонентното тестване могат да индикират за подходящо/неподходящо използване на компонент, също така и за неговото неправилно поведение. Компонентните тестове документират всички тези критични характеристики, въпреки че доста компании, занимаващи се със софтуерна разработка, не разчитат само на тази документация. В процеса на имплементиране на програмата е доста по-вероятно обикновената документация да остарее и да не е вече актуална.

Дизайн редактиране

При разработката на софтуер, когато се използва test-driven подход, комбинацията между писане на тестове за определяне на интерфейса плюс рефакторирането, извършено след преминаването на теста могат да заемат мястото на формален дизайн. Всеки компонентен тест може да се разглежда като елемент на дизайна, определящ класове, методи и поведение. Това може да се илюстрира от следния пример на Java:

Имаме тестов клас, който определя броя елементи в имплементацията. Първо трябва да има интерфейс на име Adder и имплементиращ клас с конструктор нулев аргумент на име AdderImpl. След това Adder трябва да има метод, който се казва add с два целочислени параметъра (int), който връща също целочислена стойност. Също така е определено поведението на метода за малък обхват от стойности.

public class TestAdder {
    public void testSum() {
        Adder adder = new AdderImpl();
        // can it add positive numbers?
        assert(adder.add(1, 1) == 2);
        assert(adder.add(1, 2) == 3);
        assert(adder.add(2, 2) == 4);
        // is zero neutral?
        assert(adder.add(0, 0) == 0);
        // can it add negative numbers?
        assert(adder.add(-1, -2) == -3);
        // can it add a positive and a negative?
        assert(adder.add(-1, 1) == 0);
        // how about larger numbers?
        assert(adder.add(1234, 988) == 2222);
    }
}

В този случай компонентният тест, написан на първо място преди всичко останало, се използва като дизайн документ, определящ формата и поведението на програмата, но не и детайлите по имплементацията, които са работа на програмиста. Следвайки практиката „направи най-простото нещо, които би могло да работи“, най-лесното решение, което ще премине теста е следното:

interface Adder {
    int add(int a, int b);
}
class AdderImpl implements Adder {
    int add(int a, int b) {
        return a + b;
    }
}

За разлика от други методи за дизайн, основаващи се на диаграми, използването на компонентни тестове има едно значително предимство. Дизайн документът (самият компонентен тест) може да бъде използван, за да се верифицира това, че имплементацията се придържа към дизайна. С компонентен тест, използван като дизайн метод, тестовете никога няма да бъдат преминати, ако разработчикът не имплементира програмата съгласно дизайна. Недостатъкът на компонентния тест, използван като дизайн метод, в сравнение с диаграмата е, че компонентният тест не е толкова достъпен, колкото диаграмата, а UML диаграмите вече са създават лесно от повечето модерни езици чрез безплатни инструменти (обикновено достъпни като разширения на интегрирана среда за разработка.

Отделяне на интерфейса от имплементацията редактиране

Тъй като някои класове могат да съдържат референции към други класове, тестването на един клас може да се разпростре върху тестване на друг клас. Често срещан пример за това са класове, които са зависими от база данни: за да тества класа, разработчикът обикновено пише код, който взаимодейства с базата данни. Това е грешка, тъй като компонентното тестване на даден клас не трябва да излиза от границите на самия този клас и в никакъв случай не трябва да се прехвърля върху процесите/мрежите, защото това може да доведе до недопустими проблеми в компонентните тестове. Излизането на теста изън границите на даден клас, води до използването на т.нар. интеграционно тестване и ако то се провали, много трудно ще открием кой компонент в причинил проблема.

Вместо това, разработчикът трябва да създаде абстрактен интерфейс, около заявките на базата данни, и след това да имплементира този интерфейс с техния собствен mock-обект. След отделянето на тази необходима част от кода, независимите компоненти могат да бъдат детайлно тествани. Получените резултати са с по-добро качество и са по-лесни за поддръжка.

Параметризирано компонентно тестване редактиране

Параметризирани компонентни тестве са тестове, които приемат параметри. Противно на традиционните тестове, които в повечето случаи са затворени методи, параметризираното компонентно тестване (ПКТ) използва списък от параметри. ПКТ се поддържа от TestNG, JUnit и от още много .NET тестови платформи. Параметри, подходящи за компонентно тестване, могат да бъдат подадени ръчно или в някои случаи могат да бъдат автоматично генерирани от тестовата платформа. Съществуват различни пособия за тестване, които генерират тестови вход за параметризираното компонентно тестване, например QuickCheck.

Ограничения на компонентното тестване редактиране

Тестването не може да установи всяка грешка в дадена програма, тъй като не може да оцени всяка пътека на изпълнение на програмата, освен в тривиални програми. Същото нещо важи и за компонентното тестване, което в допълнение на това по дефинция се занимава само с функционалността на отделните компоненти, следователно не може да улови грешки в интеграцията или системни грешки (като например функции, които се изпълняват на различни единици или перформънс като цяло). Компонентното тестване трябва да се прилага заедно с други софтуерни тестове, тъй като показва само наличието или отсъствието на конкретни грешки, но не може да докаже абсолютното неналичие на грешки. За да може да се гарантира коректно поведение за всяка пътека на изпълнение на програмата и да се осигури пълното неналичие на грешки, се използват други техники, а именно прилагане на формални методи за доказателство, че софтуерният компонент не може да има неочаквано поведение.

Софтуерното тестване също може да се разглежда като комбинаторен проблем. Например, всяко булево твърдение, което може да има стойности „Истина“ или „Неистина“ изисква поне два теста за всеки един от възможните изходи. Като резултат за всеки ред от написания код, програмистите често се нуждаят от 3 – 5 реда код за тестване.[3]. Това очевидно отнема време и прекалено много усилия. Освен това има много проблеми, които изобщо не могат да бъдат тествани по лесен начин – например тези, които са недетерминистчни или включват няколко неща. Може да се случи така, че кодът на компонентния тест да е също така неправилно написан и да съдържа грешки, както и самият тестван код.

Друго предизвикателство, свързано с писането на компонентни тестове, е трудността да се съставят реалистични и полезни тестове. Необходимо е да се създадат релевантни начални условия, така че частта от приложението, която се тества, да има поведение на завършена пълна система. Ако тези начални условия не са създадени правилно, тестът няма да изпитва кода по релевантен начин, което намалява стойността на коректност на резултатите от компонентния тест. [4]

За да се постигнат определени резултати от компонентното тестване, е нужна строга дисциплина по време на процеса на разработка. Важно е внимателно да се пазят записи не само на приложените тестове, но също така и на приложените промени по сорс кода на конкретната тествана и на всички останали единици в софтуера. Особено важно е използването на version control системи. Ако по-късна версия на даден компонент се провали на някой тест, който преди това е била преминала, version control системата може да предостави списък от промените в сорс кода, които са били направени между двата теста.

Важно нещо при компонентните тестове е да се имплементира устойчив тестови процес, за да се осигури ежедневен преглед и незабавно изпращане на грешките.[5] Ако такъв тестов процес липсва и не е интегриран в работния процес на екипа, разработваното приложение ще се развива извън тестовата рамка и по този начин ще се увеличи наличието на грешки и съответно ще се намали ефективността на тестовата рамка.

Вградените софтуерни системи за компонентно тестване представляват интересно предизвикателство, тъй като софтуерът е бил разработен на различна платформа от тази, на която евентуално ще работи след това, няма как да се пусне тестова програма в реалната среда на изпълнение.[6]

Приложения редактиране

Екстремно програмиране редактиране

Компонентното тестване играе ключова роля в т.нар. екстремно програмиране, което разчита на автоматзирана тестова рамка, която може да бъде или чужда (напр. xUnit), или създадена от екипа разработчици.

Екстремното програмиране използва компонентните тестове за test-driven разработка. Разработчикът пише компонентен тест, който показва или някакво изискване към софтуера или дефект. Този тест ще се провали, защото или изискването към софтуера още не е имплементирано или защото той целенасочено показва дефект в съществуващия код. След това разработчикът пише най-простият възможен код, за да направи така, че тестът да бъде преминат заедно с другите тестове.

По-голямата част от кода в системата се подлага на компонентно тестване, но не и всички пътеки в този код. В екстремното програмиране се прилага стратегията „тествай всичко, което би могло да се счупи“, за разлика от традиционното „тествай всяка пътека на изпълнение“. Това помага на разаботчиците да разработват оп-малко тестове от обикновено, когато се прилагат класически методи.

От решаващо значение е тестовият код да се разглежда като първокачествен елемент на проекта и да се изпълни със същото качество, както и самия код на имплеметацията. Кодът на компонентните тестове се създава в едно и също репозитори с кода, който ще бъде тестван. По този начин се осигурява по-прост и сигурен начин за разработка и рефакториране, опростена интегрция на кода, коректна документация. Компонентните тестове могат също да се прилагат като форма на регресионни тестове.

Техники редактиране

Компонентните тестове в общия случай са автоматизирани, но могат да се изплняват и ръчно. IEEE не дава приоритет на единия вид пред другия. Подходът на ръчното тетване може да включва инструктаж за прилагането му стъпка по стъпка. Целта на компонентното тестване е да се изолира някой компонент (единица) от програмата и да се установи нейната правилност. Автоматизираното тестване е ефикасно за постигането на тази цел и има редица предимства. По отношение на ръчното тестване, ако то не е планирано внимателно, може да се изпълни като интеграционен тест (на повече от един компонент) и по този начин да не доведе до постигането на целите на компонентното тестване.

За да се приложи напълно ефектът на изолирането при използването на автоматизирани компонентни тестове, компонентът, който се тества, се изпълнява в някаква рамка (unit test framework) извън неговата естествена среда. С други думи, компонентът се изпълнява извън програмата, от която той е част и където по начало е бил създаден и извикван. Прилагането на такъв подход на изолирано тестване разкрива наличието на излишни зависимости между тествания код и други компоненти в програмата. По този начин тези зависимости могат а бъдат премахнати.

При използването на автоматизирана тестова рамка, се разработват тестови критерии, за да се установи правилността на компонента. По време на изпълнението на теста, тестовата рамка пуска тестове, които се провалят по всички критерии и понякога автоматичо ги обозначава и докладва. В зависимост от изискванията за грешка, тестовата рамка може да прекрати последващото тестване.

Рамки за компонентно тестване (Unit testing frameworks) редактиране

Виж: Списък с рамки за компонентно тестване (List of unit testing frameworks)

Рамките за компонентно тестване обикновено са външни продукти, които не се разпространяват като част от компилаторния комплект. Те помагат за опростяването на процеса на компонентно тестване и са разработени за най-различни видове програмни езици. Някои видове тестови рамки включват възможности за приложение с отворен код, например различни code-driven тестови рамки, познати под общото название xUnit и комерсиални продукти като TBrun, JustMock, Isolator.NET, Isolator++, Parasoft Test (C/C++test, Jtest, dotTEST), Testwell CTA++ and VectorCAST/C++. Като цяло е взможно да се приложи компонентен тест бе използването на тестова рамка чрез написвне на клиент код, който да се изпълнява върху компонентите, правейки тестове и използвайки твърдения, улавяне на изключения или прилагайки други механизми за контрол и докладване на грешките. В някои тестови рамки липсват много от приложенията за компонентно тестване и те трябва да бъдат допълнително написани.

Поддръжка на компонентни тестове на ниво програмен език редактиране

Някои езици за програмиране поддържат директно компонентно тестване. Техният синтаксис позволява пряката декларация на компонентни тестове, без да се налага импортирането на библиотека. Освен това булевите изрази на тестовите условия могат да бъдат изразени чрез същия синтаксис, чрез който се представят всички останали изрази. Някои езици, които поддържат директно компонентно тестване:

Languages that directly support unit testing include:

Източници редактиране

  1. а б , Adam; Huizinga, Dorota (2007). Automated Defect Prevention: Best Practices in Software Management. Wiley-IEEE Computer Society Press. p. 426. ISBN 0-470-04212-5.
  2. msdn.microsoft.com
  3. Cramblitt, Bob. Alberto Savoia sings the praises of software testing // 20 септември 2007. Архивиран от оригинала на 2013-06-02. Посетен на 29 ноември 2007.
  4. Kolawa, Adam. Unit Testing Best Practices // 1 юли 2009. Посетен на 23 юли 2012.
  5. daVeiga, Nada. Change Code Without Fear: Utilize a regression safety net // 6 февруари 2008. Посетен на 8 февруари 2008.
  6. Kucharski, Marek. Making Unit Testing Practical for Embedded Development // 23 ноември 2011. Архивиран от оригинала на 2021-06-12. Посетен на 8 май 2012.
  7. Python Documentation. unittest -- Unit testing framework // 1999 – 2012. Посетен на 15 ноември 2012.
  8. Ruby-Doc.org. Module: Test::Unit::Assertions (Ruby 2.0) // Посетен на 19 август 2013.