Testdaten anlegen

Motivation

In der Entwicklungsphase der GUI und beim Testen ist es oft hilfreich, wenn die Datenbank nicht leer ist, sondern eine „Grundausstattung“ von Objekten existiert. Das kann man über einen Service erreichen, der eine Methode bereitstellt, die beim Start der Spring Boot Applikation aufgerufen wird.

In den Startup eingreifen

Beim Startup der Applikation (und beim Refresh des Context) wird ein ContextRefreshEvent ausgelöst, an den ein Listener gebunden werden kann.  Spring führt diese Bindung automatisch aus, wenn eine entsprechende Bean verfügbar ist.. In der Methode onApplicationEvent(…) kann dann die Initialisierung stattfinden. Aus Gründen der Modularisierung ist die eigentliche Initialisierung in einen Service ausgelagert. Da dieses Event mehr als einmal ausgelöst wird (siehe z.B. hier), muss in einer sinnvollen Weise sichergestellt werden, dass die Initialisierung nur einmal stattfindet. Im Beispiel übernimmt das die Methode des StartupService, der über @Autowired (Z. 14) als Spring Bean eingebunden wird. @Component macht die Klasse zur Spring Bean (Z. 12), die Implementierung von ApplicationListener mit dem Template Parameter ContextRefreshedEvent (Z. 13) führt dazu, dass Spring diese Bean automatisch als Listener für diese Events bindet.

package de.fh_zwickau.pti.geobe.component;

import de.fh_zwickau.pti.geobe.service.IStartupService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

/**
 * Initialize test data on startup
 */
@Component
public class ContextRefreshedListener implements ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    private IStartupService startupService;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        startupService.initApplicationData();
    }
}

StartupService

Der Service initialisiert die Datenbank in initApplicationData() und stellt außerdem für Testmethoden eine Methode cleanupAll() zur Verfügung, mit der die Datenbank wieder vollständig geleert werden kann. In Zeile 34 wird sichergestellt, dass die Methode nur maximal einmal bei einer leeren Datenbank ausgeführt wird und dadurch auch keine Produktionsdaten überschreiben kann.
In cleanupAll() müssen erst einmal die Assoziationen zwischen den Objekten entfernt werden (Zeilen 54-56, 60), bevor diese gelöscht werden können. Andernfalls würde eine
Integritätsbedingung verletzt und eine Exception geworfen.

...
/**
 *
 * Create and clear test data
 */
@Service
@Slf4j
class StartupService implements IStartupService {
    private boolean isInitialized = false

    @Autowired
    private ProjectRepository projectRepository
    @Autowired
    private TaskRepository taskRepository
    @Autowired
    private SprintRepository sprintRepository
    @Autowired
    private TaskService taskService

    @Override
    void initApplicationData() {
        if(!projectRepository.findAll() && !taskRepository.findAll() && ! sprintRepository.findAll()) {
            int cpl = 0
            log.info("initializing data at ${LocalDateTime.now()}")
            Project p = new Project([name: 'Projekt Küche', budget: 1000])
            p.backlog.add(new Subtask(tag: 'Tee kochen', description: 'Kanne zum Wasser!', estimate: 42))
// create lots more of test data
// ...
// ...
            projectRepository.saveAndFlush(p)
            def tasks = taskRepository.findAll()
            tasks.forEach({ log.info("task (${it.id}): $it.description") })
        }
    }

    @Override
    @Transactional
    void cleanupAll() {
        def projects = projectRepository.findAll()
        def tasks = taskRepository.findAll()
        tasks.each { Task t ->
            t.supertask.removeAll()
            t.project.removeAll()
            t.sprint.removeAll()
        }
        taskRepository.save(tasks)
        projects.each { Project p ->
            p.sprint.removeAll()
        }
        projectRepository.save(projects)
        projectRepository.deleteAll()
        taskRepository.deleteAll()
        sprintRepository.deleteAll()
    }
}

Aufgabe

Erweitern Sie die Klasse StartupService.groovy so, dass auch Testdaten für Ihre eigenen Daten angelegt werden.

2 Gedanken zu „Testdaten anlegen“

  1. Im gesammten Projekt existiert keine Methode initTestData. Was haben Sie eigentlich für eine Methode gemeint?
    Allgemein ist diese Generierung für die gesammte Applikation und nicht nur für das Testen oder?

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.