Skip to content

Instantly share code, notes, and snippets.

@Nico-kun123
Last active March 8, 2024 10:41
Show Gist options
  • Save Nico-kun123/4408d26a4aad782130f76c6b80e25391 to your computer and use it in GitHub Desktop.
Save Nico-kun123/4408d26a4aad782130f76c6b80e25391 to your computer and use it in GitHub Desktop.
Задание 1. Разработать функцию определения счета в игре | Задание 2. Разработать тесты для функции определения счета в игре
const TIMESTAMPS_COUNT = 50000;
const PROBABILITY_SCORE_CHANGED = 0.0001;
const PROBABILITY_HOME_SCORE = 0.45;
const OFFSET_MAX_STEP = 3;
type Score = {
home: number;
away: number;
};
type Stamp = {
offset: number;
score: Score;
};
const emptyScoreStamp: Stamp = {
offset: 0,
score: {
home: 0,
away: 0,
},
};
export const generateStamps = (): Stamp[] => {
const scoreStamps = Array(TIMESTAMPS_COUNT)
.fill(emptyScoreStamp)
.map(
((acc) => () => {
const scoreChanged = Math.random() > 1 - PROBABILITY_SCORE_CHANGED;
const homeScoreChange =
scoreChanged && Math.random() < PROBABILITY_HOME_SCORE ? 1 : 0;
const awayScoreChange = scoreChanged && !homeScoreChange ? 1 : 0;
return {
offset: (acc.offset +=
Math.floor(Math.random() * OFFSET_MAX_STEP) + 1),
score: {
home: (acc.score.home += homeScoreChange),
away: (acc.score.away += awayScoreChange),
},
};
})(emptyScoreStamp)
);
return scoreStamps;
};
export const getScore = (gameStamps: Stamp[], offset: number): Score => {
if (isNaN(offset)) {
throw new Error("Offset must be a number (got 'NaN' instead)!");
}
if (offset < 0) {
throw new Error("Offset value cannot be less than '0'!");
}
const stampsLength: number = gameStamps.length;
if (offset > gameStamps[stampsLength - 1].offset) {
throw new Error("Score with this offset doesn't exist!");
}
// Бинарный поиск
let left = 0;
let right = stampsLength - 1;
while (left <= right) {
const middle = Math.floor((left + right) / 2);
if (gameStamps[middle].offset === offset) {
return gameStamps[middle].score;
} else if (gameStamps[middle].offset < offset) {
left = middle + 1;
} else {
right = middle - 1;
}
}
if (right >= 0) {
return gameStamps[right].score;
}
// если offset равен 0
return { home: 0, away: 0 };
};
import { generateStamps, getScore } from "../game";
const gameScores = generateStamps();
describe("Проверка НЕКОРРЕКТНЫХ значений offset", () => {
test("Значение offset — 'NaN'", () => {
expect(() => {
getScore(gameScores, NaN);
}).toThrow();
});
test("Значение offset строго меньше нуля", () => {
expect(() => {
getScore(gameScores, -100);
}).toThrow();
});
test("Значение offset слишком большое", () => {
expect(() => {
getScore(gameScores, 999999);
}).toThrow();
});
});
import { getScore } from "../game";
const testStamps = [
{ offset: 3, score: { home: 0, away: 0 } },
{ offset: 5, score: { home: 0, away: 0 } },
{ offset: 8, score: { home: 0, away: 0 } },
{ offset: 7714, score: { home: 1, away: 0 } },
{ offset: 7717, score: { home: 1, away: 0 } },
{ offset: 7718, score: { home: 1, away: 0 } },
{ offset: 26975, score: { home: 1, away: 2 } },
{ offset: 26976, score: { home: 1, away: 2 } },
{ offset: 26979, score: { home: 1, away: 2 } },
{ offset: 99929, score: { home: 1, away: 1 } },
{ offset: 99930, score: { home: 1, away: 1 } },
{ offset: 99931, score: { home: 1, away: 1 } },
];
test("Проверка значений offset, которые точно есть в списке stamps", () => {
testStamps.forEach((obj) => {
expect(getScore(testStamps, obj.offset)).toBe(obj.score);
});
});
test("Проверка промежуточных значений offset", () => {
// Значения offset=6 нет. Он берёт предыдущее значение (offset=5)
expect(getScore(testStamps, 6)).toBe(testStamps[1].score);
expect(getScore(testStamps, 6)).not.toBe(testStamps[2].score);
});
test("Проверка значения offset=0", () => {
const defaultScore = { home: 0, away: 0 };
expect(getScore(testStamps, 0)).toStrictEqual(defaultScore);
});
test("Значение offset является числом с плавающей запятой", () => {
expect(getScore(testStamps, 6.0)).toBe(testStamps[1].score);
expect(getScore(testStamps, 6.9)).toBe(testStamps[1].score);
expect(getScore(testStamps, 6.000000000000000001)).toBe(testStamps[1].score);
});
import { generateStamps, getScore } from "../game";
test.concurrent("1000 случайных значений offset", async () => {
const TIMESTAMPS_COUNT = 50000;
const gameScores = generateStamps();
// Генерируем случайные offset
const randOffset: number[] = [];
for (let index = 0; index < 1000; index++) {
randOffset.push(Math.floor(Math.random() * TIMESTAMPS_COUNT));
}
// Проверка
randOffset.forEach((offset) => {
const score = getScore(gameScores, offset);
const stamp = gameScores.find((stamp) => {
stamp.offset === offset;
});
if (stamp !== undefined) {
expect(score).toEqual(stamp.score);
}
});
});
@Nico-kun123
Copy link
Author

Nico-kun123 commented Mar 8, 2024

Содержание файла package.json:

{
  "name": "game-score",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "ts-node game.ts",
    "test": "jest --config=jest.config.js --watchAll",
    "coverage": "jest --config=jest.config.js --coverage --watchAll"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "ts-node": "^10.9.2"
  },
  "devDependencies": {
    "@jest/globals": "^29.7.0",
    "@types/jest": "^29.5.12",
    "jest": "^29.7.0",
    "ts-jest": "^29.1.2",
    "typescript": "^5.4.2"
  }
}

Unit-тесты:

  • knownValue.unit.test.ts. Есть тестовый список, который будет проверяться. Если значения offset в списке нет (оно промежуточное), то будет браться score у предыдущего значения offset. Также проверяются сценарии, где offset=0 или offset -- это число с плавающей запятой.
  • incorrectValue.unit.test.ts. Здесь происходит проверка тех случаев, которые должны вернуть ошибку (входные значения: NaN, отрицательное число, слишком большое число).
  • randomValue.unit.test.ts. Генерируются случайные значения offset, а потом проверяется работа функции getScore.

Содержание файла jest.config.js:

//! Description: Jest configuration file.
//* Link: https://jestjs.io/docs/configuration

module.exports = {
  // Пресет для тестов
  preset: "ts-jest",

  // Среда для тестов
  testEnvironment: "node",
  // Паттерн для поиска тестов
  testMatch: ["**/__tests__/*.[jt]s?(x)"],

  // Трансформеры для тестов (преобразует файлы в js)
  transform: {
    "^.+\\.ts?$": "ts-jest",
  },

  // Папка для отчетов о покрытии
  coverageDirectory: "./__tests__/Test Coverage",
  // Папка для кэша
  cacheDirectory: "./__tests__/cache",

  // Количество потоков для тестов
  workerThreads: true,

  showSeed: true,
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment