Создание макетов для модульного тестирования — модернизация

#java #api #mocking #retrofit

#java #API #издевательство #модернизация

Вопрос:

Существует приложение командной строки для отображения прогноза на завтра с использованием общедоступного API

Пример вывода может быть следующим:

 Tomorrow (2019/05/01) in city XYZ:
Clear
Temp: 26.5 °C
Wind: 7.6 mph
Humidity: 61%
  

Вопрос: Как вы создадите тестовый пример, чтобы тесты не касались реального сервиса и работали без Интернета.

Я попытался создать тест junit для того же, и он работал нормально до тех пор, пока я не использую api напрямую.

Может кто-нибудь, пожалуйста, помогите, как я могу создать макет для моего модульного тестирования.

App.java

 import api.ForecastServiceImpl;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

import java.io.IOException;
import java.time.LocalDate;

public class App {

    public static void main(String[] args) throws IOException {
        if (args.length < 1) {
            System.out.println("Pass city name as an argument");
            System.exit(1);
        }

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://www.metaweather.com")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        LocalDate tomorrow = LocalDate.now().plusDays(1);

        ForecastServiceImpl service = new ForecastServiceImpl(retrofit);
        System.out.println(service.getForecast(args[0], tomorrow));
    }
}
  

ForecastServiceImpl.java

 package api;

import model.City;
import model.Forecast;
import retrofit2.Call;
import retrofit2.Retrofit;
import util.PathDate;

import java.io.IOException;
import java.time.LocalDate;
import java.util.List;
import java.util.Objects;

public class ForecastServiceImpl {

    private Retrofit retrofit;

    public ForecastServiceImpl(Retrofit retrofit) {
        this.retrofit = retrofit;
    }

    public String getForecast(String cityName, LocalDate date) throws IOException {
        PathDate pathDate = new PathDate(date);

        ForecastService service = retrofit.create(ForecastService.class);
        Call<List<City>> findCityCall = service.findCityByName(cityName.toLowerCase());
        City city = Objects.requireNonNull(findCityCall.execute().body())
                .stream()
                .findFirst()
                .orElseThrow(() -> new RuntimeException(String.format("Can't find city id for %s", cityName)));

        Call<List<Forecast>> forecastCall = service.getForecast(city.getWoeid(), pathDate);
        Forecast forecast = Objects.requireNonNull(forecastCall.execute().body())
                .stream()
                .findFirst()
                .orElseThrow(() -> new RuntimeException(String.format("Can't get forecast for %s", cityName)));

        return String.format("Weather on (%s) in %s:n%s", pathDate, city.getTitle(), forecast);
    }
}
  

ForecastService.java

 package api;

import model.City;
import model.Forecast;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;
import retrofit2.http.Query;
import util.PathDate;

import java.util.List;

public interface ForecastService {

    @GET("/api/location/{city_id}/{date}/")
    Call<List<Forecast>> getForecast(@Path("city_id") Long cityId, @Path("date") PathDate date);

    @GET("/api/location/search/")
    Call<List<City>> findCityByName(@Query("query") String city);
}
  

City.java

 package model;

public class City {

    private String title;
    private Long woeid;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Long getWoeid() {
        return woeid;
    }

    public void setWoeid(Long woeid) {
        this.woeid = woeid;
    }
}
  

Forecast.java

 package model;

import com.google.gson.annotations.SerializedName;

public class Forecast {

    private Long id;
    @SerializedName("weather_state_name")
    private String weatherState;
    @SerializedName("wind_speed")
    private Double windSpeed;
    @SerializedName("the_temp")
    private Double temperature;
    private Integer humidity;

    public Long getId() {
        return id;
    }

    public Forecast setId(Long id) {
        this.id = id;
        return this;
    }

    public String getWeatherState() {
        return weatherState;
    }

    public Forecast setWeatherState(String weatherState) {
        this.weatherState = weatherState;
        return this;
    }

    public Double getWindSpeed() {
        return windSpeed;
    }

    public Forecast setWindSpeed(Double windSpeed) {
        this.windSpeed = windSpeed;
        return this;
    }

    public Double getTemperature() {
        return temperature;
    }

    public Forecast setTemperature(Double temperature) {
        this.temperature = temperature;
        return this;
    }

    public Integer getHumidity() {
        return humidity;
    }

    public Forecast setHumidity(Integer humidity) {
        this.humidity = humidity;
        return this;
    }

    @Override
    public String toString() {
        return String.format("%snTemp: %.1f °CnWind: %.1f mphnHumidity: %d%%",
                weatherState, temperature, windSpeed, humidity);
    }
}

  

PathDate.java

 package util;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class PathDate {

    private final LocalDate date;

    public PathDate(LocalDate date) {
        this.date = date;
    }

    @Override public String toString() {
        return date.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
    }
}
  

Utils.java

 package util;

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Utils {

    public static byte[] readResourceFileToBytes(String filename) {
        byte[] fileBytes = new byte[0];
        try {
            Path path = Paths.get(Utils.class.getClassLoader().getResource(filename).toURI());
            fileBytes = Files.readAllBytes(path);
        } catch (URISyntaxException|IOException|NullPointerException e) {
            e.printStackTrace();
        }

        return fileBytes;
    }
}

  

Комментарии:

1. Вы хотите правильно написать тест для getForecast функции?

2. Да, правильно для getForecast

3. @DeepakPatankar не могли бы вы помочь мне с примером тестового примера?

Ответ №1:

service.getForecast(city.getWoeid(), pathDate); Возвращает нам Call<List<Forecast>> объект. И когда мы вызываем execute этот объект, тогда выполняется фактический вызов API. Поскольку мы не хотим выполнять фактический вызов API, мы можем попробовать издеваться над Call<List<Forecast>> объектом.

Мы можем издеваться над Call классом, например

 Call<List<Forecast>> mockedListForeCast = mock(Call.class);

  

Приведенное выше утверждение создает макет объекта Call<List<Forecast>> . Мы можем использовать when для определения того, что должно происходить при вызове метода для макетируемого объекта.

 // here I am returning the singleton list, you can return a list of forecast
when(mockedListForeCast.execute()).thenReturn(Response.success(Collections.singletonList()));

  

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

Таким образом, мы имитируем ответ API, и нам не нужно выполнять фактический вызов API.

Редактировать:

Вы также можете издеваться над своим модифицированным API, используя модифицированный макет.

Комментарии:

1. Привет, @user1873274, вот как я написал модульные тесты в своем коде. Не могли бы вы проверить, работает ли это и для вас? Возможно, вам потребуется изменить некоторую часть строк в приведенном выше ответе в соответствии с вашим случаем.

2. Спасибо @Deepak, но один вопрос, пожалуйста, объясните это немного подробнее, поскольку я новичок в этом. Должен ли я создать макет метода, в котором я должен использовать класс call для макетирования, а затем определять макет вывода?

3. Конечно, добавит объяснение

4. Привет, @user1873274, я обновил свой ответ. Пожалуйста, дайте мне знать, если это поможет.

5. я понял концепцию, и это было полезно. Нужно ли мне создавать макет метода в самом тестовом классе? или это должно быть в отдельном классе? Извините, что задаю слишком простые вопросы.