JavaRush /Java Blog /Random-TL /Java Unit Testing: mga diskarte, konsepto, kasanayan

Java Unit Testing: mga diskarte, konsepto, kasanayan

Nai-publish sa grupo
Ngayon ay halos hindi ka makakahanap ng isang application na hindi sakop ng mga pagsubok, kaya ang paksang ito ay magiging mas may-katuturan kaysa dati para sa mga baguhang developer: walang mga pagsubok na hindi mo makukuha kahit saan. Bilang isang patalastas, iminumungkahi kong tingnan mo ang aking mga nakaraang artikulo. Ang ilan sa mga ito ay sumasaklaw sa mga pagsubok (at kahit na ang mga artikulo ay magiging lubhang kapaki-pakinabang):
  1. Pagsubok sa pagsasama ng isang database gamit ang MariaDB upang palitan ang MySql
  2. Pagpapatupad ng multilinggwal na aplikasyon
  3. Pag-save ng mga file sa application at data tungkol sa mga ito sa database
Isaalang-alang natin kung anong mga uri ng pagsubok ang ginagamit sa prinsipyo, at pagkatapos nito ay pag-aaralan natin nang detalyado ang lahat ng kailangan mong malaman tungkol sa pagsubok sa yunit.

Mga uri ng pagsubok

Ano ang isang pagsubok? Gaya ng sabi ng Wiki: " Ang pagsubok o pagsubok ay isang paraan ng pag-aaral ng mga pinagbabatayan na proseso ng isang system sa pamamagitan ng paglalagay ng system sa iba't ibang sitwasyon at pagsubaybay sa mga nakikitang pagbabago dito." Sa madaling salita, ito ay isang pagsubok ng tamang operasyon ng aming system sa ilang partikular na sitwasyon. Lahat ng tungkol sa Unit testing: mga pamamaraan, konsepto, kasanayan - 2Well, tingnan natin kung anong mga uri ng pagsubok ang mayroon:
  1. Ang unit testing ay mga pagsubok na ang gawain ay subukan ang bawat module ng system nang paisa-isa. Ito ay kanais-nais na ang mga ito ay minimally divisible piraso ng system, halimbawa, modules.

  2. Ang system testing ay isang mataas na antas na pagsubok upang subukan ang pagpapatakbo ng isang mas malaking bahagi ng isang application o ng system sa kabuuan.

  3. Ang pagsubok ng regression ay pagsubok na ginagamit upang suriin kung ang mga bagong feature o pag-aayos ng bug ay nakakaapekto sa umiiral nang functionality ng application at kung muling lumitaw ang mga lumang bug.

  4. Ang functional testing ay ang pagsuri sa pagsunod ng bahagi ng application sa mga kinakailangan na nakasaad sa mga detalye, mga kwento ng user, atbp.

    Mga uri ng functional na pagsubok:

    • "white box" na pagsubok para sa pagsunod ng bahagi ng aplikasyon sa mga kinakailangan na may kaalaman sa panloob na pagpapatupad ng system;
    • "black box" na pagsubok para sa pagsunod ng bahagi ng application sa mga kinakailangan nang walang kaalaman sa panloob na pagpapatupad ng system.
  5. Ang pagsubok sa pagganap ay isang uri ng mga pagsubok na isinulat upang matukoy ang bilis kung saan tumatakbo ang isang sistema o bahagi nito sa ilalim ng isang tiyak na pagkarga.
  6. Pagsubok sa pag-load - mga pagsubok na idinisenyo upang suriin ang katatagan ng system sa ilalim ng karaniwang mga pag-load at upang mahanap ang pinakamataas na posibleng peak kung saan gumagana nang tama ang application.
  7. Ang stress testing ay isang uri ng pagsubok na idinisenyo upang suriin ang functionality ng isang application sa ilalim ng hindi karaniwang mga pag-load at upang matukoy ang pinakamataas na posibleng peak kung saan hindi mag-crash ang system.
  8. Pagsubok sa seguridad - mga pagsubok na ginagamit upang suriin ang seguridad ng isang system (mula sa mga pag-atake ng mga hacker, mga virus, hindi awtorisadong pag-access sa kumpidensyal na data at iba pang kagalakan ng buhay).
  9. Ang localization testing ay localization testing para sa isang application.
  10. Ang usability testing ay isang uri ng pagsubok na naglalayong suriin ang usability, understandability, attractiveness at learnability para sa mga user.
  11. Maganda ang lahat ng ito, ngunit paano ito gumagana sa pagsasanay? Simple lang: Ginamit ang testing pyramid ni Mike Cohn: Lahat tungkol sa Unit testing: mga pamamaraan, konsepto, kasanayan - 4Ito ay isang pinasimpleng bersyon ng pyramid: ngayon ay nahahati ito sa mas maliliit na bahagi. Ngunit ngayon ay hindi namin isasama at isaalang-alang ang pinakasimpleng opsyon.
    1. Unit - mga pagsubok sa yunit na ginagamit sa iba't ibang mga layer ng application, pagsubok sa pinakamaliit na divisible logic ng application: halimbawa, isang klase, ngunit kadalasan ay isang paraan. Karaniwang sinusubukan ng mga pagsubok na ito na ihiwalay hangga't maaari mula sa panlabas na lohika, iyon ay, upang lumikha ng ilusyon na ang natitirang bahagi ng application ay gumagana sa karaniwang mode.

      Dapat palaging mayroong maraming mga pagsubok na ito (higit pa kaysa sa iba pang mga uri), dahil sinusubok nila ang maliliit na piraso at napakagaan, hindi kumonsumo ng maraming mapagkukunan (sa mga mapagkukunan ang ibig kong sabihin ay RAM at oras).

    2. Pagsasama - pagsubok sa pagsasama. Sinusuri nito ang mas malalaking piraso ng system, iyon ay, ito ay alinman sa kumbinasyon ng ilang piraso ng lohika (ilang mga pamamaraan o klase), o ang kawastuhan ng pagtatrabaho sa isang panlabas na bahagi. Karaniwang mas kaunti ang mga pagsubok na ito kaysa sa mga pagsubok sa Unit, dahil mas mabigat ang mga ito.

      Bilang isang halimbawa ng mga pagsubok sa pagsasama, maaari mong isaalang-alang ang pagkonekta sa isang database at suriin ang tamang pagpapatupad ng mga pamamaraan na gumagana dito .

    3. UI - mga pagsubok na sumusuri sa pagpapatakbo ng user interface. Nakakaapekto ang mga ito sa lohika sa lahat ng antas ng application, kaya naman tinatawag din silang end-to-end. Bilang isang patakaran, marami ang mas kaunti sa kanila, dahil sila ang pinaka-mabigat at dapat suriin ang pinaka-kinakailangang (ginamit) na mga landas.

      Sa figure sa itaas nakikita natin ang ratio ng mga lugar ng iba't ibang bahagi ng tatsulok: humigit-kumulang sa parehong proporsyon ay pinananatili sa bilang ng mga pagsubok na ito sa totoong trabaho.

      Ngayon ay susuriin natin nang mas malapitan ang mga pinakaginagamit na pagsubok - mga pagsubok sa yunit, dahil lahat ng may paggalang sa sarili na mga developer ng Java ay dapat na magamit ang mga ito sa isang pangunahing antas.

    Mga pangunahing konsepto ng pagsubok sa yunit

    Ang saklaw ng pagsubok (Code Coverage) ay isa sa mga pangunahing pagtatasa ng kalidad ng pagsubok sa aplikasyon. Ito ang porsyento ng code na sakop ng mga pagsubok (0-100%). Sa pagsasagawa, maraming tao ang humahabol sa porsyentong ito, na hindi ko sinasang-ayunan, dahil nagsimula silang magdagdag ng mga pagsubok kung saan hindi sila kailangan. Halimbawa, ang aming serbisyo ay may karaniwang mga operasyong CRUD (gumawa/kumuha/mag-update/magtanggal) nang walang karagdagang lohika. Ang mga paraang ito ay puro tagapamagitan na nagdedelegate ng trabaho sa layer na gumagana sa repositoryo. Sa sitwasyong ito, wala tayong susubukan: marahil kung ang pamamaraang ito ay tumatawag ng isang pamamaraan mula sa Tao, ngunit hindi ito seryoso. Upang masuri ang saklaw ng pagsubok, ang mga karagdagang tool ay karaniwang ginagamit: JaCoCo, Cobertura, Clover, Emma, ​​​​etb. Para sa mas detalyadong pag-aaral ng isyung ito, panatilihin ang ilang angkop na artikulo: TDD (Test-driven development) - pagsubok-driven na development. Sa diskarteng ito, una sa lahat, nakasulat ang isang pagsubok na susuriin ang isang tiyak na code. Ito ay lumalabas na pagsubok sa itim na kahon: alam natin kung ano ang nasa input at alam natin kung ano ang dapat mangyari sa output. Iniiwasan nito ang pagdoble ng code. Ang pag-develop na hinimok ng pagsubok ay nagsisimula sa pagdidisenyo at pagbuo ng mga pagsubok para sa bawat maliit na functionality ng application. Sa diskarte ng TDD, una, isang pagsubok ang binuo na tumutukoy at nagpapatunay kung ano ang gagawin ng code. Ang pangunahing layunin ng TDD ay gawing mas malinaw, mas simple, at walang error ang code. Lahat tungkol sa Unit testing: mga pamamaraan, konsepto, kasanayan - 6Ang diskarte ay binubuo ng mga sumusunod na sangkap:
    1. Sinusulat namin ang aming pagsubok.
    2. Pinapatakbo namin ang pagsubok, pumasa man ito o hindi (nakikita namin na ang lahat ay pula - huwag matakot: ganito dapat).
    3. Nagdaragdag kami ng code na dapat masiyahan sa pagsubok na ito (patakbuhin ang pagsubok).
    4. Refactor namin ang code.
    Batay sa katotohanan na ang mga unit test ay ang pinakamaliit na elemento sa test automation pyramid, ang TDD ay nakabatay sa kanila. Sa tulong ng mga unit test masusubok natin ang lohika ng negosyo ng anumang klase. BDD (Behavior-driven development) - pag-unlad sa pamamagitan ng pag-uugali. Ang diskarte na ito ay batay sa TDD. Higit na partikular, gumagamit ito ng mga halimbawang nakasulat sa malinaw na wika (karaniwan ay sa Ingles) na naglalarawan ng pag-uugali ng system para sa lahat ng kasangkot sa pagbuo. Hindi na tayo lalalim sa terminong ito, dahil pangunahing nakakaapekto ito sa mga tester at business analyst. Test Case - isang script na naglalarawan sa mga hakbang, partikular na kundisyon at parameter na kinakailangan para ma-verify ang pagpapatupad ng code sa ilalim ng pagsubok. Ang Fixture ay isang estado ng kapaligiran ng pagsubok na kinakailangan para sa matagumpay na pagpapatupad ng pamamaraan sa ilalim ng pagsubok. Ito ay isang paunang natukoy na hanay ng mga bagay at ang kanilang pag-uugali sa ilalim ng mga kundisyong ginamit.

    Mga yugto ng pagsubok

    Ang pagsusulit ay binubuo ng tatlong yugto:
    1. Pagtukoy sa data na susuriin (mga fixture).
    2. Gamit ang code sa ilalim ng pagsubok (pagtawag sa pamamaraan sa ilalim ng pagsubok).
    3. Sinusuri ang mga resulta at inihambing ang mga ito sa mga inaasahan.
    Lahat tungkol sa Unit testing: mga pamamaraan, konsepto, kasanayan - 7Upang matiyak ang modularity ng pagsubok, kailangan mong ihiwalay sa iba pang mga layer ng application. Magagawa ito gamit ang mga stub, pangungutya at espiya. Ang mga mock ay mga bagay na nako-customize (halimbawa, partikular sa bawat pagsubok) at nagbibigay-daan sa iyong magtakda ng mga inaasahan para sa mga method call sa anyo ng mga tugon na pinaplano naming matanggap. Ang mga pagsusuri sa inaasahan ay isinasagawa sa pamamagitan ng mga tawag sa mga bagay na Mock. Stubs - magbigay ng isang hard-wired na tugon sa mga tawag sa panahon ng pagsubok. Maaari rin silang mag-imbak ng impormasyon tungkol sa tawag (halimbawa, mga parameter o ang bilang ng mga tawag na ito). Minsan ang mga ito ay tinatawag ng sarili nilang termino - espiya ( Spy ). Minsan ang mga terminong ito na stubs at mock ay nalilito: ang pagkakaiba ay ang isang stub ay hindi nagsusuri ng anuman, ngunit ginagaya lamang ang isang ibinigay na estado. Ang mock ay isang bagay na may mga inaasahan. Halimbawa, na ang isang ibinigay na paraan ng klase ay dapat na tawagan ng ilang beses. Sa madaling salita, hindi kailanman masisira ang iyong pagsubok dahil sa isang stub, ngunit maaari itong masira dahil sa isang pangungutya.

    Mga kapaligiran sa pagsubok

    Kaya ngayon, bumaba tayo sa negosyo. Mayroong ilang mga kapaligiran sa pagsubok (mga balangkas) na magagamit para sa Java. Ang pinakasikat sa kanila ay ang JUnit at TestNG. Para sa aming pagsusuri, ginagamit namin ang: Lahat ng tungkol sa Unit testing: mga pamamaraan, konsepto, kasanayan - 8Ang JUnit test ay isang paraan na nasa isang klase na ginagamit lamang para sa pagsubok. Ang isang klase ay karaniwang pinangalanang kapareho ng klase na sinusubok nito gamit ang +Test sa dulo. Halimbawa, CarService→ CarServiceTest. Awtomatikong kasama sa Maven build system ang mga ganitong klase sa lugar ng pagsubok. Sa katunayan, ang klase na ito ay tinatawag na klase ng pagsubok. Isaalang-alang natin nang kaunti ang mga pangunahing anotasyon: @Test - kahulugan ng pamamaraang ito bilang isang paraan ng pagsubok (sa katunayan, ang pamamaraang minarkahan ng anotasyong ito ay isang pagsubok sa yunit). @Before - minarkahan ang paraan na isasagawa bago ang bawat pagsubok. Halimbawa, pagpuno ng data ng pagsubok sa klase, pagbabasa ng data ng input, atbp. @After - inilagay sa itaas ng paraan na tatawagin pagkatapos ng bawat pagsubok (paglilinis ng data, pagpapanumbalik ng mga default na halaga). @BeforeClass - inilagay sa itaas ng pamamaraan - kahalintulad sa @Before. Ngunit ang pamamaraang ito ay tinatawag na isang beses lamang bago ang lahat ng mga pagsubok para sa isang partikular na klase at samakatuwid ay dapat na static. Ginagamit ito upang magsagawa ng mas mabibigat na mga operasyon, tulad ng pag-angat ng isang database ng pagsubok. Ang @AfterClass ay kabaligtaran ng @BeforeClass: naisakatuparan nang isang beses para sa isang partikular na klase, ngunit naisakatuparan pagkatapos ng lahat ng pagsubok. Ginagamit, halimbawa, upang linisin ang mga patuloy na mapagkukunan o idiskonekta mula sa database. @Ignore - tandaan na ang pamamaraan sa ibaba ay hindi pinagana at hindi papansinin kapag nagpapatakbo ng mga pagsubok sa pangkalahatan. Ito ay ginagamit sa iba't ibang mga kaso, halimbawa, kung ang base na paraan ay binago at walang oras upang gawing muli ang pagsubok para dito. Sa ganitong mga kaso, ipinapayong magdagdag ng paglalarawan - @Ignore("Some description"). @Test (inaasahan = Exception.class) - ginagamit para sa mga negatibong pagsubok. Ito ay mga pagsubok na nagsusuri kung paano kumikilos ang isang pamamaraan kung sakaling magkaroon ng error, iyon ay, inaasahan ng pagsubok na ang pamamaraan ay maghagis ng ilang pagbubukod. Ang ganitong paraan ay tinutukoy ng @Test annotation, ngunit may error na mahuhuli. @Test(timeout=100) - sinusuri kung ang pamamaraan ay gumagana nang hindi hihigit sa 100 millisecond. @Mock - ang isang klase ay ginagamit sa isang patlang upang itakda ang isang ibinigay na bagay bilang isang mock (ito ay hindi mula sa Junit library, ngunit mula sa Mockito), at kung kailangan natin ito, itatakda natin ang pag-uugali ng mock sa isang partikular na sitwasyon , direkta sa paraan ng pagsubok. @RunWith(MockitoJUnitRunner.class) - ang pamamaraan ay inilalagay sa itaas ng klase. Ito ang pindutan para sa pagpapatakbo ng mga pagsubok dito. Maaaring iba ang mga runner: halimbawa, mayroong mga sumusunod: MockitoJUnitRunner, JUnitPlatform, SpringRunner, atbp.). Sa JUnit 5, ang @RunWith annotation ay pinalitan ng mas malakas na @ExtendWith annotation. Tingnan natin ang ilang mga pamamaraan para sa paghahambing ng mga resulta:
    • assertEquals(Object expecteds, Object actuals)— sinusuri kung ang mga ipinadalang bagay ay pantay.
    • assertTrue(boolean flag)— sinusuri kung totoo ang naibalik na halaga.
    • assertFalse(boolean flag)— sinusuri kung ang ipinasa na halaga ay nagbabalik ng false.
    • assertNull(Object object)– sinusuri kung ang bagay ay null.
    • assertSame(Object firstObject, Object secondObject)— sinusuri kung ang mga naipasa na halaga ay tumutukoy sa parehong bagay.
    • assertThat(T t, Matcher<T> matcher)— sinusuri kung t natutugunan ang kundisyong tinukoy sa matcher.
    Mayroon ding isang kapaki-pakinabang na form ng paghahambing mula sa assertj - assertThat(firstObject).isEqualTo(secondObject) Dito napag-usapan ko ang tungkol sa mga pangunahing pamamaraan, dahil ang iba ay iba't ibang mga pagkakaiba-iba ng nasa itaas.

    Pagsasanay sa pagsubok

    Ngayon tingnan natin ang materyal sa itaas gamit ang isang tiyak na halimbawa. Susubukan namin ang paraan para sa serbisyo - pag-update. Hindi namin isasaalang-alang ang dao layer, dahil ito ang aming default. Magdagdag tayo ng starter para sa mga pagsubok:
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-test</artifactId>
       <version>2.2.2.RELEASE</version>
       <scope>test</scope>
    </dependency>
    Kaya, ang klase ng serbisyo:
    @Service
    @RequiredArgsConstructor
    public class RobotServiceImpl implements RobotService {
       private final RobotDAO robotDAO;
    
       @Override
       public Robot update(Long id, Robot robot) {
           Robot found = robotDAO.findById(id);
           return robotDAO.update(Robot.builder()
                   .id(id)
                   .name(robot.getName() != null ? robot.getName() : found.getName())
                   .cpu(robot.getCpu() != null ? robot.getCpu() : found.getCpu())
                   .producer(robot.getProducer() != null ? robot.getProducer() : found.getProducer())
                   .build());
       }
    }
    8 - hilahin ang na-update na bagay mula sa database 9-14 - lumikha ng bagay sa pamamagitan ng tagabuo, kung ang papasok na bagay ay may isang patlang - itakda ito, kung hindi - iwanan kung ano ang nasa database At tingnan ang aming pagsubok:
    @RunWith(MockitoJUnitRunner.class)
    public class RobotServiceImplTest {
       @Mock
       private RobotDAO robotDAO;
    
       private RobotServiceImpl robotService;
    
       private static Robot testRobot;
    
       @BeforeClass
       public static void prepareTestData() {
           testRobot = Robot
                   .builder()
                   .id(123L)
                   .name("testRobotMolly")
                   .cpu("Intel Core i7-9700K")
                   .producer("China")
                   .build();
       }
    
       @Before
       public void init() {
           robotService = new RobotServiceImpl(robotDAO);
       }
    1 — ang aming Runner 4 — ihiwalay ang serbisyo mula sa layer ng dao sa pamamagitan ng pagpapalit ng mock 11 — magtakda ng isang pansubok na entity para sa klase (ang isa na gagamitin namin bilang isang pagsubok na hamster) 22 — magtakda ng isang bagay ng serbisyo na aming susuriin
    @Test
    public void updateTest() {
       when(robotDAO.findById(any(Long.class))).thenReturn(testRobot);
       when(robotDAO.update(any(Robot.class))).then(returnsFirstArg());
       Robot robotForUpdate = Robot
               .builder()
               .name("Vally")
               .cpu("AMD Ryzen 7 2700X")
               .build();
    
       Robot resultRobot = robotService.update(123L, robotForUpdate);
    
       assertNotNull(resultRobot);
       assertSame(resultRobot.getId(),testRobot.getId());
       assertThat(resultRobot.getName()).isEqualTo(robotForUpdate.getName());
       assertTrue(resultRobot.getCpu().equals(robotForUpdate.getCpu()));
       assertEquals(resultRobot.getProducer(),testRobot.getProducer());
    }
    Dito makikita natin ang isang malinaw na dibisyon ng pagsubok sa tatlong bahagi: 3-9 - pagtatakda ng mga fixtures 11 - pagpapatupad ng nasubok na bahagi 13-17 - pagsuri sa mga resulta Higit pang mga detalye: 3-4 - pagtatakda ng gawi para sa moka dao 5 - setting ang instance na ia-update namin sa ibabaw ng aming standard 11 - gamitin ang paraan at kunin ang resultang instance 13 - suriin na ito ay hindi zero 14 - suriin ang resulta ID at ang tinukoy na paraan argumento 15 - suriin kung ang pangalan ay na-update 16 - tingnan ang resulta ng cpu 17 - dahil hindi namin ito itinakda sa patlang ng halimbawa ng pag-update, dapat itong manatiling pareho, suriin natin ito. Lahat ng tungkol sa Unit testing: mga pamamaraan, konsepto, kasanayan - 9Ilunsad natin: Lahat ng tungkol sa Unit testing: techniques, concepts, practice - 10Ang pagsubok ay berde, maaari kang huminga)) Kaya, sabihin sa buod: ang pagsubok ay nagpapabuti sa kalidad ng code at ginagawang mas nababaluktot at maaasahan ang proseso ng pagbuo. Isipin kung gaano karaming pagsisikap ang kailangan nating gastusin kapag muling nagdidisenyo ng software na may daan-daang mga file ng klase. Kapag mayroon na tayong mga unit test na isinulat para sa lahat ng klaseng ito, maaari tayong mag-refactor nang may kumpiyansa. At ang pinakamahalaga, nakakatulong ito sa amin na madaling makahanap ng mga error sa panahon ng pag-unlad. Guys, iyon lang para sa akin ngayon: ibuhos ang mga gusto, magsulat ng mga komento))) Lahat tungkol sa Unit testing: mga pamamaraan, konsepto, kasanayan - 11
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION