added test framework (#113)
* added test framework * added toString on motors * enabling logs
This commit is contained in:
		| @@ -1,94 +1,47 @@ | ||||
| ```typescript | ||||
| let errors: string[] = []; | ||||
| let tachoB = 0; | ||||
| let tachoC = 0; | ||||
|  | ||||
| function assert(name: string, condition: boolean) { | ||||
|     if (!condition) { | ||||
|         errors.push(name) | ||||
|         brick.print("error:" + name, 0, 60) | ||||
|     } | ||||
| } | ||||
|  | ||||
| function assertClose(name: string, expected: number, actual: number, tolerance = 5) { | ||||
|     const diff = Math.abs(expected - actual); | ||||
|     assert(name + ` ${expected}vs${actual}`, diff < tolerance); | ||||
| } | ||||
|  | ||||
| function state(name: string, motor: motors.SingleMotor, line: number) { | ||||
|     brick.print(`${name}: ${motor.speed()}%,${motor.angle()}deg`, 0, 12 * line) | ||||
| } | ||||
|  | ||||
| function test(name: string, f: () => void, check?: () => void) { | ||||
|     motors.stopAllMotors(); | ||||
|     motors.largeB.clearCount() | ||||
|     motors.largeC.clearCount() | ||||
|     motors.largeB.setBrake(false) | ||||
|     motors.largeC.setBrake(false) | ||||
|     motors.largeB.setReversed(false); | ||||
|     motors.largeC.setReversed(false); | ||||
|     loops.pause(500); | ||||
|     tachoB = motors.largeB.angle() | ||||
|     tachoC = motors.largeC.angle() | ||||
|     brick.clearScreen() | ||||
|     brick.print(name, 0, 0) | ||||
|     state("B", motors.largeB, 1) | ||||
|     state("C", motors.largeC, 2) | ||||
|     f(); | ||||
|     loops.pause(3000); | ||||
|     state("B", motors.largeB, 3) | ||||
|     state("C", motors.largeC, 4) | ||||
|     if (check) | ||||
|         check() | ||||
|     motors.stopAllMotors(); | ||||
|     loops.pause(1000); | ||||
| } | ||||
|  | ||||
| brick.buttonLeft.onEvent(ButtonEvent.Click, function () { | ||||
|     test("lgBC set speed 25", () => { | ||||
|         motors.largeBC.setSpeed(25) | ||||
|     }, () => { | ||||
|         assertClose("speedB", 25, motors.largeB.speed()); | ||||
|         assertClose("speedC", 25, motors.largeC.speed()); | ||||
| tests.test("lgB set speed 10", () => { | ||||
|     motors.largeB.setSpeed(10); | ||||
|     loops.pause(100) | ||||
|     tests.assertClose("speedB", 10, motors.largeB.speed(), 2) | ||||
| }); | ||||
| }) | ||||
|  | ||||
| brick.buttonEnter.onEvent(ButtonEvent.Click, function () { | ||||
|     test("lgB set speed 10", () => { | ||||
|         motors.largeB.setSpeed(10) | ||||
|     }, () => assertClose("speedB", 10, motors.largeB.speed())); | ||||
|     test("lgB set speed 25 (reversed)", () => { | ||||
| tests.test("lgB set speed 25 (reversed)", () => { | ||||
|     motors.largeB.setReversed(true) | ||||
|     motors.largeB.setSpeed(25) | ||||
|     }, () => assertClose("speedB", -25, motors.largeB.speed())); | ||||
|     test("lgBC set speed 5", () => { | ||||
|         motors.largeBC.setSpeed(5) | ||||
|     }, () => { | ||||
|         assertClose("speedB", 5, motors.largeB.speed()); | ||||
|         assertClose("speedC", 5, motors.largeC.speed()); | ||||
|     loops.pause(100) | ||||
|     tests.assertClose("speedB", -25, motors.largeB.speed(), 2) | ||||
| }); | ||||
|     test("lgBC steer 50% 2x", () => { | ||||
| tests.test("lgBC set speed 5", () => { | ||||
|     motors.largeBC.setSpeed(5) | ||||
|     loops.pause(100) | ||||
|     tests.assertClose("speedB", 5, motors.largeB.speed(), 1); | ||||
|     tests.assertClose("speedC", 5, motors.largeC.speed(), 1); | ||||
| }); | ||||
| tests.test("lgBC steer 50% 2x", () => { | ||||
|     motors.largeBC.setBrake(true) | ||||
|         motors.largeBC.steer(50, 50, 2, MoveUnit.Rotations) | ||||
|     }, () => assertClose("largeB", 720, motors.largeB.angle())); | ||||
|     test("lgBC steer 50% 500deg", () => { | ||||
|         motors.largeBC.setBrake(true) | ||||
|         motors.largeBC.steer(50, 50, 500, MoveUnit.Degrees) | ||||
|     }, () => assertClose("largeB", 500, motors.largeB.angle())); | ||||
|     test("lgBC steer 50% 2s", () => { | ||||
|         motors.largeBC.setBrake(true) | ||||
|         motors.largeBC.steer(50, 50, 2000, MoveUnit.MilliSeconds) | ||||
|     motors.largeBC.steer(50, 50, 1, MoveUnit.Rotations) | ||||
|     loops.pause(1000) | ||||
|     tests.assertClose("largeB", 360, motors.largeB.angle(), 5) | ||||
|     motors.largeBC.setBrake(false) | ||||
| }) | ||||
|     test("lgBC tank 50% 720deg", () => { | ||||
| tests.test("lgBC steer 50% 500deg", () => { | ||||
|     motors.largeBC.setBrake(true) | ||||
|         motors.largeBC.tank(50, 50, 720, MoveUnit.Degrees) | ||||
|     }, () => assertClose("largeB", 720, motors.largeB.angle())); | ||||
|  | ||||
|     brick.clearScreen() | ||||
|     brick.print(`${errors.length} errors`, 0, 0) | ||||
|     let l = 1; | ||||
|     for (const error of errors) | ||||
|         brick.print(`error: ${error}`, 0, l++ * 12) | ||||
|     motors.largeBC.steer(50, 50, 135, MoveUnit.Degrees) | ||||
|     loops.pause(1000) | ||||
|     tests.assertClose("largeB", 135, motors.largeB.angle(), 5) | ||||
| }); | ||||
| tests.test("lgBC steer 50% 2s", () => { | ||||
|     motors.largeBC.setBrake(true) | ||||
|     motors.largeBC.steer(50, 50, 500, MoveUnit.MilliSeconds) | ||||
|     loops.pause(1000) | ||||
| }) | ||||
|  | ||||
| tests.test("lgBC tank 50% 720deg", () => { | ||||
|     motors.largeBC.setBrake(true) | ||||
|     motors.largeBC.tank(50, 50, 180, MoveUnit.Degrees) | ||||
|     loops.pause(1000) | ||||
|     tests.assertClose("largeB", 180, motors.largeB.angle(), 5) | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| ```package | ||||
| tests | ||||
| ``` | ||||
| @@ -43,7 +43,7 @@ | ||||
|   "brick.setStatusLight|param|pattern": "the lights pattern to use.", | ||||
|   "brick.showImage": "Shows an image on screen", | ||||
|   "brick.showImage|param|image": "image to draw", | ||||
|   "console": "Reading and writing data to the console output.\r\n\r\nReading and writing data to the console output.", | ||||
|   "console": "Reading and writing data to the console output.\n\nReading and writing data to the console output.", | ||||
|   "console.addListener": "Adds a listener for the log messages", | ||||
|   "console.log": "Write a line of text to the console output.", | ||||
|   "console.logValue": "Write a name:value pair as a line of text to the console output.", | ||||
| @@ -58,23 +58,24 @@ | ||||
|   "control.raiseEvent": "Announce that an event happened to registered handlers.", | ||||
|   "control.raiseEvent|param|src": "ID of the Component that generated the event", | ||||
|   "control.raiseEvent|param|value": "Component specific code indicating the cause of the event.", | ||||
|   "motors.Motor.isReady": "Returns a value indicating if the motor is still running a previous command.", | ||||
|   "motors.Motor.move": "Moves the motor by a number of rotations, degress or seconds", | ||||
|   "motors.Motor.move|param|speed": "the speed from ``100`` full forward to ``-100`` full backward, eg: 50", | ||||
|   "motors.Motor.move|param|unit": "the meaning of the value", | ||||
|   "motors.Motor.move|param|value": "the move quantity, eg: 2", | ||||
|   "motors.Motor.pauseUntilReady": "Pauses the execution until the previous command finished.", | ||||
|   "motors.Motor.pauseUntilReady|param|timeOut": "optional maximum pausing time in milliseconds", | ||||
|   "motors.Motor.reset": "Resets the motor(s).", | ||||
|   "motors.Motor.setBrake": "Sets the automatic brake on or off when the motor is off", | ||||
|   "motors.Motor.setBrake|param|brake": "a value indicating if the motor should break when off", | ||||
|   "motors.Motor.setReversed": "Reverses the motor polarity", | ||||
|   "motors.Motor.setSpeed": "Sets the speed of the motor.", | ||||
|   "motors.Motor.setSpeed|param|speed": "the speed from ``100`` full forward to ``-100`` full backward, eg: 50", | ||||
|   "motors.Motor.stop": "Stops the motor(s).", | ||||
|   "motors.SingleMotor.angle": "Gets motor ration angle.", | ||||
|   "motors.SingleMotor.clearCount": "Clears the motor count", | ||||
|   "motors.SingleMotor.speed": "Gets motor actual speed.", | ||||
|   "motors.Motor.angle": "Gets motor ration angle.", | ||||
|   "motors.Motor.clearCount": "Clears the motor count", | ||||
|   "motors.Motor.speed": "Gets motor actual speed.", | ||||
|   "motors.Motor.toString": "Returns the status of the motor", | ||||
|   "motors.MotorBase.isReady": "Returns a value indicating if the motor is still running a previous command.", | ||||
|   "motors.MotorBase.move": "Moves the motor by a number of rotations, degress or seconds", | ||||
|   "motors.MotorBase.move|param|speed": "the speed from ``100`` full forward to ``-100`` full backward, eg: 50", | ||||
|   "motors.MotorBase.move|param|unit": "the meaning of the value", | ||||
|   "motors.MotorBase.move|param|value": "the move quantity, eg: 2", | ||||
|   "motors.MotorBase.pauseUntilReady": "Pauses the execution until the previous command finished.", | ||||
|   "motors.MotorBase.pauseUntilReady|param|timeOut": "optional maximum pausing time in milliseconds", | ||||
|   "motors.MotorBase.reset": "Resets the motor(s).", | ||||
|   "motors.MotorBase.setBrake": "Sets the automatic brake on or off when the motor is off", | ||||
|   "motors.MotorBase.setBrake|param|brake": "a value indicating if the motor should break when off", | ||||
|   "motors.MotorBase.setReversed": "Reverses the motor polarity", | ||||
|   "motors.MotorBase.setSpeed": "Sets the speed of the motor.", | ||||
|   "motors.MotorBase.setSpeed|param|speed": "the speed from ``100`` full forward to ``-100`` full backward, eg: 50", | ||||
|   "motors.MotorBase.stop": "Stops the motor(s).", | ||||
|   "motors.SynchedMotorPair.steer": "Turns the motor and the follower motor by a number of rotations", | ||||
|   "motors.SynchedMotorPair.steer|param|speed": "the speed from ``100`` full forward to ``-100`` full backward, eg: 50", | ||||
|   "motors.SynchedMotorPair.steer|param|steering": "the ratio of power sent to the follower motor, from ``-100`` to ``100``", | ||||
| @@ -84,7 +85,17 @@ | ||||
|   "motors.SynchedMotorPair.tank|param|speedRight": "the speed on the right motor, eg: 50", | ||||
|   "motors.SynchedMotorPair.tank|param|unit": "@param speedLeft the speed on the left motor, eg: 50", | ||||
|   "motors.SynchedMotorPair.tank|param|value": "the amount of movement, eg: 2", | ||||
|   "motors.SynchedMotorPair.toString": "Returns the name(s) of the motor", | ||||
|   "motors.mkCmd": "Allocates a message buffer", | ||||
|   "motors.mkCmd|param|addSize": "required additional bytes", | ||||
|   "motors.mkCmd|param|cmd": "command id", | ||||
|   "motors.mkCmd|param|out": "ports", | ||||
|   "motors.readPWM": "Sends and receives a message from the motors device", | ||||
|   "motors.readPWM|param|buf": "message buffer", | ||||
|   "motors.resetAllMotors": "Resets all motors", | ||||
|   "motors.stopAllMotors": "Stops all motors", | ||||
|   "motors.writePWM": "Sends a command to the motors device", | ||||
|   "motors.writePWM|param|buf": "the command buffer", | ||||
|   "output.createBuffer": "Create a new zero-initialized buffer.", | ||||
|   "output.createBuffer|param|size": "number of bytes in the buffer", | ||||
|   "screen.clear": "Clear screen and reset font to normal.", | ||||
|   | ||||
| @@ -47,13 +47,13 @@ | ||||
|   "console|block": "console", | ||||
|   "control.raiseEvent|block": "raise event|from %src|with value %value", | ||||
|   "control|block": "control", | ||||
|   "motors.Motor.move|block": "move `icons.motorLarge` %motor|for %value|%unit|at %speed|%", | ||||
|   "motors.Motor.pauseUntilReady|block": "%motor|pause until ready", | ||||
|   "motors.Motor.setBrake|block": "set `icons.motorLarge` %motor|brake %brake", | ||||
|   "motors.Motor.setReversed|block": "set `icons.motorLarge` %motor|reversed %reversed", | ||||
|   "motors.Motor.setSpeed|block": "set speed of `icons.motorLarge` %motor|to %speed|%", | ||||
|   "motors.SingleMotor.angle|block": "`icons.motorLarge` %motor|angle", | ||||
|   "motors.SingleMotor.speed|block": "`icons.motorLarge` %motor|speed", | ||||
|   "motors.Motor.angle|block": "`icons.motorLarge` %motor|angle", | ||||
|   "motors.Motor.speed|block": "`icons.motorLarge` %motor|speed", | ||||
|   "motors.MotorBase.move|block": "move `icons.motorLarge` %motor|for %value|%unit|at %speed|%", | ||||
|   "motors.MotorBase.pauseUntilReady|block": "%motor|pause until ready", | ||||
|   "motors.MotorBase.setBrake|block": "set `icons.motorLarge` %motor|brake %brake", | ||||
|   "motors.MotorBase.setReversed|block": "set `icons.motorLarge` %motor|reversed %reversed", | ||||
|   "motors.MotorBase.setSpeed|block": "set speed of `icons.motorLarge` %motor|to %speed|%", | ||||
|   "motors.SynchedMotorPair.steer|block": "steer %chassis|%steering|%|at speed %speed|%|by %value|%unit", | ||||
|   "motors.SynchedMotorPair.tank|block": "tank %chassis|left %speedLeft|%|right %speedRight|%|by %value|%unit", | ||||
|   "motors.largeAB|block": "large A+B", | ||||
|   | ||||
| @@ -53,32 +53,56 @@ namespace motors { | ||||
|         motorMM = control.mmap("/dev/lms_motor", MotorDataOff.Size * DAL.NUM_OUTPUTS, 0) | ||||
|         if (!motorMM) control.fail("no motor file") | ||||
|  | ||||
|         resetMotors() | ||||
|         resetAllMotors() | ||||
|  | ||||
|         let buf = output.createBuffer(1) | ||||
|         const buf = output.createBuffer(1) | ||||
|         buf[0] = DAL.opProgramStart | ||||
|         writePWM(buf) | ||||
|     } | ||||
|  | ||||
|     function writePWM(buf: Buffer): void { | ||||
|     /** | ||||
|      * Sends a command to the motors device | ||||
|      * @param buf the command buffer | ||||
|      */ | ||||
|     //% | ||||
|     export function writePWM(buf: Buffer): void { | ||||
|         init() | ||||
|         pwmMM.write(buf) | ||||
|     } | ||||
|  | ||||
|     function readPWM(buf: Buffer): number { | ||||
|     /** | ||||
|      * Sends and receives a message from the motors device | ||||
|      * @param buf message buffer | ||||
|      */ | ||||
|     //% | ||||
|     export function readPWM(buf: Buffer): number { | ||||
|         init() | ||||
|         return pwmMM.read(buf); | ||||
|     } | ||||
|  | ||||
|     function mkCmd(out: Output, cmd: number, addSize: number) { | ||||
|     /** | ||||
|      * Allocates a message buffer | ||||
|      * @param out ports | ||||
|      * @param cmd command id | ||||
|      * @param addSize required additional bytes | ||||
|      */ | ||||
|     //% | ||||
|     export function mkCmd(out: Output, cmd: number, addSize: number) { | ||||
|         const b = output.createBuffer(2 + addSize) | ||||
|         b.setNumber(NumberFormat.UInt8LE, 0, cmd) | ||||
|         b.setNumber(NumberFormat.UInt8LE, 1, out) | ||||
|         return b | ||||
|     } | ||||
|  | ||||
|     function resetMotors() { | ||||
|         reset(Output.ALL) | ||||
|     function outputToName(out: Output): string { | ||||
|         let r = ""; | ||||
|         for (let i = 0; i < DAL.NUM_OUTPUTS; ++i) { | ||||
|             if (out & (1 << i)) { | ||||
|                 if (r.length > 0) r += "+"; | ||||
|                 r += "ABCD"[i]; | ||||
|             } | ||||
|         } | ||||
|         return r; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -92,9 +116,18 @@ namespace motors { | ||||
|         writePWM(b) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Resets all motors | ||||
|      */ | ||||
|     //% group="Motion" | ||||
|     export function resetAllMotors() { | ||||
|         reset(Output.ALL) | ||||
|     } | ||||
|  | ||||
|     //% fixedInstances | ||||
|     export class Motor extends control.Component { | ||||
|     export class MotorBase extends control.Component { | ||||
|         protected _port: Output; | ||||
|         protected _portName: string; | ||||
|         protected _brake: boolean; | ||||
|         private _initialized: boolean; | ||||
|         private _init: () => void; | ||||
| @@ -104,6 +137,7 @@ namespace motors { | ||||
|         constructor(port: Output, init: () => void, setSpeed: (speed: number) => void, move: (steps: boolean, stepsOrTime: number, speed: number) => void) { | ||||
|             super(); | ||||
|             this._port = port; | ||||
|             this._portName = outputToName(this._port); | ||||
|             this._brake = false; | ||||
|             this._initialized = false; | ||||
|             this._init = init; | ||||
| @@ -244,7 +278,7 @@ namespace motors { | ||||
|     } | ||||
|  | ||||
|     //% fixedInstances | ||||
|     export class SingleMotor extends Motor { | ||||
|     export class Motor extends MotorBase { | ||||
|         private _large: boolean; | ||||
|  | ||||
|         constructor(port: Output, large: boolean) { | ||||
| @@ -322,34 +356,42 @@ namespace motors { | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Returns the status of the motor | ||||
|          */ | ||||
|         //% | ||||
|         toString(): string { | ||||
|             return `${this._large ? "" : "M"}${this._portName} ${this.speed()}% ${this.angle()}>`; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     //% whenUsed fixedInstance block="large A" | ||||
|     export const largeA = new SingleMotor(Output.A, true); | ||||
|     export const largeA = new Motor(Output.A, true); | ||||
|  | ||||
|     //% whenUsed fixedInstance block="large B" | ||||
|     export const largeB = new SingleMotor(Output.B, true); | ||||
|     export const largeB = new Motor(Output.B, true); | ||||
|  | ||||
|     //% whenUsed fixedInstance block="large C" | ||||
|     export const largeC = new SingleMotor(Output.C, true); | ||||
|     export const largeC = new Motor(Output.C, true); | ||||
|  | ||||
|     //% whenUsed fixedInstance block="large D" | ||||
|     export const largeD = new SingleMotor(Output.D, true); | ||||
|     export const largeD = new Motor(Output.D, true); | ||||
|  | ||||
|     //% whenUsed fixedInstance block="medium A" | ||||
|     export const mediumA = new SingleMotor(Output.A, false); | ||||
|     export const mediumA = new Motor(Output.A, false); | ||||
|  | ||||
|     //% whenUsed fixedInstance block="medium B" | ||||
|     export const mediumB = new SingleMotor(Output.B, false); | ||||
|     export const mediumB = new Motor(Output.B, false); | ||||
|  | ||||
|     //% whenUsed fixedInstance block="medium C" | ||||
|     export const mediumC = new SingleMotor(Output.C, false); | ||||
|     export const mediumC = new Motor(Output.C, false); | ||||
|  | ||||
|     //% whenUsed fixedInstance block="medium D" | ||||
|     export const mediumD = new SingleMotor(Output.D, false); | ||||
|     export const mediumD = new Motor(Output.D, false); | ||||
|  | ||||
|     //% fixedInstances | ||||
|     export class SynchedMotorPair extends Motor { | ||||
|     export class SynchedMotorPair extends MotorBase { | ||||
|  | ||||
|         constructor(ports: Output) { | ||||
|             super(ports, () => this.__init(), (speed) => this.__setSpeed(speed), (steps, stepsOrTime, speed) => this.__move(steps, stepsOrTime, speed)); | ||||
| @@ -458,6 +500,20 @@ namespace motors { | ||||
|             const steering = (speedRight * 100 / speedLeft) >> 0; | ||||
|             this.steer(speedLeft, steering, value, unit); | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * Returns the name(s) of the motor | ||||
|          */ | ||||
|         //% | ||||
|         toString(): string { | ||||
|             let r = outputToName(this._port); | ||||
|             for (let i = 0; i < DAL.NUM_OUTPUTS; ++i) { | ||||
|                 if (this._port & (1 << i)) { | ||||
|                     r += ` ${getMotorData(1 << i).actualSpeed}%` | ||||
|                 } | ||||
|             } | ||||
|             return r; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     //% whenUsed fixedInstance block="large B+C" | ||||
|   | ||||
							
								
								
									
										35
									
								
								libs/tests/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								libs/tests/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| # tests | ||||
|  | ||||
| A unit test framework | ||||
|  | ||||
| ## Defining tests | ||||
|  | ||||
| Tests are registered as event handlers. They will automatically run once ``on start`` is finished. | ||||
|  | ||||
| ```blocks | ||||
| tests.test("lgB set speed 10", () => { | ||||
|     motors.largeB.setSpeed(10); | ||||
|     loops.pause(100) | ||||
|     tests.assertClose("speedB", 10, motors.largeB.speed(), 2) | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| ## Assertions | ||||
|  | ||||
| The library has various asserts that will register fault. Note that since exceptions are not available, assertion failure **do not** stop the program execution. | ||||
|  | ||||
| * **assert** checks a boolean condition | ||||
|  | ||||
| ```blocks | ||||
| tests.assert("speed positive", motors.largeB.speed() > 0) | ||||
| ``` | ||||
|  | ||||
| * **assert close** checks that a numberical value is within a particular range | ||||
|  | ||||
| ```blocks | ||||
| tests.assertClose("speed", motors.largeB.speed(), 10, 2) | ||||
| ``` | ||||
|  | ||||
| ```package | ||||
| tests | ||||
| ``` | ||||
							
								
								
									
										9
									
								
								libs/tests/_locales/tests-jsdoc-strings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								libs/tests/_locales/tests-jsdoc-strings.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| { | ||||
|   "tests": "Unit tests framework", | ||||
|   "tests.assert": "Checks a boolean condition", | ||||
|   "tests.assertClose": "Checks that 2 values are close to each other", | ||||
|   "tests.assertClose|param|actual": "what the value was", | ||||
|   "tests.assertClose|param|expected": "what the value should be", | ||||
|   "tests.assertClose|param|tolerance": "the acceptable error margin", | ||||
|   "tests.test": "Registers a test to run" | ||||
| } | ||||
							
								
								
									
										6
									
								
								libs/tests/_locales/tests-strings.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								libs/tests/_locales/tests-strings.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| { | ||||
|   "tests.assert|block": "assert %message|%condition", | ||||
|   "tests.test|block": "test %name", | ||||
|   "tests|block": "tests", | ||||
|   "{id:category}Tests": "Tests" | ||||
| } | ||||
							
								
								
									
										14
									
								
								libs/tests/pxt.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								libs/tests/pxt.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| { | ||||
|     "name": "tests", | ||||
|     "description": "A unit test library", | ||||
|     "files": [ | ||||
|         "README.md", | ||||
|         "tests.ts" | ||||
|     ], | ||||
|     "testFiles": [ | ||||
|     ], | ||||
|     "public": true, | ||||
|     "dependencies": { | ||||
|         "core": "file:../core" | ||||
|     } | ||||
| } | ||||
							
								
								
									
										89
									
								
								libs/tests/tests.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								libs/tests/tests.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| /** | ||||
|  * Unit tests framework | ||||
|  */ | ||||
| //% weight=100 color=#0fbc11 icon="" | ||||
| namespace tests { | ||||
|     class Test { | ||||
|         name: string; | ||||
|         handler: () => void; | ||||
|         errors: string[]; | ||||
|  | ||||
|         constructor(name: string, handler: () => void) { | ||||
|             this.name = name; | ||||
|             this.handler = handler; | ||||
|             this.errors = []; | ||||
|         } | ||||
|  | ||||
|         reset() { | ||||
|             motors.stopAllMotors(); | ||||
|             motors.resetAllMotors(); | ||||
|         } | ||||
|  | ||||
|         run() { | ||||
|             // clear state | ||||
|             this.reset(); | ||||
|  | ||||
|             console.log(`# ${this.name}`) | ||||
|             this.handler() | ||||
|  | ||||
|             // ensure clean state after test | ||||
|             this.reset(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     let _tests: Test[] = undefined; | ||||
|     let _currentTest: Test = undefined; | ||||
|  | ||||
|     function run() { | ||||
|         if (!_tests) return; | ||||
|  | ||||
|         const start = control.millis(); | ||||
|         console.sendToScreen(); | ||||
|         console.log(`${_tests.length} tests`) | ||||
|         for (let i = 0; i < _tests.length; ++i) { | ||||
|             const t = _currentTest = _tests[i]; | ||||
|             t.run(); | ||||
|             _currentTest = undefined; | ||||
|         } | ||||
|         console.log(`${_tests.map(t => t.errors.length).reduce((p, c) => p + c, 0)} X, ${Math.ceil((control.millis() - start) / 1000)}s`) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Registers a test to run | ||||
|      */ | ||||
|     //% blockId=testtest block="test %name" | ||||
|     export function test(name: string, handler: () => void): void { | ||||
|         if (!name || !handler) return; | ||||
|         if (!_tests) { | ||||
|             _tests = []; | ||||
|             control.runInBackground(function () { | ||||
|                 // should run after on start | ||||
|                 loops.pause(100) | ||||
|                 run() | ||||
|             }) | ||||
|         } | ||||
|         _tests.push(new Test(name, handler)); | ||||
|     } | ||||
|  | ||||
|     /**  | ||||
|      * Checks a boolean condition | ||||
|      */ | ||||
|     //% blockId=testAssert block="assert %message|%condition" | ||||
|     export function assert(message: string, condition: boolean) { | ||||
|         if (!condition) { | ||||
|             console.log(` X ${message || ''}`) | ||||
|             if (_currentTest) | ||||
|                 _currentTest.errors.push(message); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Checks that 2 values are close to each other | ||||
|      * @param expected what the value should be | ||||
|      * @param actual what the value was | ||||
|      * @param tolerance the acceptable error margin | ||||
|      */ | ||||
|     export function assertClose(name: string, expected: number, actual: number, tolerance: number) { | ||||
|         assert(`${name} ${expected} != ${actual} +-${tolerance}`, Math.abs(expected - actual) <= tolerance); | ||||
|     } | ||||
| } | ||||
| @@ -15,7 +15,8 @@ | ||||
|         "libs/ultrasonic-sensor", | ||||
|         "libs/infrared-sensor", | ||||
|         "libs/gyro-sensor", | ||||
|         "libs/ev3" | ||||
|         "libs/ev3", | ||||
|         "libs/tests" | ||||
|     ], | ||||
|     "simulator": { | ||||
|         "autoRun": true, | ||||
| @@ -52,7 +53,9 @@ | ||||
|     "serial": { | ||||
|       "vendorId": "0x0694", | ||||
|       "productId": "0x0005", | ||||
|       "rawHID": true | ||||
|       "rawHID": true, | ||||
|       "useEditor": true, | ||||
|       "log": true | ||||
|   }, | ||||
|     "runtime": { | ||||
|         "mathBlocks": true, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user