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.
- 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).
- 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.
- 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,- Code Coverage sa Medium
- Pagsubok sa Black Box: Isang Malalim na Tutorial na May Mga Halimbawa At Teknik
- 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.
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.
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: Ang 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.
- 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.
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:Ang 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 :)
GO TO FULL VERSION