Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

온톨로지 편집 단위 테스트

TSv1의 verifyOntologyEditFunction은 함수 실행 중 수집한 편집을 검사합니다. @foundry/functions-testing-lib에서 가져옵니다. 준비한 객체 상태와 검증 대상 함수가 만든 변경을 구분할 수 있습니다.

단일과 복수 검증

편집한 개여러 개
객체 생성createsObject({ objectType, properties })createsObjects([{ objectType, properties }])
속성 수정modifiesObject({ object, properties })modifiesObjects([{ object, properties }])
객체 삭제deletesObject(object)deletesObjects([object])
링크 추가addsLink({ link, linkedObject })addsLinks([{ link, linkedObject }])
링크 제거removesLink({ link, unlinkedObject })removesLinks([{ link, unlinkedObject }])

단일 메서드에는 객체 하나, 복수 메서드에는 배열을 전달합니다. 서로 바꾸면 타입 오류가 발생합니다.

네 링크 메서드는 현재까지 수집한 edits를 받는 콜백도 허용합니다. 함수가 새로 만든 객체를 연결할 때 유용합니다. 단일 콜백은 링크 하나를, 복수 콜백은 링크 배열을 반환합니다.

hasNoMoreEdits는 지정한 것 외에 다른 편집이 없는지 검사합니다. 반환값이 없으므로 체인의 마지막에 사용하며 복수 형태는 없습니다.

예제 스키마와 함수

다음은 테스트용 가상 온톨로지입니다.

  • ExampleDataAirport는 String 기본 키 airport와 String displayAirportName을 가집니다.
  • ExampleDataFlight는 String 기본 키 flightId와 외래 키 tailNumber를 가집니다. aircraft는 ExampleDataAircraft와의 단일 관계입니다.
  • ExampleDataAircraft의 String 기본 키는 tailNumber입니다. Flight.tailNumber가 관계의 외래 키를 보유합니다.
  • ExampleFlightDelayEvent의 String 기본 키는 eventId입니다. Flight.flightDelayEvent와 DelayEvent.flight는 서로 반대 방향의 다대다 관계입니다.

src/airportEditFunctions.ts에 정의하고 index.ts에서 AirportEditFunctions를 내보냅니다. createAndLinkDelays의 numDelay는 0 이상의 개수이고 idPrefix는 해당 호출에서만 사용하는 새 기본 키 접두사입니다.

import { Edits, Integer, OntologyEditFunction } from "@foundry/functions-api";
import {
    Objects, ExampleDataAirport, ExampleDataAircraft,
    ExampleDataFlight, ExampleFlightDelayEvent,
} from "@foundry/ontology-api";

export class AirportEditFunctions {
    @Edits(ExampleDataAirport)
    @OntologyEditFunction()
    public createAirport(airport: string, displayName: string): void {
        Objects.create().exampleDataAirport(airport).displayAirportName = displayName;
    }

    @Edits(ExampleDataFlight, ExampleFlightDelayEvent)
    @OntologyEditFunction()
    public createAndLinkDelays(flight: ExampleDataFlight, numDelay: Integer, idPrefix: string): void {
        for (let index = 0; index < numDelay; index++) {
            const delay = Objects.create().exampleFlightDelayEvent(`${idPrefix}-${index}`);
            flight.flightDelayEvent.add(delay);
        }
    }

    @Edits(ExampleDataFlight)
    @OntologyEditFunction()
    public assignAircraftToFlight(flight: ExampleDataFlight, aircraft: ExampleDataAircraft): void {
        flight.aircraft.set(aircraft);
    }

    @Edits(ExampleDataFlight, ExampleFlightDelayEvent)
    @OntologyEditFunction()
    public linkDelays(flight: ExampleDataFlight, delay: ExampleFlightDelayEvent): void {
        flight.flightDelayEvent.add(delay);
    }

    @Edits(ExampleDataFlight, ExampleFlightDelayEvent)
    @OntologyEditFunction()
    public removeAllDelays(flight: ExampleDataFlight): void {
        flight.flightDelayEvent.clear();
    }

    @Edits(ExampleDataFlight, ExampleFlightDelayEvent)
    @OntologyEditFunction()
    public async removeAllDelaysAsync(flight: ExampleDataFlight): Promise<void> {
        for (const delay of await flight.flightDelayEvent.allAsync()) {
            flight.flightDelayEvent.remove(delay);
        }
    }

    @Edits(ExampleDataFlight)
    @OntologyEditFunction()
    public deleteFlight(flight: ExampleDataFlight): void {
        flight.delete();
    }

    @Edits(ExampleDataAirport, ExampleDataFlight, ExampleFlightDelayEvent)
    @OntologyEditFunction()
    public multistageEdits(
        flight: ExampleDataFlight,
        airport: ExampleDataAirport,
        oldDelay: ExampleFlightDelayEvent,
        obsoleteDelay: ExampleFlightDelayEvent,
        newDelayId: string,
        airportName: string
    ): void {
        const newDelay = Objects.create().exampleFlightDelayEvent(newDelayId);
        airport.displayAirportName = airportName;
        flight.flightDelayEvent.add(newDelay);
        flight.flightDelayEvent.remove(oldDelay);
        obsoleteDelay.delete();
    }
}

생성과 속성 수정

src/tests/airportEditFunctions.ts에 다음 import를 두고 아래 테스트들을 같은 파일에 추가합니다.

import { AirportEditFunctions } from "../airportEditFunctions";
import { Objects, ExampleDataAirport, ExampleFlightDelayEvent } from "@foundry/ontology-api";
import { verifyOntologyEditFunction } from "@foundry/functions-testing-lib";

const functions = new AirportEditFunctions();

test("creates an airport", () => {
    verifyOntologyEditFunction(() => functions.createAirport("airportCode", "airportDisplayName"))
        .createsObject({
            objectType: ExampleDataAirport,
            properties: { airport: "airportCode", displayAirportName: "airportDisplayName" },
        })
        .hasNoMoreEdits();
});

test("changes the aircraft foreign key", () => {
    const flight = Objects.create().exampleDataFlight("NY -> LA");
    const oldAircraft = Objects.create().exampleDataAircraft("N11111");
    flight.aircraft.set(oldAircraft);
    const newAircraft = Objects.create().exampleDataAircraft("A00000");
    verifyOntologyEditFunction(() => functions.assignAircraftToFlight(flight, newAircraft))
        .modifiesObject({ object: flight, properties: { tailNumber: "A00000" } })
        .hasNoMoreEdits();
});

기존 링크를 준비한 부분은 검증 콜백 밖에 있습니다. aircraft의 외래 키를 Flight가 가지므로 연결을 바꾸는 편집은 Flight 속성 수정으로 확인합니다.

새 객체를 연결한 편집

createsObject로 새 지연 이벤트를 검사한 뒤 콜백이 받은 createdObjects에서 해당 객체를 얻습니다.

test("creates and links one delay", () => {
    const flight = Objects.create().exampleDataFlight("flightTest");
    verifyOntologyEditFunction(() => functions.createAndLinkDelays(flight, 1, "new-flight-delay"))
        .createsObject({
            objectType: ExampleFlightDelayEvent,
            properties: { eventId: "new-flight-delay-0" },
        })
        .addsLink(edits => {
            const delay = edits.createdObjects.byObjectType(ExampleFlightDelayEvent)[0];
            if (delay === undefined) throw new Error("생성된 지연 이벤트가 없습니다.");
            return { link: flight.flightDelayEvent, linkedObject: delay };
        })
        .hasNoMoreEdits();
});

여러 객체는 createsObjects와 addsLinks를 사용합니다. 첫 검증이 확인한 세 개의 이벤트에 링크가 생겼는지 검사합니다.

test("creates and links three delays", () => {
    const flight = Objects.create().exampleDataFlight("three-delay-flight");
    verifyOntologyEditFunction(() => functions.createAndLinkDelays(flight, 3, "new-flight-delay"))
        .createsObjects([0, 1, 2].map(index => ({
            objectType: ExampleFlightDelayEvent,
            properties: { eventId: `new-flight-delay-${index}` },
        })))
        .addsLinks(edits => edits.createdObjects.byObjectType(ExampleFlightDelayEvent).map(delay => ({
            link: flight.flightDelayEvent, linkedObject: delay,
        })))
        .hasNoMoreEdits();
});

링크 추가와 반대 방향

같은 관계를 Flight에서 DelayEvent로 검사하거나 반대 방향에서 검사할 수 있습니다.

test("adds a delay link", () => {
    const flight = Objects.create().exampleDataFlight("forward-flight");
    const delay = Objects.create().exampleFlightDelayEvent("forward-delay");
    verifyOntologyEditFunction(() => functions.linkDelays(flight, delay))
        .addsLink({ link: flight.flightDelayEvent, linkedObject: delay })
        .hasNoMoreEdits();
});

test("observes the same link in reverse", () => {
    const flight = Objects.create().exampleDataFlight("reverse-flight");
    const delay = Objects.create().exampleFlightDelayEvent("reverse-delay");
    verifyOntologyEditFunction(() => functions.linkDelays(flight, delay))
        .addsLink({ link: delay.flight, linkedObject: flight })
        .hasNoMoreEdits();
});

연결 제거와 객체 삭제

removesLink에는 unlinkedObject를 사용합니다. 링크 제거는 객체 삭제와 다릅니다.

test("removes the existing delay link", () => {
    const flight = Objects.create().exampleDataFlight("unlink-flight");
    const delay = Objects.create().exampleFlightDelayEvent("unlink-delay");
    flight.flightDelayEvent.add(delay);
    verifyOntologyEditFunction(() => functions.removeAllDelays(flight))
        .removesLink({ link: flight.flightDelayEvent, unlinkedObject: delay })
        .hasNoMoreEdits();
});

test("deletes a flight", () => {
    const flight = Objects.create().exampleDataFlight("delete-flight");
    verifyOntologyEditFunction(() => functions.deleteFlight(flight))
        .deletesObject(flight)
        .hasNoMoreEdits();
});

비동기 편집

함수가 Promise를 반환하면 verifyOntologyEditFunction도 await한 뒤 검증 메서드를 연결합니다.

test("awaits asynchronous link removal", async () => {
    const flight = Objects.create().exampleDataFlight("async-flight");
    const delay = Objects.create().exampleFlightDelayEvent("async-delay");
    flight.flightDelayEvent.add(delay);
    (await verifyOntologyEditFunction(() => functions.removeAllDelaysAsync(flight)))
        .removesLink({ link: flight.flightDelayEvent, unlinkedObject: delay })
        .hasNoMoreEdits();
});

여러 종류의 편집을 함께 검증

다음 테스트는 새 이벤트 생성, 공항 이름 변경, 새 연결, 이전 연결 제거와 별도 미사용 이벤트 삭제를 한 번의 실행에서 검사합니다. oldDelay는 연결만 제거하며 obsoleteDelay는 별도로 삭제하는 객체입니다.

test("verifies all stages of one edit function", () => {
    const flight = Objects.create().exampleDataFlight("staged-flight");
    const airport = Objects.create().exampleDataAirport("staged-airport");
    airport.displayAirportName = "Old airport name";
    const oldDelay = Objects.create().exampleFlightDelayEvent("old-delay");
    const obsoleteDelay = Objects.create().exampleFlightDelayEvent("obsolete-delay");
    flight.flightDelayEvent.add(oldDelay);

    verifyOntologyEditFunction(() => functions.multistageEdits(
        flight, airport, oldDelay, obsoleteDelay, "replacement-delay", "New airport name"
    ))
        .createsObject({
            objectType: ExampleFlightDelayEvent,
            properties: { eventId: "replacement-delay" },
        })
        .modifiesObjects([{ object: airport, properties: { displayAirportName: "New airport name" } }])
        .addsLinks(edits => edits.createdObjects.byObjectType(ExampleFlightDelayEvent).map(delay => ({
            link: flight.flightDelayEvent, linkedObject: delay,
        })))
        .removesLinks([{ link: flight.flightDelayEvent, unlinkedObject: oldDelay }])
        .deletesObject(obsoleteDelay)
        .hasNoMoreEdits();
});

허용할 추가 편집

hasNoMoreEdits에 다음 선택 값을 true로 주면 해당 종류의 추가 편집을 검사 대상에서 제외할 수 있습니다.

옵션무시할 추가 편집
ignoreExtraCreatedObjects객체 생성
ignoreExtraModifiedObjects속성 수정
ignoreExtraDeletedObjects객체 삭제
ignoreExtraLinkedObjects링크 추가
ignoreExtraUnlinkedObjects링크 제거

예를 들어 .hasNoMoreEdits({ ignoreExtraModifiedObjects: true })는 명시적으로 검사하지 않은 속성 수정을 허용합니다. 전체 변경을 엄격히 확인하려면 옵션 없이 호출합니다.