diff --git a/Sprint-3/1-implement-and-rewrite-tests/README.md b/Sprint-3/1-implement-and-rewrite-tests/README.md index 96fd8cd68..a65bd2077 100644 --- a/Sprint-3/1-implement-and-rewrite-tests/README.md +++ b/Sprint-3/1-implement-and-rewrite-tests/README.md @@ -1,5 +1,8 @@ # Implement solutions and rewrite tests with Jest +Before writing any code, please read the [Testing Function Guide](testing-guide.md) to learn how +to choose test values that thoroughly test a function. + ## 1 Implement solutions In the `implement` directory you've got a number of functions you'll need to implement. diff --git a/Sprint-3/1-implement-and-rewrite-tests/implement/1-get-angle-type.js b/Sprint-3/1-implement-and-rewrite-tests/implement/1-get-angle-type.js index ca1dfe7f2..9e05a871e 100644 --- a/Sprint-3/1-implement-and-rewrite-tests/implement/1-get-angle-type.js +++ b/Sprint-3/1-implement-and-rewrite-tests/implement/1-get-angle-type.js @@ -1,26 +1,29 @@ // Implement a function getAngleType -// Build up your function case by case, writing tests as you go -// The first test and case is written for you. The next case has a test, but no code. -// Execute this script in your terminal -// node 1-get-angle-type.js -// The assertion error will tell you what the expected output is -// Write the code to pass the test -// Then, write the next test! :) Go through this process until all the cases are implemented +// +// When given an angle in degrees, it should return a string indicating the type of angle: +// - "Acute angle" for angles greater than 0° and less than 90° +// - "Right angle" for exactly 90° +// - "Obtuse angle" for angles greater than 90° and less than 180° +// - "Straight angle" for exactly 180° +// - "Reflex angle" for angles greater than 180° and less than 360° +// - "Invalid angle" for angles outside the valid range. + +// Assumption: The parameter is a valid number. (You do not need to handle non-numeric inputs.) + +// Acceptance criteria: +// After you have implemented the function, write tests to cover all the cases, and +// execute the code to ensure all tests pass. function getAngleType(angle) { - if (angle === 90) { - return "Right angle"; - } - // Run the tests, work out what Case 2 is testing, and implement the required code here. - // Then keep going for the other cases, one at a time. + // TODO: Implement this function } // The line below allows us to load the getAngleType function into tests in other files. // This will be useful in the "rewrite tests with jest" step. module.exports = getAngleType; -// we're going to use this helper function to make our assertions easier to read -// if the actual output matches the target output, the test will pass +// This helper function is written to make our assertions easier to read. +// If the actual output matches the target output, the test will pass function assertEquals(actualOutput, targetOutput) { console.assert( actualOutput === targetOutput, @@ -28,36 +31,7 @@ function assertEquals(actualOutput, targetOutput) { ); } -// Acceptance criteria: - -// Given an angle in degrees, -// When the function getAngleType is called with this angle, -// Then it should: - -// Case 1: Identify Right Angles: -// When the angle is exactly 90 degrees, -// Then the function should return "Right angle" +// TODO: Write tests to cover all cases, including boundary and invalid cases. +// Example: Identify Right Angles const right = getAngleType(90); assertEquals(right, "Right angle"); - -// Case 2: Identify Acute Angles: -// When the angle is less than 90 degrees, -// Then the function should return "Acute angle" -const acute = getAngleType(45); -assertEquals(acute, "Acute angle"); - -// Case 3: Identify Obtuse Angles: -// When the angle is greater than 90 degrees and less than 180 degrees, -// Then the function should return "Obtuse angle" -const obtuse = getAngleType(120); -// ====> write your test here, and then add a line to pass the test in the function above - -// Case 4: Identify Straight Angles: -// When the angle is exactly 180 degrees, -// Then the function should return "Straight angle" -// ====> write your test here, and then add a line to pass the test in the function above - -// Case 5: Identify Reflex Angles: -// When the angle is greater than 180 degrees and less than 360 degrees, -// Then the function should return "Reflex angle" -// ====> write your test here, and then add a line to pass the test in the function above \ No newline at end of file diff --git a/Sprint-3/1-implement-and-rewrite-tests/implement/2-is-proper-fraction.js b/Sprint-3/1-implement-and-rewrite-tests/implement/2-is-proper-fraction.js index a4739af77..970cb9b64 100644 --- a/Sprint-3/1-implement-and-rewrite-tests/implement/2-is-proper-fraction.js +++ b/Sprint-3/1-implement-and-rewrite-tests/implement/2-is-proper-fraction.js @@ -1,23 +1,24 @@ -// Implement a function isProperFraction -// Write assertions for your function to check it works in different cases -// Terms: -// Fractions: https://www.bbc.co.uk/bitesize/topics/zt9n6g8/articles/zjxpp4j -// Written here like this: 1/2 == Numerator/Denominator -// the first test and first case is written for you -// complete the rest of the tests and cases -// write one test at a time, and make it pass, build your solution up methodically +// Implement a function isProperFraction, +// when given two numbers, a numerator and a denominator, it should return true if +// the given numbers form a proper fraction, and false otherwise. + +// Assumption: The parameters are valid numbers (not NaN or Infinity). + +// Note: If you are unfamiliar with proper fractions, please look up its mathematical definition. + +// Acceptance criteria: +// After you have implemented the function, write tests to cover all the cases, and +// execute the code to ensure all tests pass. function isProperFraction(numerator, denominator) { - if (numerator < denominator) { - return true; - } + // TODO: Implement this function } // The line below allows us to load the isProperFraction function into tests in other files. // This will be useful in the "rewrite tests with jest" step. module.exports = isProperFraction; -// here's our helper again +// Here's our helper again function assertEquals(actualOutput, targetOutput) { console.assert( actualOutput === targetOutput, @@ -25,35 +26,8 @@ function assertEquals(actualOutput, targetOutput) { ); } -// Acceptance criteria: +// TODO: Write tests to cover all cases. +// What combinations of numerators and denominators should you test? -// Proper Fraction check: -// Input: numerator = 2, denominator = 3 -// target output: true -// Explanation: The fraction 2/3 is a proper fraction, where the numerator is less than the denominator. The function should return true. -const properFraction = isProperFraction(2, 3); -assertEquals(properFraction, true); - -// Improper Fraction check: -// Input: numerator = 5, denominator = 2 -// target output: false -// Explanation: The fraction 5/2 is an improper fraction, where the numerator is greater than or equal to the denominator. The function should return false. -const improperFraction = isProperFraction(5, 2); -assertEquals(improperFraction, false); - -// Negative Fraction check: -// Input: numerator = -4, denominator = 7 -// target output: true -// Explanation: The fraction -4/7 is a proper fraction because the absolute value of the numerator (4) is less than the denominator (7). The function should return true. -const negativeFraction = isProperFraction(-4, 7); -// ====> complete with your assertion - -// Equal Numerator and Denominator check: -// Input: numerator = 3, denominator = 3 -// target output: false -// Explanation: The fraction 3/3 is not a proper fraction because the numerator is equal to the denominator. The function should return false. -const equalFraction = isProperFraction(3, 3); -// ====> complete with your assertion - -// Stretch: -// What other scenarios could you test for? +// Example: 1/2 is a proper fraction +assertEquals(isProperFraction(1, 2), true); diff --git a/Sprint-3/1-implement-and-rewrite-tests/implement/3-get-card-value.js b/Sprint-3/1-implement-and-rewrite-tests/implement/3-get-card-value.js index 266525d1b..c7559e787 100644 --- a/Sprint-3/1-implement-and-rewrite-tests/implement/3-get-card-value.js +++ b/Sprint-3/1-implement-and-rewrite-tests/implement/3-get-card-value.js @@ -1,57 +1,52 @@ // This problem involves playing cards: https://en.wikipedia.org/wiki/Standard_52-card_deck -// You will need to implement a function getCardValue -// the function takes a single parameter, a string representing a playing card -// the function should return the numerical value of the card -// the first test and first case is written for you -// complete the rest of the tests and cases -// write one test at a time, and make it pass, build your solution up methodically -// just make one change at a time -- don't rush -- programmers are deep and careful thinkers +// Implement a function getCardValue, when given a string representing a playing card, +// should return the numerical value of the card. + +// A valid card string will contain a rank followed by the suit. +// The rank can be one of the following strings: +// "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K" +// The suit can be one of the following emojis: +// "♠", "♥", "♦", "♣" +// For example: "A♠", "2♥", "10♥", "J♣", "Q♦", "K♦". + +// When the card is an ace ("A"), the function should return 11. +// When the card is a face card ("J", "Q", "K"), the function should return 10. +// When the card is a number card ("2" to "10"), the function should return its numeric value. + +// When the card string is invalid (not following the above format), the function should +// throw an error. + +// Acceptance criteria: +// After you have implemented the function, write tests to cover all the cases, and +// execute the code to ensure all tests pass. + function getCardValue(card) { - if (rank === "A") { - return 11; - } + // TODO: Implement this function } // The line below allows us to load the getCardValue function into tests in other files. // This will be useful in the "rewrite tests with jest" step. module.exports = getCardValue; -// You need to write assertions for your function to check it works in different cases -// we're going to use this helper function to make our assertions easier to read -// if the actual output matches the target output, the test will pass +// Helper functions to make our assertions easier to read. function assertEquals(actualOutput, targetOutput) { console.assert( actualOutput === targetOutput, `Expected ${actualOutput} to equal ${targetOutput}` ); } -// Acceptance criteria: -// Given a card string in the format "A♠" (representing a card in blackjack - the last character will always be an emoji for a suit, and all characters before will be a number 2-10, or one letter of J, Q, K, A), -// When the function getCardValue is called with this card string as input, -// Then it should return the numerical card value -const aceofSpades = getCardValue("A♠"); -assertEquals(aceofSpades, 11); - -// Handle Number Cards (2-10): -// Given a card with a rank between "2" and "9", -// When the function is called with such a card, -// Then it should return the numeric value corresponding to the rank (e.g., "5" should return 5). -const fiveofHearts = getCardValue("5♥"); -// ====> write your test here, and then add a line to pass the test in the function above - -// Handle Face Cards (J, Q, K): -// Given a card with a rank of "10," "J," "Q," or "K", -// When the function is called with such a card, -// Then it should return the value 10, as these cards are worth 10 points each in blackjack. - -// Handle Ace (A): -// Given a card with a rank of "A", -// When the function is called with an Ace, -// Then it should, by default, assume the Ace is worth 11 points, which is a common rule in blackjack. - -// Handle Invalid Cards: -// Given a card with an invalid rank (neither a number nor a recognized face card), -// When the function is called with such a card, -// Then it should throw an error indicating "Invalid card rank." +// TODO: Write tests to cover all outcomes, including throwing errors for invalid cards. +// Examples: +assertEquals(getCardValue("9♠"), 9); + +// Handling invalid cards +try { + getCardValue("invalid"); + + // This line will not be reached if an error is thrown as expected + console.error("Error was not thrown for invalid card"); +} catch (e) {} + +// What other invalid card cases can you think of? diff --git a/Sprint-3/1-implement-and-rewrite-tests/rewrite-tests-with-jest/1-get-angle-type.test.js b/Sprint-3/1-implement-and-rewrite-tests/rewrite-tests-with-jest/1-get-angle-type.test.js index 4a92a3e82..d777f348d 100644 --- a/Sprint-3/1-implement-and-rewrite-tests/rewrite-tests-with-jest/1-get-angle-type.test.js +++ b/Sprint-3/1-implement-and-rewrite-tests/rewrite-tests-with-jest/1-get-angle-type.test.js @@ -2,25 +2,19 @@ // We will use the same function, but write tests for it using Jest in this file. const getAngleType = require("../implement/1-get-angle-type"); -test("should identify right angle (90°)", () => { - expect(getAngleType(90)).toEqual("Right angle"); -}); - -// REPLACE the comments with the tests -// make your test descriptions as clear and readable as possible - -// Case 2: Identify Acute Angles: -// When the angle is less than 90 degrees, -// Then the function should return "Acute angle" +// TODO: Write tests in Jest syntax to cover all cases/outcomes, +// including boundary and invalid cases. -// Case 3: Identify Obtuse Angles: -// When the angle is greater than 90 degrees and less than 180 degrees, -// Then the function should return "Obtuse angle" - -// Case 4: Identify Straight Angles: -// When the angle is exactly 180 degrees, -// Then the function should return "Straight angle" +// Case 1: Acute angles +test(`should return "Acute angle" when (0 < angle < 90)`, () => { + // Test various acute angles, including boundary cases + expect(getAngleType(1)).toEqual("Acute angle"); + expect(getAngleType(45)).toEqual("Acute angle"); + expect(getAngleType(89)).toEqual("Acute angle"); +}); -// Case 5: Identify Reflex Angles: -// When the angle is greater than 180 degrees and less than 360 degrees, -// Then the function should return "Reflex angle" +// Case 2: Right angle +// Case 3: Obtuse angles +// Case 4: Straight angle +// Case 5: Reflex angles +// Case 6: Invalid angles diff --git a/Sprint-3/1-implement-and-rewrite-tests/rewrite-tests-with-jest/2-is-proper-fraction.test.js b/Sprint-3/1-implement-and-rewrite-tests/rewrite-tests-with-jest/2-is-proper-fraction.test.js index caf08d15b..b9f77ada2 100644 --- a/Sprint-3/1-implement-and-rewrite-tests/rewrite-tests-with-jest/2-is-proper-fraction.test.js +++ b/Sprint-3/1-implement-and-rewrite-tests/rewrite-tests-with-jest/2-is-proper-fraction.test.js @@ -2,12 +2,9 @@ // We will use the same function, but write tests for it using Jest in this file. const isProperFraction = require("../implement/2-is-proper-fraction"); -test("should return true for a proper fraction", () => { - expect(isProperFraction(2, 3)).toEqual(true); -}); - -// Case 2: Identify Improper Fractions: +// TODO: Write tests in Jest syntax to cover all combinations of positives, negatives, and zeros. -// Case 3: Identify Negative Fractions: - -// Case 4: Identify Equal Numerator and Denominator: +// Special case: numerator is zero +test(`should return false when denominator is zero`, () => { + expect(isProperFraction(1, 0)).toEqual(false); +}); diff --git a/Sprint-3/1-implement-and-rewrite-tests/rewrite-tests-with-jest/3-get-card-value.test.js b/Sprint-3/1-implement-and-rewrite-tests/rewrite-tests-with-jest/3-get-card-value.test.js index 04418ff72..cf7f9dae2 100644 --- a/Sprint-3/1-implement-and-rewrite-tests/rewrite-tests-with-jest/3-get-card-value.test.js +++ b/Sprint-3/1-implement-and-rewrite-tests/rewrite-tests-with-jest/3-get-card-value.test.js @@ -2,12 +2,19 @@ // We will use the same function, but write tests for it using Jest in this file. const getCardValue = require("../implement/3-get-card-value"); -test("should return 11 for Ace of Spades", () => { - const aceofSpades = getCardValue("A♠"); - expect(aceofSpades).toEqual(11); +// TODO: Write tests in Jest syntax to cover all possible outcomes. + +// Case 1: Ace (A) +test(`Should return 11 when given an ace card`, () => { + expect(getCardValue("A♠")).toEqual(11); }); -// Case 2: Handle Number Cards (2-10): -// Case 3: Handle Face Cards (J, Q, K): -// Case 4: Handle Ace (A): -// Case 5: Handle Invalid Cards: +// Suggestion: Group the remaining test data into these categories: +// Number Cards (2-10) +// Face Cards (J, Q, K) +// Invalid Cards + +// To learn how to test whether a function throws an error as expected in Jest, +// please refer to the Jest documentation: +// https://jestjs.io/docs/expect#tothrowerror + diff --git a/Sprint-3/1-implement-and-rewrite-tests/testing-guide.md b/Sprint-3/1-implement-and-rewrite-tests/testing-guide.md new file mode 100644 index 000000000..1303a4bae --- /dev/null +++ b/Sprint-3/1-implement-and-rewrite-tests/testing-guide.md @@ -0,0 +1,90 @@ +# A Beginner's Guide to Testing Functions + +## 1. What Is a Function? + +``` +Input ──▶ Function ──▶ Output +``` + +A ***pure function*** +- Takes **input** (via **arguments**) +- Does some work +- Yields **one output** (via a **return value**) + +Example: + +``` +sum(2, 3) → 5 +``` + +Important idea: the same input should produce the same output. + + +## 2. Testing Means Predicting + +Testing means: +> If I give this input, what output should I get? + + +## 3. Choosing Good Test Values + +### Step 1: Determining the space of possible inputs +Ask: +- What type of value is expected? +- What values make sense? + - If they are numbers: + - Are they integers or floating-point numbers? + - What is their range? + - If they are strings: + - What are their length and patterns? +- What values would not make sense? + +### Step 2: Choosing Good Test Values + +#### Normal Cases + +These confirm that the function works in normal use. + +- What does a typical, ordinary input look like? + +#### Boundary Cases + +Test values exactly at, just inside, and just outside defined ranges. +These values are where logic breaks most often. + +#### Consider All Outcomes + +Every outcome must be reached by at least one test. + +- How many different results can this function produce? +- Have I tested a value that leads to each one? + +#### Crossing the Edges and Invalid Values + +This tests how the function behaves when assumptions are violated. +- What happens when input is outside of the expected range? +- What happens when input is not of the expected type? +- What happens when input is not in the expected format? + +## 4. How to Test + +### 1. Using `console.assert()` + +```javascript + // Report a failure only when the first argument is false + console.assert( sum(4, 6) === 10, "Expected 4 + 6 to equal 10" ); +``` + +It is simpler than using `if-else` and requires no setup. + +### 2. Jest Testing Framework + +```javascript + test("Should correctly return the sum of two positive numbers", () => { + expect( sum(4, 6) ).toEqual(10); + ... // Can test multiple samples + }); + +``` + +Jest supports many useful functions for testing but requires additional setup. diff --git a/Sprint-3/2-practice-tdd/count.test.js b/Sprint-3/2-practice-tdd/count.test.js index 42baf4b4b..179ea0ddf 100644 --- a/Sprint-3/2-practice-tdd/count.test.js +++ b/Sprint-3/2-practice-tdd/count.test.js @@ -1,14 +1,14 @@ // implement a function countChar that counts the number of times a character occurs in a string const countChar = require("./count"); -// Given a string str and a single character char to search for, +// Given a string `str` and a single character `char` to search for, // When the countChar function is called with these inputs, // Then it should: // Scenario: Multiple Occurrences -// Given the input string str, -// And a character char that may occur multiple times with overlaps within str (e.g., 'a' in 'aaaaa'), +// Given the input string `str`, +// And a character `char` that occurs one or more times in `str` (e.g., 'a' in 'aaaaa'), // When the function is called with these inputs, -// Then it should correctly count overlapping occurrences of char (e.g., 'a' appears five times in 'aaaaa'). +// Then it should correctly count occurrences of `char`. test("should count multiple occurrences of a character", () => { const str = "aaaaa"; @@ -18,7 +18,7 @@ test("should count multiple occurrences of a character", () => { }); // Scenario: No Occurrences -// Given the input string str, -// And a character char that does not exist within the case-sensitive str, +// Given the input string `str`, +// And a character `char` that does not exist within `str`. // When the function is called with these inputs, -// Then it should return 0, indicating that no occurrences of the char were found in the case-sensitive str. +// Then it should return 0, indicating that no occurrences of `char` were found. diff --git a/Sprint-3/2-practice-tdd/get-ordinal-number.test.js b/Sprint-3/2-practice-tdd/get-ordinal-number.test.js index dfe4b6091..d4c38183e 100644 --- a/Sprint-3/2-practice-tdd/get-ordinal-number.test.js +++ b/Sprint-3/2-practice-tdd/get-ordinal-number.test.js @@ -1,13 +1,20 @@ const getOrdinalNumber = require("./get-ordinal-number"); -// In this week's prep, we started implementing getOrdinalNumber +// In this week's prep, we started implementing getOrdinalNumber. -// continue testing and implementing getOrdinalNumber for additional cases -// Write your tests using Jest - remember to run your tests often for continual feedback +// Continue testing and implementing getOrdinalNumber for additional cases. +// Write your tests using Jest — remember to run your tests often for continual feedback. -// Case 1: Identify the ordinal number for 1 -// When the number is 1, -// Then the function should return "1st" +// To ensure thorough testing, we need broad scenarios that cover all possible cases. +// Listing individual values, however, can quickly lead to an unmanageable number of test cases. +// Instead of writing tests for individual numbers, consider grouping all possible input values +// into meaningful categories. Then, select representative samples from each category to test. +// This approach improves coverage and makes our tests easier to maintain. -test("should return '1st' for 1", () => { +// Case 1: Numbers ending with 1 (but not 11) +// When the number ends with 1, except those ending with 11, +// Then the function should return a string by appending "st" to the number. +test("should append 'st' for numbers ending with 1, except those ending with 11", () => { expect(getOrdinalNumber(1)).toEqual("1st"); + expect(getOrdinalNumber(21)).toEqual("21st"); + expect(getOrdinalNumber(31)).toEqual("131st"); }); diff --git a/Sprint-3/2-practice-tdd/repeat-str.test.js b/Sprint-3/2-practice-tdd/repeat-str.test.js index fc59d019e..a3fc1196c 100644 --- a/Sprint-3/2-practice-tdd/repeat-str.test.js +++ b/Sprint-3/2-practice-tdd/repeat-str.test.js @@ -1,13 +1,13 @@ // Implement a function repeatStr const repeatStr = require("./repeat-str"); -// Given a target string str and a positive integer count, +// Given a target string `str` and a positive integer `count`, // When the repeatStr function is called with these inputs, // Then it should: -// case: repeat String: -// Given a target string str and a positive integer count, +// Case: handle multiple repetitions: +// Given a target string `str` and a positive integer `count` greater than 1, // When the repeatStr function is called with these inputs, -// Then it should repeat the str count times and return a new string containing the repeated str values. +// Then it should return a string that contains the original `str` repeated `count` times. test("should repeat the string count times", () => { const str = "hello"; @@ -16,17 +16,17 @@ test("should repeat the string count times", () => { expect(repeatedStr).toEqual("hellohellohello"); }); -// case: handle Count of 1: -// Given a target string str and a count equal to 1, +// Case: handle count of 1: +// Given a target string `str` and a `count` equal to 1, // When the repeatStr function is called with these inputs, -// Then it should return the original str without repetition, ensuring that a count of 1 results in no repetition. +// Then it should return the original `str` without repetition. -// case: Handle Count of 0: -// Given a target string str and a count equal to 0, +// Case: Handle count of 0: +// Given a target string `str` and a `count` equal to 0, // When the repeatStr function is called with these inputs, -// Then it should return an empty string, ensuring that a count of 0 results in an empty output. +// Then it should return an empty string. -// case: Negative Count: -// Given a target string str and a negative integer count, +// Case: Handle negative count: +// Given a target string `str` and a negative integer `count`, // When the repeatStr function is called with these inputs, -// Then it should throw an error or return an appropriate error message, as negative counts are not valid. +// Then it should throw an error, as negative counts are not valid.