From 0464cf425ef6d72f16098444b8543b636f5653c7 Mon Sep 17 00:00:00 2001 From: AxelHowe Date: Wed, 5 Mar 2025 21:38:16 +0800 Subject: [PATCH 1/4] feat: lab1 --- lab1/README.md | 22 +++++++++++++++++++ lab1/main.js | 55 +++++++++++++++++++++++++++++++++++++++++++++++ lab1/main_test.js | 23 ++++++++++++++++++++ lab1/validate.sh | 38 ++++++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+) create mode 100644 lab1/README.md create mode 100644 lab1/main.js create mode 100644 lab1/main_test.js create mode 100755 lab1/validate.sh diff --git a/lab1/README.md b/lab1/README.md new file mode 100644 index 0000000..df0ab9f --- /dev/null +++ b/lab1/README.md @@ -0,0 +1,22 @@ +# Lab1 + +## Introduction + +In this lab, you will write unit tests for functions implemented in `main.js`. You can learn how to use classes and functions in it by uncommenting the code in it. (But remember don't commit them on GitHub) + +## Requirement + +1. Write test cases in `main_test.js` and achieve 100% code coverage. (100%) + +You can run `validate.sh` in your local to test if you satisfy the requirements. + +Please note that you must not alter files other than `main_test.js`. You will get 0 points if + +1. you modify other files to achieve requirements. +2. you can't pass all CI on your PR. + +## Submission + +You need to open a pull request to your branch (e.g. 311XXXXXX, your student number) and contain the code that satisfies the abovementioned requirements. + +Moreover, please submit the URL of your PR to E3. Your submission will only be accepted when you present at both places. diff --git a/lab1/main.js b/lab1/main.js new file mode 100644 index 0000000..c9aed9f --- /dev/null +++ b/lab1/main.js @@ -0,0 +1,55 @@ +// NOTICE: DO NOT MODIFY THE CODE IN THIS FILE +// But you can uncomment code below and run this file to understand how to use the classes + +class MyClass { + constructor() { + this.students = []; + } + + addStudent(student) { + if (!(student instanceof Student)) { + return -1; + } + this.students.push(student); + return this.students.length - 1; + } + + getStudentById(id) { + if (id < 0 || id >= this.students.length) { + return null; + } + return this.students[id]; + } +} + +class Student { + constructor() { + this.name = undefined; + } + + setName(userName) { + if (typeof userName !== 'string') { + return; + } + this.name = userName; + } + + getName() { + if (this.name === undefined) { + return ''; + } + return this.name; + } +} + +// const myClass = new MyClass(); +// const names = ['John', 'Jane', 'Doe', 'Smith']; +// names.forEach(name => { +// const student = new Student(); +// student.setName(name); +// const newStudentId = myClass.addStudent(student); +// const newStudentName = myClass.getStudentById(newStudentId).getName(); +// console.log('[+] Added student with id: %d, name: %s', newStudentId, newStudentName); +// }); + +module.exports = { MyClass, Student }; \ No newline at end of file diff --git a/lab1/main_test.js b/lab1/main_test.js new file mode 100644 index 0000000..74a716b --- /dev/null +++ b/lab1/main_test.js @@ -0,0 +1,23 @@ +const test = require('node:test'); +const assert = require('assert'); +const { MyClass, Student } = require('./main'); + +test("Test MyClass's addStudent", () => { + // TODO + throw new Error("Test not implemented"); +}); + +test("Test MyClass's getStudentById", () => { + // TODO + throw new Error("Test not implemented"); +}); + +test("Test Student's setName", () => { + // TODO + throw new Error("Test not implemented"); +}); + +test("Test Student's getName", () => { + // TODO + throw new Error("Test not implemented"); +}); \ No newline at end of file diff --git a/lab1/validate.sh b/lab1/validate.sh new file mode 100755 index 0000000..8f2fcd4 --- /dev/null +++ b/lab1/validate.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Check for unwanted files +for file in *; do + if [[ $file != "main.js" && $file != "main_test.js" && $file != "README.md" && $file != "validate.sh" ]]; then + echo "[!] Unwanted file detected: $file." + exit 1 + fi +done + +node=$(which node) +test_path="${BASH_SOURCE[0]}" +solution_path="$(realpath .)" +tmp_dir=$(mktemp -d -t lab1-XXXXXXXXXX) + +cd $tmp_dir + +rm -rf * +cp $solution_path/*.js . +result=$($"node" --test --experimental-test-coverage) ; ret=$? +if [ $ret -ne 0 ] ; then + echo "[!] testing fails." + exit 1 +else + coverage=$(echo "$result" | grep 'all files' | awk -F '|' '{print $2}' | sed 's/ //g') + if (( $(echo "$coverage < 100" | bc -l) )); then + echo "[!] Coverage is only $coverage%, should be 100%." + exit 1 + else + echo "[V] Coverage is 100%, great job!" + fi +fi + +rm -rf $tmp_dir + +exit 0 + +# vim: set fenc=utf8 ff=unix et sw=2 ts=2 sts=2: \ No newline at end of file From b630cfa70f0c7abf11fb3907652a007a6292f48b Mon Sep 17 00:00:00 2001 From: AxelHowe Date: Wed, 12 Mar 2025 20:50:08 +0800 Subject: [PATCH 2/4] feat: lab2 --- lab2/README.md | 22 +++++++++++++ lab2/main.js | 81 +++++++++++++++++++++++++++++++++++++++++++++++ lab2/main_test.js | 6 ++++ lab2/validate.sh | 38 ++++++++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 lab2/README.md create mode 100644 lab2/main.js create mode 100644 lab2/main_test.js create mode 100755 lab2/validate.sh diff --git a/lab2/README.md b/lab2/README.md new file mode 100644 index 0000000..60a9c80 --- /dev/null +++ b/lab2/README.md @@ -0,0 +1,22 @@ +# Lab2 + +## Introduction + +In this lab, you will write unit tests for functions implemented in `main.js`. You can learn how to use classes and functions in it by uncommenting the code in it. (But remember don't commit them on GitHub) + +## Requirement + +1. Write test cases in `main_test.js` and achieve 100% code coverage. Remember to use Mock, Spy, or Stub when necessary, you need to at least use one of them in your test cases. (100%) + +You can run `validate.sh` in your local to test if you satisfy the requirements. + +Please note that you must not alter files other than `main_test.js`. You will get 0 points if + +1. you modify other files to achieve requirements. +2. you can't pass all CI on your PR. + +## Submission + +You need to open a pull request to your branch (e.g. 311XXXXXX, your student number) and contain the code that satisfies the abovementioned requirements. + +Moreover, please submit the URL of your PR to E3. Your submission will only be accepted when you present at both places. diff --git a/lab2/main.js b/lab2/main.js new file mode 100644 index 0000000..2e159e7 --- /dev/null +++ b/lab2/main.js @@ -0,0 +1,81 @@ +const fs = require('fs'); +const util = require('util'); +const readFile = util.promisify(fs.readFile); + +class MailSystem { + write(name) { + console.log('--write mail for ' + name + '--'); + const context = 'Congrats, ' + name + '!'; + return context; + } + + send(name, context) { + console.log('--send mail to ' + name + '--'); + // Interact with mail system and send mail + // random success or failure + const success = Math.random() > 0.5; + if (success) { + console.log('mail sent'); + } else { + console.log('mail failed'); + } + return success; + } +} + +class Application { + constructor() { + this.people = []; + this.selected = []; + this.mailSystem = new MailSystem(); + this.getNames().then(([people, selected]) => { + this.people = people; + this.selected = selected; + }); + } + + async getNames() { + const data = await readFile('name_list.txt', 'utf8'); + const people = data.split('\n'); + const selected = []; + return [people, selected]; + } + + getRandomPerson() { + const i = Math.floor(Math.random() * this.people.length); + return this.people[i]; + } + + selectNextPerson() { + console.log('--select next person--'); + if (this.people.length === this.selected.length) { + console.log('all selected'); + return null; + } + let person = this.getRandomPerson(); + while (this.selected.includes(person)) { + person = this.getRandomPerson(); + } + this.selected.push(person); + return person; + } + + notifySelected() { + console.log('--notify selected--'); + for (const x of this.selected) { + const context = this.mailSystem.write(x); + this.mailSystem.send(x, context); + } + } +} + +// const app = new Application(); +// app.selectNextPerson(); +// app.selectNextPerson(); +// app.selectNextPerson(); +// app.notifySelected(); + +module.exports = { + Application, + MailSystem, +}; \ No newline at end of file diff --git a/lab2/main_test.js b/lab2/main_test.js new file mode 100644 index 0000000..5034468 --- /dev/null +++ b/lab2/main_test.js @@ -0,0 +1,6 @@ +const test = require('node:test'); +const assert = require('assert'); +const { Application, MailSystem } = require('./main'); + +// TODO: write your tests here +// Remember to use Stub, Mock, and Spy when necessary \ No newline at end of file diff --git a/lab2/validate.sh b/lab2/validate.sh new file mode 100755 index 0000000..13b53ed --- /dev/null +++ b/lab2/validate.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Check for unwanted files +for file in *; do + if [[ $file != "main.js" && $file != "main_test.js" && $file != "README.md" && $file != "validate.sh" ]]; then + echo "[!] Unwanted file detected: $file." + exit 1 + fi +done + +node=$(which node) +test_path="${BASH_SOURCE[0]}" +solution_path="$(realpath .)" +tmp_dir=$(mktemp -d -t lab2-XXXXXXXXXX) + +cd $tmp_dir + +rm -rf * +cp $solution_path/*.js . +result=$($"node" --test --experimental-test-coverage) ; ret=$? +if [ $ret -ne 0 ] ; then + echo "[!] testing fails" + exit 1 +else + coverage=$(echo "$result" | grep 'all files' | awk -F '|' '{print $2}' | sed 's/ //g') + if (( $(echo "$coverage < 100" | bc -l) )); then + echo "[!] Coverage is only $coverage%" + exit 1 + else + echo "[V] Coverage is 100%" + fi +fi + +rm -rf $tmp_dir + +exit 0 + +# vim: set fenc=utf8 ff=unix et sw=2 ts=2 sts=2: \ No newline at end of file From b62db07362a44f8830ab8504403924824aedaf56 Mon Sep 17 00:00:00 2001 From: Bruce Date: Thu, 13 Mar 2025 01:12:19 +0800 Subject: [PATCH 3/4] Modify main_test.js --- lab1/main_test.js | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/lab1/main_test.js b/lab1/main_test.js index 74a716b..a6d33a6 100644 --- a/lab1/main_test.js +++ b/lab1/main_test.js @@ -3,21 +3,47 @@ const assert = require('assert'); const { MyClass, Student } = require('./main'); test("Test MyClass's addStudent", () => { - // TODO - throw new Error("Test not implemented"); + const Class = new MyClass(); + const student = new Student(); + const id = Class.addStudent(student); + assert.strictEqual(id, 0); + assert.strictEqual(Class.addStudent({}), -1); }); test("Test MyClass's getStudentById", () => { - // TODO - throw new Error("Test not implemented"); + const Class = new MyClass(); + const student = new Student(); + student.setName("John"); + + const id = Class.addStudent(student); + const StudentID = Class.getStudentById(id); + + assert.strictEqual(StudentID.getName(), "John"); + assert.strictEqual(Class.getStudentById(-1), null); + assert.strictEqual(Class.getStudentById(999), null); + }); test("Test Student's setName", () => { - // TODO - throw new Error("Test not implemented"); + const student = new Student(); + student.setName(123); + assert.strictEqual(student.getName(), ""); + + student.setName("John"); + const StudentName = student.getName(); + + + assert.strictEqual(StudentName, "John"); + }); test("Test Student's getName", () => { - // TODO - throw new Error("Test not implemented"); + const student = new Student(); + + // "" in default + assert.strictEqual(student.getName(), ""); + + student.setName("John"); + const StudentName = student.getName(); + assert.strictEqual(StudentName, "John"); }); \ No newline at end of file From acbf1a7050b7649936df8ac7a1639277cf128403 Mon Sep 17 00:00:00 2001 From: Bruce Date: Tue, 18 Mar 2025 15:48:00 +0800 Subject: [PATCH 4/4] 0318 --- lab2/main_test.js | 92 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/lab2/main_test.js b/lab2/main_test.js index 5034468..d1a2859 100644 --- a/lab2/main_test.js +++ b/lab2/main_test.js @@ -1,6 +1,94 @@ const test = require('node:test'); const assert = require('assert'); +const fs = require('fs'); + +// 模擬 fs.readFile +function mockReadFile(path, encoding, callback) { + callback(null, 'Alice\nBob\nCharlie'); // Stub +} +const mockFsRead = test.mock.method(fs, 'readFile', mockReadFile); + const { Application, MailSystem } = require('./main'); -// TODO: write your tests here -// Remember to use Stub, Mock, and Spy when necessary \ No newline at end of file +test('MailSystem: write()', () => { + const mailSystem = new MailSystem; + assert.strictEqual(mailSystem.write('Alice'), 'Congrats, Alice!'); +}); + +test('MailSystem: send() success case', () => { + function returnHigh() { return 0.9; } // Stub + const mailSystem = new MailSystem(); + const mockRandom = test.mock.method(Math, 'random', returnHigh); + assert.strictEqual(mailSystem.send('Alice', 'Congrats, Alice!'), true); + mockRandom.mock.restore(); +}); + +test('MailSystem: send() failure case', () => { + function returnLow() { return 0.1; } // Stub + const mailSystem = new MailSystem(); + const mockRandom = test.mock.method(Math, 'random', returnLow); + assert.strictEqual(mailSystem.send('Alice', 'Congrats, Alice!'), false); + mockRandom.mock.restore(); +}); + +test('Application: constructor initializes names', async () => { + const app = new Application(); + await new Promise((resolve) => setTimeout(resolve, 10)); // Wait for async init + assert.deepStrictEqual(app.people, ['Alice', 'Bob', 'Charlie']); + assert.deepStrictEqual(app.selected, []); +}); + +test('Application: getRandomPerson()', () => { + function returnFirst() { return 0; } + const app = new Application(); + const mockRandom = test.mock.method(Math, 'random', returnFirst); + assert.strictEqual(app.getRandomPerson(), 'Alice'); + mockRandom.mock.restore(); +}); + +test('Application: selectNextPerson()', async () => { + const application = new Application(); + await new Promise(resolve => setTimeout(resolve, 50)); + + function returnApple() { + return 'Apple'; + } + const mockrandompersonA = test.mock.method(application, 'getRandomPerson', returnApple); + assert.strictEqual(application.selectNextPerson(), 'Apple'); + assert.deepStrictEqual(application.selected, ['Apple']); + mockrandompersonA.mock.restore(); + + let cnt = 0; + function returnAppleThenBanana() { + return cnt++ === 0 ? 'Apple' : 'Banana'; + } + const mockrandompersonAB = test.mock.method(application, 'getRandomPerson', returnAppleThenBanana); + assert.strictEqual(application.selectNextPerson(), 'Banana'); + assert.deepStrictEqual(application.selected, ['Apple', 'Banana']); + mockrandompersonAB.mock.restore(); + + function returnOrange() { + return 'Orange'; + } + const mockrandompersonO = test.mock.method(application, 'getRandomPerson', returnOrange); + assert.strictEqual(application.selectNextPerson(), 'Orange'); + assert.deepStrictEqual(application.selected, ['Apple', 'Banana', 'Orange']); + mockrandompersonO.mock.restore(); + + assert.strictEqual(application.selectNextPerson(), null); +}); + +test('Application: notifySelected()', () => { + const app = new Application(); + app.selected = ['Alice', 'Bob']; + + const mockWrite = test.mock.method(app.mailSystem, 'write'); + const mockSend = test.mock.method(app.mailSystem, 'send'); + + app.notifySelected(); + + assert.strictEqual(mockWrite.mock.callCount(), 2); + assert.strictEqual(mockSend.mock.callCount(), 2); + mockWrite.mock.restore(); + mockSend.mock.restore(); +});