13.7. 测试应用


良好的应用程序包括测试覆盖。测试时间表项目中的限制和 solver。

13.7.1. 测试 school 计时的限制

要以隔离方式测试每个 timetable 项目约束,请在单元测试中使用 ConstraintVerifier。这会测试每个约束与其他测试隔离的情况,这会在添加具有适当测试覆盖的新约束时降低维护。

此测试会验证约束 TimeTableConstraintProvider::roomConflict,当同一间有 3 个经验时,两个经验都有相同的 timeslot,被 penalizes 为 1。因此,如果约束权重为 10hard,它将分数降低为 -10hard

流程

创建 src/test/java/org/acme/optaplanner/solver/TimeTableConstraintProviderTest.java 类:

package org.acme.optaplanner.solver;

import java.time.DayOfWeek;
import java.time.LocalTime;

import javax.inject.Inject;

import io.quarkus.test.junit.QuarkusTest;
import org.acme.optaplanner.domain.Lesson;
import org.acme.optaplanner.domain.Room;
import org.acme.optaplanner.domain.TimeTable;
import org.acme.optaplanner.domain.Timeslot;
import org.junit.jupiter.api.Test;
import org.optaplanner.test.api.score.stream.ConstraintVerifier;

@QuarkusTest
class TimeTableConstraintProviderTest {

    private static final Room ROOM = new Room("Room1");
    private static final Timeslot TIMESLOT1 = new Timeslot(DayOfWeek.MONDAY, LocalTime.of(9,0), LocalTime.NOON);
    private static final Timeslot TIMESLOT2 = new Timeslot(DayOfWeek.TUESDAY, LocalTime.of(9,0), LocalTime.NOON);

    @Inject
    ConstraintVerifier<TimeTableConstraintProvider, TimeTable> constraintVerifier;

    @Test
    void roomConflict() {
        Lesson firstLesson = new Lesson(1, "Subject1", "Teacher1", "Group1");
        Lesson conflictingLesson = new Lesson(2, "Subject2", "Teacher2", "Group2");
        Lesson nonConflictingLesson = new Lesson(3, "Subject3", "Teacher3", "Group3");

        firstLesson.setRoom(ROOM);
        firstLesson.setTimeslot(TIMESLOT1);

        conflictingLesson.setRoom(ROOM);
        conflictingLesson.setTimeslot(TIMESLOT1);

        nonConflictingLesson.setRoom(ROOM);
        nonConflictingLesson.setTimeslot(TIMESLOT2);

        constraintVerifier.verifyThat(TimeTableConstraintProvider::roomConflict)
                .given(firstLesson, conflictingLesson, nonConflictingLesson)
                .penalizesBy(1);
    }

}
Copy to Clipboard Toggle word wrap

请注意,ConstraintVerifier 在测试过程中如何忽略约束权重,即使这些约束权重在 ConstraintProvider 中被硬编码。这是因为在进入生产环境前,会定期更改约束权重。这样,约束的权重调整不会破坏单元测试。

13.7.2. 测试 school timetable solver

这个示例测试红帽在 Red Hat build of Quarkus 上构建的 OptaPlanner National Timetable 项目。它使用 JUnit 测试来生成测试数据集,并将其发送到 TimeTableController 来解决。

流程

  1. 使用以下内容创建 src/test/java/com/example/rest/TimeTableResourceTest.java 类:

    package com.exmaple.optaplanner.rest;
    
    import java.time.DayOfWeek;
    import java.time.LocalTime;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.inject.Inject;
    
    import io.quarkus.test.junit.QuarkusTest;
    import com.exmaple.optaplanner.domain.Room;
    import com.exmaple.optaplanner.domain.Timeslot;
    import com.exmaple.optaplanner.domain.Lesson;
    import com.exmaple.optaplanner.domain.TimeTable;
    import com.exmaple.optaplanner.rest.TimeTableResource;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.Timeout;
    
    import static org.junit.jupiter.api.Assertions.assertFalse;
    import static org.junit.jupiter.api.Assertions.assertNotNull;
    import static org.junit.jupiter.api.Assertions.assertTrue;
    
    @QuarkusTest
    public class TimeTableResourceTest {
    
        @Inject
        TimeTableResource timeTableResource;
    
        @Test
        @Timeout(600_000)
        public void solve() {
            TimeTable problem = generateProblem();
            TimeTable solution = timeTableResource.solve(problem);
            assertFalse(solution.getLessonList().isEmpty());
            for (Lesson lesson : solution.getLessonList()) {
                assertNotNull(lesson.getTimeslot());
                assertNotNull(lesson.getRoom());
            }
            assertTrue(solution.getScore().isFeasible());
        }
    
        private TimeTable generateProblem() {
            List<Timeslot> timeslotList = new ArrayList<>();
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(8, 30), LocalTime.of(9, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(9, 30), LocalTime.of(10, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(10, 30), LocalTime.of(11, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(13, 30), LocalTime.of(14, 30)));
            timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(14, 30), LocalTime.of(15, 30)));
    
            List<Room> roomList = new ArrayList<>();
            roomList.add(new Room("Room A"));
            roomList.add(new Room("Room B"));
            roomList.add(new Room("Room C"));
    
            List<Lesson> lessonList = new ArrayList<>();
            lessonList.add(new Lesson(101L, "Math", "B. May", "9th grade"));
            lessonList.add(new Lesson(102L, "Physics", "M. Curie", "9th grade"));
            lessonList.add(new Lesson(103L, "Geography", "M. Polo", "9th grade"));
            lessonList.add(new Lesson(104L, "English", "I. Jones", "9th grade"));
            lessonList.add(new Lesson(105L, "Spanish", "P. Cruz", "9th grade"));
    
            lessonList.add(new Lesson(201L, "Math", "B. May", "10th grade"));
            lessonList.add(new Lesson(202L, "Chemistry", "M. Curie", "10th grade"));
            lessonList.add(new Lesson(203L, "History", "I. Jones", "10th grade"));
            lessonList.add(new Lesson(204L, "English", "P. Cruz", "10th grade"));
            lessonList.add(new Lesson(205L, "French", "M. Curie", "10th grade"));
            return new TimeTable(timeslotList, roomList, lessonList);
        }
    
    }
    Copy to Clipboard Toggle word wrap

    此测试会验证在解决后,所有课程都会被分配给一个时间插槽和房间。它还会验证它是否找到了可行的解决方案(没有硬约束问题)。

  2. 将 test 属性添加到 src/main/resources/application.properties 文件中:

    # The solver runs only for 5 seconds to avoid a HTTP timeout in this simple implementation.
    # It's recommended to run for at least 5 minutes ("5m") otherwise.
    quarkus.optaplanner.solver.termination.spent-limit=5s
    
    # Effectively disable this termination in favor of the best-score-limit
    %test.quarkus.optaplanner.solver.termination.spent-limit=1h
    %test.quarkus.optaplanner.solver.termination.best-score-limit=0hard/*soft
    Copy to Clipboard Toggle word wrap

通常,解决者在 200 毫秒内找到可行的解决方案。注意 application.properties 文件如何在测试过程中覆盖 solver 终止,以便在找到可行的解决方案 (0hard86_64) soft) 后马上终止。这可避免对解决者进行硬编码,因为单元测试可能会在任意硬件上运行。这种方法可确保测试运行足够长,以查找可行的解决方案,即使在较慢的系统中。但它不会严格运行比严格运行的时间更长,即使在快速系统上也是如此。

返回顶部
Red Hat logoGithubredditYoutubeTwitter

学习

尝试、购买和销售

社区

关于红帽文档

通过我们的产品和服务,以及可以信赖的内容,帮助红帽用户创新并实现他们的目标。 了解我们当前的更新.

让开源更具包容性

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。欲了解更多详情,请参阅红帽博客.

關於紅帽

我们提供强化的解决方案,使企业能够更轻松地跨平台和环境(从核心数据中心到网络边缘)工作。

Theme

© 2025 Red Hat