CodeGym /Java Blog /Random /Lahat ng tungkol sa pagsubok sa yunit: mga diskarte, kons...
John Squirrels
Antas
San Francisco

Lahat ng tungkol sa pagsubok sa yunit: mga diskarte, konsepto, pagsasanay

Nai-publish sa grupo
Ngayon, hindi ka makakahanap ng isang application na hindi nababalot ng mga pagsubok, kaya ang paksang ito ay magiging mas may kaugnayan kaysa dati para sa mga baguhang developer: hindi ka magtagumpay nang walang mga pagsubok. Isaalang-alang natin kung anong mga uri ng pagsubok ang ginagamit sa prinsipyo, at pagkatapos ay pag-aaralan natin nang detalyado ang lahat ng dapat malaman tungkol sa unit testing. Lahat tungkol sa pagsubok sa yunit: mga diskarte, konsepto, kasanayan - 1

Mga uri ng pagsubok

Ano ang isang pagsubok? Ayon sa Wikipedia: "Ang pagsubok sa software ay nagsasangkot ng pagpapatupad ng isang bahagi ng software o bahagi ng system upang suriin ang isa o higit pang mga katangian ng interes." Sa madaling salita, ito ay isang pagsusuri sa kawastuhan ng aming system sa ilang partikular na sitwasyon. Well, tingnan natin kung anong mga uri ng pagsubok ang mayroon sa pangkalahatan:
  • Unit testing — Mga pagsubok na ang layunin ay suriin ang bawat module ng system nang hiwalay. Ang mga pagsubok na ito ay dapat ilapat sa pinakamaliit na bahagi ng atomic ng system, hal. mga module.
  • Pagsusuri ng system — Pagsubok sa mataas na antas upang suriin ang pagpapatakbo ng isang mas malaking bahagi ng application o ng system sa kabuuan.
  • Pagsusuri ng regression — Pagsubok na ginagamit upang suriin kung ang mga bagong feature o pag-aayos ng bug ay nakakaapekto sa kasalukuyang functionality ng application o nagpapakilala ng mga lumang bug.
  • Functional testing — Sinusuri kung ang isang bahagi ng application ay nakakatugon sa mga kinakailangan na nakasaad sa mga detalye, mga kwento ng user, atbp.

    Mga uri ng functional na pagsubok:

    • White-box testing — Sinusuri kung ang isang bahagi ng application ay nakakatugon sa mga kinakailangan habang alam ang panloob na pagpapatupad ng system;
    • Black-box testing — Pagsusuri kung ang isang bahagi ng application ay nakakatugon sa mga kinakailangan nang hindi nalalaman ang panloob na pagpapatupad ng system.

  • Pagsubok sa pagganap — Mga pagsubok na isinulat upang matukoy kung paano gumaganap ang system o bahagi ng system sa ilalim ng isang tiyak na pagkarga.
  • Pagsubok sa pag-load — Mga pagsubok na idinisenyo upang suriin ang katatagan ng system sa ilalim ng mga karaniwang pag-load at upang mahanap ang maximum na pagkarga kung saan gumagana pa rin nang tama ang application.
  • Pagsusuri ng stress — Pagsubok na idinisenyo upang suriin ang pagganap ng application sa ilalim ng hindi karaniwang mga pag-load at upang matukoy ang pinakamataas na pagkarga bago masira ang system.
  • Pagsubok sa seguridad — Mga pagsubok na ginagamit upang suriin ang seguridad ng system (mula sa mga hacker, mga virus, hindi awtorisadong pag-access sa kumpidensyal na data, at iba pang kasiya-siyang pag-atake).
  • Pagsubok sa lokalisasyon — Mga pagsubok sa lokalisasyon ng application.
  • Usability testing — Pagsubok na naglalayong suriin ang usability, understandability, attractiveness, at learnability.
Maganda ang lahat ng ito, ngunit paano ito gumagana sa pagsasanay? Simple lang! Ginagamit namin ang testing pyramid ni Mike Cohn: Lahat tungkol sa pagsubok sa unit: mga diskarte, konsepto, kasanayan - 2Ito ay isang pinasimpleng bersyon ng pyramid: nahahati na ito sa mas maliliit na bahagi. Ngunit ngayon hindi tayo magiging masyadong sopistikado. Isasaalang-alang namin ang pinakasimpleng bersyon.
  1. Yunit — Ang seksyong ito ay tumutukoy sa mga pagsubok sa yunit, na inilalapat sa iba't ibang layer ng application. Sinusubukan nila ang pinakamaliit na divisible unit ng application logic. Halimbawa, mga klase, ngunit madalas na mga pamamaraan. Karaniwang sinusubukan ng mga pagsubok na ito hangga't maaari na ihiwalay ang sinusuri sa anumang panlabas na lohika. Iyon ay, sinusubukan nilang lumikha ng ilusyon na ang natitirang bahagi ng application ay tumatakbo tulad ng inaasahan.

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

  2. Integrasyon — Ang seksyong ito ay tumutukoy sa integration testing. Sinusuri ng pagsubok na ito ang mas malalaking bahagi ng system. Ibig sabihin, pinagsasama nito ang ilang piraso ng lohika (ilang mga pamamaraan o klase), o sinusuri nito ang kawastuhan ng pakikipag-ugnayan sa isang panlabas na bahagi. Ang mga pagsusulit na ito ay kadalasang mas maliit kaysa sa mga unit test dahil mas mabigat ang mga ito.

    Ang isang halimbawa ng isang pagsubok sa pagsasama ay maaaring pagkonekta sa isang database at pagsuri sa kawastuhan ng pagpapatakbo ng mga pamamaraan para sa pagtatrabaho dito.

  3. UI — Ang seksyong ito ay tumutukoy sa mga pagsubok na sumusuri sa pagpapatakbo ng user interface. Kabilang sa mga ito ang lohika sa lahat ng antas ng aplikasyon, kaya naman tinatawag din silang mga end-to-end na pagsubok. Bilang isang patakaran, mayroong mas kaunti sa kanila, dahil sila ang pinakamahirap at dapat suriin ang pinaka-kinakailangang (ginamit) na mga landas.

    Sa larawan sa itaas, nakikita natin na ang iba't ibang bahagi ng tatsulok ay nag-iiba-iba sa laki: humigit-kumulang sa parehong proporsyon ang umiiral sa bilang ng iba't ibang uri ng mga pagsubok sa totoong trabaho.

    Ngayon ay susuriin natin nang mas malapitan ang mga pinakakaraniwang 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 sa pagsubok ng yunit

Ang saklaw ng pagsubok (saklaw ng code) ay isa sa mga pangunahing sukatan kung gaano kahusay nasubok ang isang aplikasyon. Ito ang porsyento ng code na sakop ng mga pagsubok (0-100%). Sa pagsasagawa, marami ang hinahabol ang porsyentong ito bilang kanilang layunin. Iyan ay isang bagay na hindi ako sumasang-ayon, dahil ang ibig sabihin nito ay magsisimulang ilapat ang mga pagsusulit kung saan hindi ito kailangan. Halimbawa, ipagpalagay na mayroon kaming karaniwang CRUD (lumikha/kumuha/mag-update/magtanggal) sa aming serbisyo nang walang karagdagang lohika. Ang mga pamamaraang ito ay mga tagapamagitan lamang na nagdedelegate ng trabaho sa layer na nagtatrabaho sa repositoryo. Sa sitwasyong ito, wala tayong susubukan, maliban marahil kung ang ibinigay na pamamaraan ay tinatawag na DAO na pamamaraan, ngunit iyon ay isang biro. Karaniwang ginagamit ang mga karagdagang tool upang masuri ang saklaw ng pagsubok: JaCoCo, Cobertura, Clover, Emma, ​​atbp. Para sa mas detalyadong pag-aaral ng paksang ito, Ang TDD ay nangangahulugang pag-unlad na hinimok ng pagsubok. Sa diskarteng ito, bago gumawa ng anupaman, sumulat ka ng isang pagsubok na susuriin ang partikular na code. Ito pala ay black-box testing: alam namin ang input at alam namin kung ano dapat ang output. Ginagawa nitong posible na maiwasan ang pagdoble ng code. Ang pag-develop na hinimok ng pagsubok ay nagsisimula sa pagdidisenyo at pagbuo ng mga pagsubok para sa bawat bit ng functionality sa iyong application. Sa diskarte ng TDD, gumawa muna kami ng pagsubok na tumutukoy at sumusubok sa gawi ng code. Ang pangunahing layunin ng TDD ay gawing mas nauunawaan, mas simple, at walang error ang iyong code. Lahat tungkol sa pagsubok sa unit: mga diskarte, konsepto, kasanayan - 3Ang diskarte ay binubuo ng mga sumusunod:
  • Sinusulat namin ang aming pagsubok.
  • Pinapatakbo namin ang pagsubok. Hindi nakakagulat, nabigo ito, dahil hindi pa namin naipapatupad ang kinakailangang lohika.
  • Idagdag ang code na nagiging sanhi ng pagpasa sa pagsubok (patakbo naming muli ang pagsubok).
  • Refactor namin ang code.
Ang TDD ay batay sa mga unit test, dahil sila ang pinakamaliit na building blocks sa test automation pyramid. Sa mga unit test, masusubok natin ang lohika ng negosyo ng anumang klase. Ang BDD ay kumakatawan sa pag-unlad na hinimok ng pag-uugali. Ang diskarte na ito ay batay sa TDD. Higit na partikular, gumagamit ito ng mga simpleng halimbawa ng wika na nagpapaliwanag ng gawi ng system para sa lahat ng kasangkot sa pag-unlad. Hindi namin susuriin ang terminong ito, dahil pangunahing nakakaapekto ito sa mga tester at business analyst. Ang test case ay isang senaryo na naglalarawan sa mga hakbang, partikular na kundisyon, at parameter na kinakailangan upang suriin ang code sa ilalim ng pagsubok. Ang test fixture ay code na nagse-set up sa kapaligiran ng pagsubok upang magkaroon ng estado na kinakailangan para matagumpay na tumakbo ang pamamaraang sinusubok. Ito ay isang paunang natukoy na hanay ng mga bagay at ang kanilang pag-uugali sa ilalim ng mga tinukoy na kundisyon.

Mga yugto ng pagsubok

Ang pagsusulit ay binubuo ng tatlong yugto:
  • Tukuyin ang data ng pagsubok (mga fixture).
  • Gamitin ang code sa ilalim ng pagsubok (tawagan ang nasubok na paraan).
  • I-verify ang mga resulta at ihambing sa mga inaasahang resulta.
Lahat ng tungkol sa pagsubok sa yunit: mga diskarte, konsepto, kasanayan - 4Upang matiyak ang modularity ng pagsubok, kailangan mong ihiwalay mula sa iba pang mga layer ng application. Magagawa ito gamit ang mga stub, pangungutya, at espiya. Ang mga mock ay mga bagay na maaaring i-customize (halimbawa, iayon para sa bawat pagsubok). Hinahayaan nila kaming tukuyin kung ano ang inaasahan namin mula sa mga tawag sa pamamaraan, ibig sabihin, ang mga inaasahang tugon. Gumagamit kami ng mga kunwaring bagay para i-verify na nakukuha namin ang inaasahan namin. Ang mga stub ay nagbibigay ng hard-coded na tugon sa mga tawag sa panahon ng pagsubok. Maaari rin silang mag-imbak ng impormasyon tungkol sa tawag (halimbawa, mga parameter o bilang ng mga tawag). Ang mga ito ay minsang tinutukoy bilang mga espiya. Minsan nalilito ng mga tao ang mga terminong stub at pangungutya: ang kaibahan ay walang sinusuri ang isang stub — ginagaya lamang nito ang isang partikular na estado. Ang mock ay isang bagay na may mga inaasahan. Halimbawa, na ang isang ibinigay na pamamaraan ay dapat na tinatawag na isang tiyak na bilang ng beses. Sa ibang salita,

Mga kapaligiran sa pagsubok

Kaya, ngayon sa punto. Mayroong ilang mga pagsubok na kapaligiran (mga balangkas) na magagamit para sa Java. Ang pinakasikat sa mga ito ay ang JUnit at TestNG. Para sa aming pagsusuri dito, ginagamit namin ang: Lahat ng tungkol sa pagsubok sa yunit: mga diskarte, konsepto, kasanayan - 5Ang JUnit test ay isang paraan sa isang klase na ginagamit lamang para sa pagsubok. Ang klase ay karaniwang pinangalanang kapareho ng klase na sinusuri nito, na may "Pagsusulit" na nakadugtong sa dulo. Halimbawa, CarService -> CarServiceTest. Awtomatikong isinasama ng Maven build system ang mga ganitong klase sa saklaw ng pagsubok. Sa katunayan, ang klase na ito ay tinatawag na klase ng pagsubok. Tingnan natin sandali ang mga pangunahing anotasyon:

  • Ipinapahiwatig ng @Test na ang pamamaraan ay isang pagsubok (sa pangkalahatan, ang isang pamamaraan na minarkahan ng anotasyong ito ay isang pagsubok sa yunit).
  • Ang @Before ay nangangahulugang isang paraan na isasagawa bago ang bawat pagsubok. Halimbawa, upang punan ang isang klase ng data ng pagsubok, basahin ang data ng input, atbp.
  • Ang @After ay ginagamit upang markahan ang isang paraan na tatawagin pagkatapos ng bawat pagsubok (hal. upang i-clear ang data o ibalik ang mga default na halaga).
  • Ang @BeforeClass ay inilalagay sa itaas ng isang pamamaraan, katulad ng @Before. Ngunit ang ganitong pamamaraan ay tinatawag na isang beses lamang bago ang lahat ng mga pagsubok para sa ibinigay na klase at samakatuwid ay dapat na static. Ito ay ginagamit upang magsagawa ng mas maraming resource-intensive na operasyon, tulad ng pag-ikot ng isang test database.
  • Ang @AfterClass ay kabaligtaran ng @BeforeClass: ito ay isinasagawa nang isang beses para sa ibinigay na klase, ngunit pagkatapos lamang ng lahat ng mga pagsubok. Ito ay ginagamit, halimbawa, upang i-clear ang patuloy na mga mapagkukunan o idiskonekta mula sa isang database.
  • Ang @Ignore ay nagsasaad na ang isang paraan ay hindi pinagana at hindi papansinin sa panahon ng pangkalahatang pagsubok. Ito ay ginagamit sa iba't ibang sitwasyon, halimbawa, kung ang batayang pamamaraan ay nabago at ang pagsubok ay hindi pa nagagawang muli upang matugunan ang mga pagbabago. Sa ganitong mga kaso, kanais-nais din na magdagdag ng isang paglalarawan, ie @Ignore("Some description").
  • @Test(expected = Exception.class) ay ginagamit para sa mga negatibong pagsubok. Ito ay mga pagsubok na nagpapatunay kung paano kumikilos ang pamamaraan sa kaso ng isang error, iyon ay, inaasahan ng pagsubok na ang pamamaraan ay magtapon ng ilang uri ng pagbubukod. Ang ganitong paraan ay ipinahiwatig ng @Test annotation, ngunit may indikasyon kung aling error ang mahuhuli.
  • Sinusuri ng @Test(timeout = 100) na ang pamamaraan ay naisakatuparan nang hindi hihigit sa 100 millisecond.
  • @Mock ay ginagamit sa itaas ng isang field para magtalaga ng mock object (ito ay hindi JUnit annotation, ngunit sa halip ay mula sa Mockito). Kung kinakailangan, itinakda namin ang pag-uugali ng kunwaring para sa isang partikular na sitwasyon nang direkta sa paraan ng pagsubok.
  • Ang @RunWith(MockitoJUnitRunner.class) ay inilalagay sa itaas ng isang klase. Ang anotasyong ito ay nagsasabi sa JUnit na gamitin ang mga pagsubok sa klase. Mayroong iba't ibang mga runner, kabilang ang mga ito: MockitoJUnitRunner, JUnitPlatform, at SpringRunner. Sa JUnit 5, ang @RunWith annotation ay pinalitan ng mas malakas na @ExtendWith annotation.
Tingnan natin ang ilang paraan na ginamit upang ihambing ang mga resulta:

  • assertEquals(Object expected, Object actuals) — sinusuri kung ang mga naipasa na bagay ay pantay.
  • assertTrue(boolean flag) — sinusuri kung totoo ang naipasa na halaga.
  • assertFalse(boolean flag) — sinusuri kung mali ang ipinasa na halaga.
  • assertNull(Object object) — sinusuri kung ang naipasa na bagay ay null.
  • assertSame(Object firstObject, Object secondObject) — sinusuri kung ang mga naipasa na halaga ay tumutukoy sa parehong bagay.
  • igiit na(T t, Matcher matcher) — Sinusuri kung t natutugunan ang kundisyong tinukoy sa matcher.
Nagbibigay din ang AssertJ ng isang kapaki-pakinabang na paraan ng paghahambing: assertThat(firstObject).isEqualTo(secondObject) . Dito ko nabanggit ang mga pangunahing pamamaraan — ang iba ay mga pagkakaiba-iba ng nasa itaas.

Pagsubok sa pagsasanay

Ngayon tingnan natin ang materyal sa itaas sa isang partikular na halimbawa. Susubukan namin ang paraan ng pag-update ng isang serbisyo. Hindi namin isasaalang-alang ang layer ng DAO, dahil ginagamit namin ang 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>
At narito mayroon kaming 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());
   }
}
Linya 8 - hilahin ang na-update na bagay mula sa database. Linya 9-14 — lumikha ng isang bagay sa pamamagitan ng tagabuo. Kung ang papasok na bagay ay may field, itakda ito. Kung hindi, iiwan namin kung ano ang nasa database. Ngayon 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);
   }
Linya 1 — ang aming Runner. Linya 4 — ihihiwalay namin ang serbisyo mula sa layer ng DAO sa pamamagitan ng pagpapalit ng mock. Linya 11 — nagtakda kami ng isang pagsubok na entity (ang isa na gagamitin namin bilang guinea pig) para sa klase. Linya 22 — itinakda namin ang object ng serbisyo, na siyang susuriin namin.

@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 na ang pagsusulit ay may tatlong malinaw na dibisyon: Mga Linya 3-9 — pagtukoy ng mga fixture. Linya 11 — pagsasagawa ng code sa ilalim ng pagsubok. Mga linya 13-17 — pagsuri sa mga resulta. Sa mas malaking detalye: Mga Linya 3-4 — itakda ang gawi para sa DAO mock. Linya 5 — itakda ang instance na ia-update namin sa itaas ng aming pamantayan. Linya 11 — gamitin ang pamamaraan at kunin ang resultang pagkakataon. Linya 13 — suriin na ito ay hindi null. Linya 14 — ihambing ang ID ng resulta at ang ibinigay na mga argumento ng pamamaraan. Linya 15 — tingnan kung na-update ang pangalan. Linya 16 — tingnan ang resulta ng CPU. Linya 17 — hindi namin tinukoy ang field na ito sa halimbawa, kaya dapat itong manatiling pareho. Sinusuri namin ang kundisyong iyon dito. Patakbuhin natin ito:Lahat ng tungkol sa pagsubok sa yunit: mga diskarte, konsepto, kasanayan - 6Ang pagsubok ay berde! Makahinga tayo ng maluwag :) Sa kabuuan, pinapabuti ng pagsubok ang kalidad ng code at ginagawang mas flexible at maaasahan ang proseso ng pagbuo. Isipin kung gaano karaming pagsisikap ang kinakailangan upang muling idisenyo ang software na kinasasangkutan ng daan-daang mga file ng klase. Kapag mayroon kaming mga unit test na isinulat para sa lahat ng mga klaseng ito, maaari kaming mag-refactor nang may kumpiyansa. At higit sa lahat, nakakatulong ito sa amin na madaling makahanap ng mga bug sa panahon ng pag-unlad. Guys and gals, yan lang ang meron ako ngayon. Bigyan mo ako ng like, at mag-iwan ng komento :)
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION