Może moje pytanie wyda się newbie, ale nadal tak się czuję. Pokaże na prymitywnym przykładzie:
Mam w środku aplikacji javowej gdzieś w logice biznesowej nazwijmy to QuestionService metodę:
@Transactional
public boolean checkUserAnswer(int questionId, String userAnswer) {
Question q = findById(questionId);
Exam exam = examService.findByQuestionId(questionId);
Predicate<Answer> correctAnswer = a -> advancedComparator.isEqual(userAnswer, a.str(), exam.settings());
boolean isUserAnswerCorrect = q.getAnswers().stream().anyMatch(correctAnswer);
if(isUserAnswerCorrect) {
q.markGoodAnswer();
} else {
q.markWrongAnswer();
}
return isUserAnswerCorrect;
}
operuje tam sobie na modelu wyciągam encje ExamSettings i przekazuje do AdvancedComparator metody
public boolean isEqual(String userAnswer, String answer, ExamSettings exam)
- ta klaska korzysta sobie z modelu ExamSettings, z kilku booleanów READONLY.
Wysoko od strony serwisów mam też ExamSettingsDTO i metode w ExamService typu:
@Transactional
public void changeExamSettings(ExamSettingsDTO settings) {
ExamSettings newSettings = ef.createExamSettingsFromDTO(settings);
examSettingsRepository.save(newSettings);
}
ef to EntityFactory/konwerter która tworzy poprawne obiekty modelu z dto i ma metodę:
public ExamSettings createExamSettingsFromDTO(ExamSettingsDTO dto) {
Exam exam = examService.findById(dto.getExamId());
ExamSettings settings = new ExamSettings();
settings.setExam(exam);
settings.setExamId(dto.getExamId());
settings.setIgnoreBlank(dto.isIgnoreBlank());
settings.setIgnoreCase(dto.isIgnoreCase());
settings.setIgnoreDiacritics(dto.isIgnoreDiacritics());
settings.setIgnoreFirstAndLastSpaces(false); //TODO
settings.setIgnorePunctuation(dto.isIgnorePunctuation());
settings.setIgnoreSpaces(dto.isIgnoreSpaces());
return settings;
}
dodatkowo setery modelu ExamSettings są package bo kodzie jest taka konwencja,
że spoza niektórych pakietów nie pozwalam od tak sobie tworzyć obiektów modelu przez konstruktor. do tego trzeba używać/pisać konwerter-fabryke!
Teraz jednak chciałabym przetestować klasę AdvancedComparator metodę advancedComparator.isEqual(userAnswer, correctAnswer, examSettings)
.
Najłatwiej byłoby mi stworzyć w teście new ExamSettings()
, seterami ustawić tych kilka booleanów z których ta metoda isEqual korzysta i ją wywołać.
Zwłascza jak z ExamSettings ta metoda olewa np. exam, examId itd... Jednak przez package settery i tą konwencję całą tak nie mogę tak w teście zrobić.
Muszę robić inaczej -niżej przykładowy kod testu i komentarze ilustrujące moje podejście:
@Test
public void ignoreSpaceShouldIgnoreNonBreakingSpaceToo() {
// given
// tworze egzamin. olewam, stworzenie do egzaminu pytania, bo metoda advancedComparator.isEqual ma to gdzieś
final int examId = examService.createExam("non breaking space anty hack");
final String correctAnswer = "<- non breaking space here";
final String userAnswer = " <- non breakingspace here";
// tworze ExamSettingsDTO. olewam, wywołania typu examService.changeSettings , bo przetestuje sobie bezpośrednio
// advancedComparator.isEqual i model dostarcze w teście.
ExamSettingsDTO examSettings = new ExamSettingsDTO();
examSettings.setExamId(examId); // ustawiam id żeby fabryka sobie poradziła z konwersją
examSettings.setIgnoreSpaces(true);
// używam fabryko-konwertera żeby dostać encje modelu. (w bazie tak naprawdę egzamin ma inne settings model)
ExamSettings examSettingsModel = entityFactory.createExamSettingsFromDTO(examSettings);
// when
// wywołuje bezpośrednio gdzieś wybebeszoną metodę advancedComparator.isEqual z tym co chce.
boolean isCorrect = advancedComparator.isEqual(userAnswer, correctAnswer, examSettingsModel);
// then
assertTrue(isCorrect);
}
Obiło mi się o uszy, że jak coś jest trudne w testowaniu to jest źle napisane.
- Czy jest coś co byście tutaj zmienili / poprawili albo są błędy w moim myśleniu?
- Może najlepiej środek aplikacji też testować wywołując metody z góry/z serwisów? czyli przykładowo u mnie mogę tego AdvancedComparatora sobie przetestować za pomocą checkUserAnswer (na samej górze jest kod tej metody). Aczkolwiek nie wiem jak to się ma do testowania konkretnie jednej rzeczy i prostoty. Przy testowaniu od góry,
będę musiała użyć z 3 serwisów i normalnie stworzyć egzamin, pytania, zmienić ustawienia egzaminu, wywołać checkUserAnswer a tak naprawdę chce przetestować
tylko jedną prostą metodę z środka. - Czy konwencja fabryk/konwerterów jest okey?
- advancedComparator.isEqual czyta model read only - może ta metoda powinna przyjmować inny obiekt, który w teście będę mogła stworzyć przez
new
a ExamSettings będę po prostu do tego konwertować w środku logiki? - Może wszystko jest robię dobrze i nie ma się czym przejmować?
Wiem jak wygląda kod w większych komercyjnych projektach i że to są problemy pierwszego świata, ale chcę zweryfikować poprawność swoje podejścia i myślenia na prymitywnym przykładzie.
PS więcej kodu mogę dostarczyć
PS2 najłatwiej mi rozumieć na przykładach