portaldacalheta.pt
  • Основен
  • Agile Talent
  • Дизайнерски Живот
  • Възходът На Дистанционното
  • Рентабилност И Ефективност
Back-End

Изграждане с увереност: Ръководство за JUnit тестове



С напредването на технологиите и индустрията, преминаващи от модела на водопада към Agile и сега към DevOps, промените и подобренията в приложението се внедряват в производството в момента, в който са направени. С бързото внедряване на кода в производството, трябва да сме уверени, че промените ни работят, както и че те не нарушават никаква съществуваща функционалност.

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



  • Единично тестване , където който и да е тест обхваща най-малката единица от програма (функция или процедура). Той може или не може да вземе някои входни параметри и може или не може да върне някои стойности.
  • Интеграционно тестване , където отделни единици се тестват заедно, за да се провери дали всички единици взаимодействат помежду си, както се очаква.

Налични са множество рамки за всеки език за програмиране. Ще се съсредоточим върху писането на модули и тестване на интеграция за уеб приложение, написано на Java Пролетна рамка.



симулацията може да използва всяко разпределение на вероятностите, което потребителят дефинира.

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




Нека да преминем към Java модулното тестване през пролетта, използвайки JUnit framework. Ще започнем с нещо, за което може би сте чували: подигравка.

Какво е подигравка и кога се появява в картината?

Да предположим, че имате клас, CalculateArea, който има функция calculateArea(Type type, Double... args) което изчислява площта на фигура от дадения тип (кръг, квадрат или правоъгълник.)



Кодът е нещо подобно в нормално приложение, което не използва инжектиране на зависимости:

public class CalculateArea { SquareService squareService; RectangleService rectangleService; CircleService circleService; CalculateArea(SquareService squareService, RectangleService rectangeService, CircleService circleService) { this.squareService = squareService; this.rectangleService = rectangeService; this.circleService = circleService; } public Double calculateArea(Type type, Double... r ) { switch (type) { case RECTANGLE: if(r.length >=2) return rectangleService.area(r[0],r[1]); else throw new RuntimeException('Missing required params'); case SQUARE: if(r.length >=1) return squareService.area(r[0]); else throw new RuntimeException('Missing required param'); case CIRCLE: if(r.length >=1) return circleService.area(r[0]); else throw new RuntimeException('Missing required param'); default: throw new RuntimeException('Operation not supported'); } } } public class SquareService { public Double area(double r) { return r * r; } } public class RectangleService { public Double area(Double r, Double h) { return r * h; } } public class CircleService { public Double area(Double r) { return Math.PI * r * r; } } public enum Type { RECTANGLE,SQUARE,CIRCLE; }

Сега, ако искаме да тестваме модулно функцията calculateArea() на класа CalculateArea, тогава мотивът ни трябва да бъде да проверим дали switch случаи и условия за изключение работят. Не бива да проверяваме дали услугите за фигури връщат правилните стойности, тъй като, както беше споменато по-рано, мотивът за единично тестване на функция е да се тества логиката на функцията, а не логиката на извикванията, които функцията прави.



Така че ще се подиграваме със стойностите, върнати от отделни сервизни функции (напр. rectangleService.area() И ще тестваме извикващата функция (напр. CalculateArea.calculateArea()) Въз основа на тези подигравани стойности.

Един прост тестов случай за услугата за правоъгълник - тестване, че calculateArea() наистина извиква rectangleService.area() с правилните параметри - ще изглежда така:



import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; public class CalculateAreaTest { RectangleService rectangleService; SquareService squareService; CircleService circleService; CalculateArea calculateArea; @Before public void init() { rectangleService = Mockito.mock(RectangleService.class); squareService = Mockito.mock(SquareService.class); circleService = Mockito.mock(CircleService.class); calculateArea = new CalculateArea(squareService,rectangleService,circleService); } @Test public void calculateRectangleAreaTest() { Mockito.when(rectangleService.area(5.0d,4.0d)).thenReturn(20d); Double calculatedArea = this.calculateArea.calculateArea(Type.RECTANGLE, 5.0d, 4.0d); Assert.assertEquals(new Double(20d),calculatedArea); } }

Тук се отбелязват два основни реда:

  • rectangleService = Mockito.mock(RectangleService.class); —Това създава макет, който не е действителен обект, а подиграван.
  • Mockito.when(rectangleService.area(5.0d,4.0d)).thenReturn(20d); —Това казва, че когато се подиграва и rectangleService object’s area метод се извиква с посочените параметри, след което връща 20d.

Сега, какво се случва, когато горният код е част от приложение Spring?



import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class CalculateArea { SquareService squareService; RectangleService rectangleService; CircleService circleService; public CalculateArea(@Autowired SquareService squareService, @Autowired RectangleService rectangeService, @Autowired CircleService circleService) { this.squareService = squareService; this.rectangleService = rectangeService; this.circleService = circleService; } public Double calculateArea(Type type, Double... r ) { // (same implementation as before) } }

Тук имаме две анотации за основната Spring рамка, които да бъдат открити по време на инициализация на контекста:

  • @Component: Създава боб от тип CalculateArea
  • @Autowired: Търси зърната rectangleService, squareService и circleService и ги инжектира в зърното calculatedArea

По същия начин създаваме боб и за други класове:



import org.springframework.stereotype.Service; @Service public class SquareService { public Double area(double r) { return r*r; } } import org.springframework.stereotype.Service; @Service public class CircleService { public Double area(Double r) { return Math.PI * r * r; } } import org.springframework.stereotype.Service; @Service public class RectangleService { public Double area(Double r, Double h) { return r*h; } }

Сега, ако пуснем тестовете, резултатите са същите. Използвахме инжектор на конструктор тук и за щастие не променяме тестовия си случай.

Но има и друг начин за инжектиране на зърната на услугите за квадрат, кръг и правоъгълник: инжектиране на поле. Ако използваме това, тогава нашият тестов случай ще се нуждае от някои незначителни промени.

Няма да навлизаме в дискусията кой механизъм за инжектиране е по-добър, тъй като това не е в обхвата на статията. Но можем да кажем следното: Без значение какъв тип механизъм използвате, за да инжектирате боб, винаги има начин да напишете JUnit тестове за него.

В случай на инжектиране на поле, кодът е нещо подобно:

@Component public class CalculateArea { @Autowired SquareService squareService; @Autowired RectangleService rectangleService; @Autowired CircleService circleService; public Double calculateArea(Type type, Double... r ) { // (same implementation as before) } }

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

Кодът за нашите класове на обслужване остава същият както по-горе, но кодът за тестовия клас е следният:

public class CalculateAreaTest { @Mock RectangleService rectangleService; @Mock SquareService squareService; @Mock CircleService circleService; @InjectMocks CalculateArea calculateArea; @Before public void init() { MockitoAnnotations.initMocks(this); } @Test public void calculateRectangleAreaTest() { Mockito.when(rectangleService.area(5.0d,4.0d)).thenReturn(20d); Double calculatedArea = this.calculateArea.calculateArea(Type.RECTANGLE, 5.0d, 4.0d); Assert.assertEquals(new Double(20d),calculatedArea); } }

Няколко неща протичат по различен начин тук: не основите, а начинът, по който го постигаме.

Първо, начинът, по който се подиграваме с нашите обекти: Използваме @Mock анотации заедно с initMocks() за създаване на подигравки. Второ, инжектираме подигравки в действителния обект, използвайки @InjectMocks заедно с initMocks().

Това е направено само за намаляване на броя на редовете код.

Какво представляват тестовите бегачи и какви типове има?

В горната проба основният бегач, който се използва за провеждане на всички тестове, е BlockJUnit4ClassRunner който открива всички анотации и съответно изпълнява всички тестове.

Ако искаме повече функционалност, тогава можем да напишем персонализиран бегач. Например в горния тестов клас, ако искаме да пропуснем реда MockitoAnnotations.initMocks(this); тогава бихме могли да използваме различен бегач, който е изграден върху BlockJUnit4ClassRunner, напр. MockitoJUnitRunner.

Използвайки MockitoJUnitRunner, дори не е необходимо да инициализираме макети и да ги инжектираме. Това ще бъде направено от MockitoJUnitRunner само чрез четене на анотации.

(Има и SpringJUnit4ClassRunner, което инициализира ApplicationContext, необходимо за тестване за интегриране на Spring - точно както ApplicationContext се създава при стартиране на приложение Spring. Това ще разгледаме по-късно.)

Частично подигравка

Когато искаме обект от тестовия клас да се подиграва с някакъв метод (и), но също така да извика някакъв действителен метод (и), тогава се нуждаем от частично подиграване. Това се постига чрез @Spy в JUnit.

За разлика от използването на @Mock, с @Spy се създава реален обект, но методите на този обект могат да бъдат подигравани или всъщност да бъдат извикани - каквото ни трябва.

Например, ако area метод в класа RectangleService извиква допълнителен метод log() и всъщност искаме да отпечатаме този дневник, тогава кодът се променя на нещо като по-долу:

@Service public class RectangleService { public Double area(Double r, Double h) { log(); return r*h; } public void log() { System.out.println('skip this'); } }

Ако сменим @Mock анотация на rectangleService до @Spy, и също така направете някои промени в кода, както е показано по-долу, тогава в резултатите всъщност ще видим отпечатване на регистрационните файлове, но методът area() ще бъде подиграван. Тоест, оригиналната функция се изпълнява единствено заради нейните странични ефекти; връщаните му стойности се заменят с подигравани.

@RunWith(MockitoJUnitRunner.class) public class CalculateAreaTest { @Spy RectangleService rectangleService; @Mock SquareService squareService; @Mock CircleService circleService; @InjectMocks CalculateArea calculateArea; @Test public void calculateRectangleAreaTest() { Mockito.doCallRealMethod().when(rectangleService).log(); Mockito.when(rectangleService.area(5.0d,4.0d)).thenReturn(20d); Double calculatedArea = this.calculateArea.calculateArea(Type.RECTANGLE, 5.0d, 4.0d); Assert.assertEquals(new Double(20d),calculatedArea); } }

Как става въпрос за тестване на Controller или RequestHandler?

От това, което научихме по-горе, тестовият код на контролер за нашия пример ще бъде нещо като по-долу:

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class AreaController { @Autowired CalculateArea calculateArea; @RequestMapping(value = 'api/area', method = RequestMethod.GET) @ResponseBody public ResponseEntity calculateArea( @RequestParam('type') String type, @RequestParam('param1') String param1, @RequestParam(value = 'param2', required = false) String param2 ) { try { Double area = calculateArea.calculateArea( Type.valueOf(type), Double.parseDouble(param1), Double.parseDouble(param2) ); return new ResponseEntity(area, HttpStatus.OK); } catch (Exception e) { return new ResponseEntity(e.getCause(), HttpStatus.INTERNAL_SERVER_ERROR); } } } import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @RunWith(MockitoJUnitRunner.class) public class AreaControllerTest { @Mock CalculateArea calculateArea; @InjectMocks AreaController areaController; @Test public void calculateAreaTest() { Mockito .when(calculateArea.calculateArea(Type.RECTANGLE,5.0d, 4.0d)) .thenReturn(20d); ResponseEntity responseEntity = areaController.calculateArea('RECTANGLE', '5', '4'); Assert.assertEquals(HttpStatus.OK,responseEntity.getStatusCode()); Assert.assertEquals(20d,responseEntity.getBody()); } }

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

Този код е по-добър:

import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup; @RunWith(SpringJUnit4ClassRunner.class) public class AreaControllerTest { @Mock CalculateArea calculateArea; @InjectMocks AreaController areaController; MockMvc mockMvc; @Before public void init() { mockMvc = standaloneSetup(areaController).build(); } @Test public void calculateAreaTest() throws Exception { Mockito .when(calculateArea.calculateArea(Type.RECTANGLE,5.0d, 4.0d)) .thenReturn(20d); mockMvc.perform( MockMvcRequestBuilders.get('/api/area?type=RECTANGLE¶m1=5¶m2=4') ) .andExpect(status().isOk()) .andExpect(content().string('20.0')); } }

Тук можем да видим как MockMvc поема работата по извършване на действителни API повиквания. Той също така има някои специални съвпадения като status() и content() които улесняват проверката на съдържанието.

Тестване на Java интеграция с помощта на JUnit и Mocks

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

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

За това дефинираме всички зърна в клас, да кажем TestConfig.java:

import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class TestConfig { @Bean public AreaController areaController() { return new AreaController(); } @Bean public CalculateArea calculateArea() { return new CalculateArea(); } @Bean public RectangleService rectangleService() { return new RectangleService(); } @Bean public SquareService squareService() { return new SquareService(); } @Bean public CircleService circleService() { return new CircleService(); } }

Сега да видим как използваме този клас и да напишем тест за интеграция:

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {TestConfig.class}) public class AreaControllerIntegrationTest { @Autowired AreaController areaController; MockMvc mockMvc; @Before public void init() { mockMvc = standaloneSetup(areaController).build(); } @Test public void calculateAreaTest() throws Exception { mockMvc.perform( MockMvcRequestBuilders.get('/api/area?type=RECTANGLE¶m1=5¶m2=4') ) .andExpect(status().isOk()) .andExpect(content().string('20.0')); } }

Тук се променят няколко неща:

  • @ContextConfiguration(classes = {TestConfig.class}) - това разказва тестовия случай, в който се намират всички дефиниции на боб.
  • Сега вместо @InjectMocks ние използваме:
@Autowired AreaController areaController;

Всичко останало остава същото. Ако отстраним грешката на теста, ще видим, че кодът действително работи до последния ред на area() метод в RectangleService където return r*h се изчислява. С други думи, действителната бизнес логика работи.

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

Бонус: Как да създадете данни от тестове за големи обекти

Често това, което спира back-end разработчиците при писането на модулни или интеграционни тестове, са тестовите данни, които трябва да подготвим за всеки тест.

Обикновено, ако данните са достатъчно малки, имащи една или две променливи, тогава е лесно просто да създадете обект от тестов клас данни и да присвоите някои стойности.

Например, ако очакваме подиграван обект да върне друг обект, когато се извика функция на подигравания обект, ще направим нещо подобно:

Class1 object = new Class1(); object.setVariable1(1); object.setVariable2(2);

И тогава, за да използваме този обект, ще направим нещо подобно:

Mockito.when(service.method(arguments...)).thenReturn(object);

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

Решението е да се генерира JSON схема от горния клас и да се добавят съответните данни в JSON файла веднъж. Сега в тестовия клас, където създаваме Class1 обект, не е нужно да създаваме обекта ръчно. Вместо това четем файла JSON и, използвайки ObjectMapper, го преобразуваме в необходимия Class1 клас:

ObjectMapper objectMapper = new ObjectMapper(); Class1 object = objectMapper.readValue( new String(Files.readAllBytes( Paths.get('src/test/resources/'+fileName)) ), Class1.class );

Това е еднократно усилие за създаване на JSON файл и добавяне на стойности към него. Всички нови тестове след това могат да използват копие на този JSON файл с полета, променени според нуждите на новия тест.

Основи на JUnit: Множество подходи и прехвърляеми умения

Ясно е, че има много начини за писане на модулни тестове на Java в зависимост от това как решим да инжектираме боб. За съжаление повечето статии по темата са склонни да приемат, че има само един начин, така че е лесно да се объркате, особено когато работите с код, написан под друго предположение. Надяваме се, че нашият подход тук спестява време на разработчиците да измислят правилния начин за подигравка и кой тестов бегач да използва.

Независимо от езика или рамката, които използваме - може би дори всяка нова версия на Spring или JUnit - концептуалната основа остава същата, както е обяснено в горния урок за JUnit. Приятно тестване!

Разбиране на основите

Как се пише единичен тест в Java?

JUnit е най-известната рамка за писане на модулни тестове в Java. Пишете тестови методи, които извикват действителните методи за тестване. Тестовият случай проверява поведението на кода, като утвърждава връщаната стойност спрямо очакваната стойност, като се имат предвид предадените параметри.

Каква е най-добрата рамка за модулно тестване за Java?

По-голямата част от разработчиците на Java са съгласни, че JUnit е най-добрата рамка за модулно тестване. Той е де факто стандарт от 1997 г. и със сигурност има най-голям обем поддръжка в сравнение с други рамки за модулно тестване на Java.

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

При модулно тестване отделни единици (често обектните методи се считат за „единица“) се тестват по автоматизиран начин.

Защо използваме JUnit тестване?

JUnit тестването се използва за тестване на поведението на методите в класовете, които сме написали. Тестваме метод за очакваните резултати и понякога случаи на хвърляне на изключения - дали методът е в състояние да се справи с изключенията по начина, по който искаме.

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

Каква е ползата от JUnit?

JUnit е рамка, която предоставя много различни класове и методи за лесно писане на модулни тестове.

JUnit с отворен код ли е?

Да, JUnit е проект с отворен код, поддържан от много активни разработчици.

Защо е важен JUnit?

JUnit намалява шаблона, който разработчиците трябва да използват, когато пишат модулни тестове.

Кой е изобретил JUnit?

Кент Бек и Ерих Гама първоначално създадоха JUnit. Днес проектът с отворен код има над сто участници.

Навигация в предприятието: Методи за проектиране за сътрудничество със заинтересованите страни

Бъдещето На Работата

Навигация в предприятието: Методи за проектиране за сътрудничество със заинтересованите страни
Кой знаеше Adobe CC Could Wireframe?

Кой знаеше Adobe CC Could Wireframe?

Инструменти И Уроци

Популярни Публикации
Широки срещу тесни набори от умения: Демистифицирани умения за софтуерно инженерство
Широки срещу тесни набори от умения: Демистифицирани умения за софтуерно инженерство
Талантът не е стока
Талантът не е стока
iOS 9 Betas и WatchOS 2 за разработчици
iOS 9 Betas и WatchOS 2 за разработчици
Ръководство за многообработващи мрежови сървърни модели
Ръководство за многообработващи мрежови сървърни модели
Как да създадете персонализирани шрифтове: 7 стъпки и 3 казуса
Как да създадете персонализирани шрифтове: 7 стъпки и 3 казуса
 
Настройка на производителността на базата данни на SQL за разработчици
Настройка на производителността на базата данни на SQL за разработчици
Събуждане на спяща индустрия: Нарушаване на индустрията на матраците
Събуждане на спяща индустрия: Нарушаване на индустрията на матраците
Figma срещу Sketch срещу Axure - Преглед, основан на задачи
Figma срещу Sketch срещу Axure - Преглед, основан на задачи
Разбиране на нюансите на класификацията на шрифтовете
Разбиране на нюансите на класификацията на шрифтовете
Възраст преди красотата - Ръководство за дизайн на интерфейси за възрастни възрастни
Възраст преди красотата - Ръководство за дизайн на интерфейси за възрастни възрастни
Популярни Публикации
  • конвертирайте заплатата в договорна ставка
  • на какъв език е написан линуксът
  • как да проверите изтичане на памет
  • смесена реалност срещу разширена реалност
  • кое от тях не е причина, поради която осигуряването на уеб приложения от страна на сървъра е трудно
Категории
  • Agile Talent
  • Дизайнерски Живот
  • Възходът На Дистанционното
  • Рентабилност И Ефективност
  • © 2022 | Всички Права Запазени

    portaldacalheta.pt