문서 메뉴

문서 홈Atlas App Services

Atlas 기능 테스트

이 페이지의 내용

  • 시작하기 전에
  • 함수 단위 테스트
  • App Services 앱의 로컬 사본 받기
  • 새 함수 만들기
  • 함수 코드 작성
  • 단위 테스트에 사용하기 위한 내보내기 함수
  • 함수 코드 내보내기 단위 테스트
  • 모의 서비스
  • 함수 통합 테스트
  • 테스트 앱 만들기
  • 실제 환경에서 테스트

이 페이지에서는 Atlas 함수를 테스트하는 데 사용할 수 있는 몇 가지 전략에 대해 설명합니다.

함수 JavaScript 런타임과 표준 Node.js 런타임 간의 차이로 인해 함수를 테스트할 때 몇 가지 특수 사항을 고려해야 합니다. 이 페이지에서는 함수의 고유성을 처리하는 방법에 대해 설명합니다.

Atlas Function를 테스트하려면 다음이 필요합니다:

  • Atlas App Services 앱. 앱을 만드는 방법을 알아보려면 앱 만들기를 참조하세요.

  • 앱을 구성하는 코드 배포 메서드. 다음 중 하나를 선택합니다:

    • 로컬 시스템 PATH에 설치 및 추가된 App Services CLI의 사본입니다. 방법을 알아보려면 App Services CLI 설치를 참조하세요.

    • 앱의 구성 파일을 보관하고 배포하도록 구성된 GitHub 저장소입니다. 설정 방법을 알아보려면 GitHub를 통한 자동 배포를 참조하세요.

단위 테스트를 통해 함수의 기능을 검증할 수 있습니다. Node.js와 호환되는 테스트 프레임워크를 사용하여 함수를 테스트합니다. 이 페이지의 예제에서는 Jest 테스트 프레임워크를 사용합니다.

CommonJS 모듈 을 사용해야 합니다. 함수에 대한 단위 테스트를 작성합니다.

1
2

새 함수를 만듭니다. 앱 구성 파일에서 함수에 대한 functions 디렉토리에 새 JavaScript 파일을 만듭니다.

touch functions/hello.js

또한 함수에 대한 구성 정보를 functions/config.json에 추가해야 합니다.

{
"name": "hello",
"private": false,
"run_as_system": true
},

다음도 참조하세요.

새 함수 생성에 대한 자세한 내용은 함수 정의를 참조하세요.

3

함수 코드를 쉽게 테스트하려면 관련 문제를 별개의 구성 요소로 분리하여 모듈식으로 유지하세요. 이전 단계에서 정의한 파일의 함수에 대한 모든 로직을 유지해야 합니다. 함수 파일에서는 프로젝트의 다른 파일에서 상대 경로로 가져오기를 수행할 수 없습니다. npm을 사용하여 종속성을 가져올 수도 있습니다.

함수를 exports에 할당하여 내보내야 합니다.

hello.js
function greet(word) {
return "hello " + word;
}
function greetWithPunctuation(word, punctuation) {
return greet(word) + punctuation;
}
// Function exported to App Services
exports = greetWithPunctuation;
4

별도의 Node.js 단위 테스트 파일에서 사용할 코드를 내보내려면 CommonJS module.exports 구문을 사용해야 합니다.

이 구문은 함수 런타임과 호환되지 않습니다. Atlas 함수 환경은 Node.js 글로벌 module을 제공하지 않습니다. 파일을 함수와 호환되도록 유지하면서 단위 테스트로 모듈을 내보내려면 module.exports 진술(statement)을 글로벌 module 객체가 존재하는지 확인하는 검사로 래핑합니다.

functions/hello.js
function greet(word) {
return "hello " + word;
}
function greetWithPunctuation(word, punctuation) {
return greet(word) + punctuation;
}
// Function exported to App Services
exports = greetWithPunctuation;
// export locally for use in unit test
if (typeof module !== "undefined") {
module.exports = { greet, greetWithPunctuation };
}
5

이제 함수 파일에서 내보낸 모듈에 대한 단위 테스트를 작성할 수 있습니다. 프로젝트 내 별도 test 디렉토리에 함수 파일에 대한 테스트 파일을 만듭니다.

mkdir -p test/unit
touch test/unit/hello.test.js

이전 단계에서 내보낸 모듈을 가져오고 단위 테스트를 추가합니다.

test/unit/hello.test.js
const { greet, greetWithPunctuation } = require("../../functions/hello");
test("should greet", () => {
const helloWorld = greet("world");
expect(helloWorld).toBe("hello world");
});
test("should greet with punctuation", () => {
const excitedHelloWorld = greetWithPunctuation("world", "!!!");
expect(excitedHelloWorld).toBe("hello world!!!");
});

글로벌 컨텍스트 객체 또는 함수가 노출하는 다른 글로벌 모듈 중 하나를 사용하는 함수에 대한 단위 테스트를 작성하려면 해당 동작의 모의 항목(mock)을 만들어야 합니다.

이 예제에서 함수는 context.values.get() 를 통해 App Services 값을 참조하고 글로벌 모듈 BSON을 사용하여 ObjectId를 생성합니다.

accessAppServicesGlobals.js
function accessAppServicesGlobals() {
const mongodb = context.services.get("mongodb-atlas");
const objectId = BSON.ObjectId()
// ... do stuff with these values
}
exports = accessAppServicesGlobals;
if (typeof module !== "undefined") {
module.exports = accessAppServicesGlobals;
}

이러한 모의 항목을 Node.js 글로벌 네임스페이스에 연결합니다. 이를 통해 함수 런타임에서와 동일한 방식으로 단위 테스트에서 모의 항목을 호출할 수 있습니다.

global.context = {
// whichever global context methods you want to mock.
// 'services', 'functions', values, etc.
}
// you can also mock other Functions global modules
global.BSON = {
// mock methods
}

또한 설정 블록과 해체(teardown) 블록에서 이러한 모의 항목을 선언하고 제거하여 글로벌 네임스페이스를 오염시키지 않도록 할 수도 있습니다.

// adds context mock to global namespace before each test
beforeEach(() => {
global.context = {
// your mocking services
};
});
// removes context from global namespace after each test
afterEach(() => {
delete global.context;
});
test("should perform operation using App Services globals", () => {
// test function that uses context
});

예제

컨텍스트에 액세스하는 함수 모방

이 예제의 함수는 App Services 값에 액세스하여 이를 반환합니다.

helloWithValue.js
function greet() {
const greeting = context.values.get("greeting"); // the greeting is 'beautiful world'
return "hello " + greeting;
}
exports = greet;
if (typeof module !== "undefined") {
module.exports = greet;
}

이제 테스트 파일 helloWithValue.test.js를 만듭니다. 테스트 파일에는 다음이 포함됩니다:

  • helloWithValue.js에서 내보낸 함수를 가져옵니다.

  • context.values.get()의 모의 항목. 글로벌 네임스페이스를 오염시키지 않도록 모의 항목을 설정 및 해체 블록으로 래핑합니다.

  • 모의 항목을 사용하는 가져온 함수에 대한 테스트입니다.

helloWithValue.test.js
// import the function
const greet = require("../../functions/helloWithValue");
// wrap the mock in beforeEach/afterEach blocks to avoid
// pollution of the global namespace
beforeEach(() => {
// mock of context.values.get()
global.context = {
values: {
get: (val) => {
const valsMap = {
greeting: "magnificent morning",
};
return valsMap[val];
},
},
};
});
afterEach(() => {
// delete the mock to not pollute global namespace
delete global.context;
});
// test function using mock
test("should greet with value", () => {
const greeting = greet();
expect(greeting).toBe("hello magnificent morning");
});

프로덕션 환경에 배포하기 전에 모든 함수에 대해 통합 테스트를 수행해야 합니다. 이는 Atlas Function JavaScript 런타임이 표준 Node.js 런타임과 다르기 때문에 특히 중요합니다. App Services에 배포된 함수를 테스트하지 않으면 예기치 않은 오류가 발생할 수 있습니다.

Functions에 대한 통합 테스트를 작성하는 데에는 여러 방법이 있습니다. Functions는 다양한 컨텍스트에서 다양한 목적으로 사용될 수 있으므로 각 사용 사례에는 서로 다른 통합 테스트 전략이 필요합니다.

예를 들어, 장치 SDK 클라이언트에서 호출하는 함수에 대한 통합 테스트를 생성하는 방식은 데이터베이스 트리거 함수를 테스트하는 방식과 다릅니다.

그러나 몇 가지 일반적인 단계를 통해 Functions에 대한 통합 테스트를 작성할 수 있습니다. 이러한 단계는 크게 다음과 같습니다:

  1. 프로덕션 앱과 동일한 구성으로 테스트 앱을 만듭니다.

  2. 라이브 테스트 환경에 배포된 Functions와 상호 작용하는 통합 테스트를 작성합니다.

이 섹션의 나머지 부분에서는 앱에 대한 통합 테스트를 구현하는 방법을 더 자세히 설명합니다.

다음도 참조하세요.

Functions JavaScript 런타임의 특이 사항에 대한 자세한 내용은 다음을 참조하세요.

Functions의 다양한 사용 사례에 대한 자세한 내용은 Functions 사용 시나리오를 참조하세요.

1

프로덕션 앱과 동일한 구성을 가진 테스트용 앱을 만듭니다. 이때 데이터 소스 및 백엔드 구성은 프로덕션 앱과 다릅니다.

동일한 구성으로 여러 앱을 만드는 방법에 대한 자세한 내용은 앱 환경 구성을 참조하세요.

2

테스트 앱을 배포한 후에는 선호하는 테스트 언어 및 프레임워크를 사용하여 기능을 테스트합니다.

Realm 클라이언트 SDK 는 앱을 테스트하는 데 유용합니다. 이러한 SDK는 App Services에 대한 최고 수준의 액세스를 제공합니다. 테스트 제품군에서 Realm SDK를 사용하여 테스트 앱에 연결할 수 있습니다. Realm SDK를 사용하여 앱과의 상호 작용을 테스트합니다.

예제

데이터베이스 트리거 함수 테스트

이 예제는 Realm Node.js SDK와 Jest 테스팅 프레임워크를 사용하여 데이터베이스 트리거를 테스트합니다.

트리거 함수는 구체화된 뷰 를 생성합니다. 새로운 판매가 발생할 때마다 제품의 총 매출

트리거는 sales 표에 항목이 추가될 때마다 실행됩니다. total_sales_materialized 테이블의 total_sales 필드를 하나씩 증가시킵니다.

데이터베이스 트리거의 구성은 다음과 같습니다:

triggers/materializeTotalSales.json
{
"id": "62bb0d9f852c6e062432c454",
"name": "materializeTotalSales",
"type": "DATABASE",
"config": {
"operation_types": ["INSERT"],
"database": "store",
"collection": "sales",
"service_name": "mongodb-atlas",
"match": {},
"project": {},
"full_document": true,
"full_document_before_change": false,
"unordered": false,
"skip_catchup_events": false
},
"disabled": false,
"event_processors": {
"FUNCTION": {
"config": {
"function_name": "materializeTotalSales"
}
}
}
}

트리거는 다음 Function을 호출합니다:

functions/materializeTotalSales.js
exports = function (changeEvent) {
const {
fullDocument: { productId },
} = changeEvent;
const totalSalesMaterialization = context.services
.get("mongodb-atlas")
.db("store")
.collection("total_sales_materialized");
totalSalesMaterialization.updateOne(
{ _id: productId },
{ $inc: { total_sales: 1 } },
{ upsert: true }
);
};

이 예제에서는 MongoDB Atlas와 상호 작용하기 위해 Node.js Realm SDK 를 사용하여 트리거를 테스트합니다. 또한, MongoDB 쿼리 API 또는 MongoDB 드라이버 중 하나와 함께 모든 Realm SDK 사용하여 데이터베이스 트리거를 테스트하기 위해 MongoDB Atlas를 쿼리할 수 있습니다.

test/integration/materializeTotalSales.test.js
const { app_id } = require("../../realm_config.json");
const Realm = require("realm");
const { BSON } = require("realm");
let user;
const app = new Realm.App(app_id);
const sandwichId = BSON.ObjectId();
const saladId = BSON.ObjectId();
// utility function
async function sleep(ms) {
await new Promise((resolve) => setTimeout(resolve, ms));
}
// Set up. Creates and logs in a user, which you need to query MongoDB Atlas
// with the Realm Node.js SDK
beforeEach(async () => {
const credentials = Realm.Credentials.anonymous();
user = await app.logIn(credentials);
});
// Clean up. Removes user and data created in the test.
afterEach(async () => {
const db = user.mongoClient("mongodb-atlas").db("store");
await db.collection("sales").deleteMany({});
await db.collection("total_sales_materialized").deleteMany({});
await app.deleteUser(user);
});
test("Trigger creates a new materialization", async () => {
const sales = user
.mongoClient("mongodb-atlas")
.db("store")
.collection("sales");
await sales.insertOne({
_id: BSON.ObjectId(),
productId: sandwichId,
price: 12.0,
timestamp: Date.now(),
});
// give time for the Trigger to execute on Atlas
await sleep(1000);
const totalSalesMaterialized = user
.mongoClient("mongodb-atlas")
.db("store")
.collection("total_sales_materialized");
const allSandwichSales = await totalSalesMaterialized.findOne({
_id: sandwichId,
});
// checks that Trigger increments creates and increments total_sales
expect(allSandwichSales.total_sales).toBe(1);
});
test("Trigger updates an existing materialization", async () => {
const sales = user
.mongoClient("mongodb-atlas")
.db("store")
.collection("sales");
await sales.insertOne({
_id: BSON.ObjectId(),
productId: saladId,
price: 15.0,
timestamp: Date.now(),
});
await sales.insertOne({
_id: BSON.ObjectId(),
productId: saladId,
price: 15.0,
timestamp: Date.now(),
});
// give time for Trigger to execute on Atlas
await sleep(1000);
const totalSalesMaterialized = user
.mongoClient("mongodb-atlas")
.db("store")
.collection("total_sales_materialized");
const allSaladSales = await totalSalesMaterialized.findOne({
_id: saladId,
});
// checks that Trigger increments total_sales for each sale
expect(allSaladSales.total_sales).toBe(2);
});
← 외부 종속성