온톨로지 편집 단위 테스트
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 })는 명시적으로 검사하지 않은 속성 수정을 허용합니다. 전체 변경을 엄격히 확인하려면 옵션 없이 호출합니다.