Reports & Tests

The Course
•
5 min read

A report answers a question about data. Our report needs total planned minutes, completed minutes, and remaining minutes. Decide what each number means before naming the variables. A session marked done contributes its whole duration; partial completion is outside this project’s model.

Use a function returning an object of totals. A second function can turn that object into text. This separation lets a webpage display the same numbers without parsing a formatted string. Test the numbers directly, including an empty plan.

When a result is wrong, inspect the input and each stage. Did filtering select the right records? Did the sum start at zero? Did a property name match the records? More clever syntax will not repair an unclear definition.

Predict before running

Only one session is completed. Which minutes count toward planned, and which toward completed?

Predict, then run JavaScript
function summarize(sessions) {
  let planned = 0;
  let completed = 0;

  for (const session of sessions) {
    planned = planned + session.minutes;

    if (session.done) {
      completed = completed + session.minutes;
    }
  }

  return { planned, completed, remaining: planned - completed };
}

const summary = summarize([
  { topic: "Loops", minutes: 25, done: true },
  { topic: "Arrays", minutes: 20, done: false }
]);

console.log(summary.planned);
console.log(summary.completed);
console.log(summary.remaining);
Check your prediction
45
25
20

Trace each statement in order. If your prediction differed, describe the first place your mental model and the result diverged before trying again.

Make report rules explicit

A report depends on definitions. Does total mean all planned minutes, only completed minutes, or minutes still remaining? Choose names that make this visible. Empty input is not an exceptional situation: a new learner has no records yet. Avoid dividing by zero when calculating an average.

Make report rules explicit JavaScript
function averageMinutes(sessions) {
  if (sessions.length === 0) {
    return 0;
  }

  let total = 0;

  for (const session of sessions) {
    total += session.minutes;
  }

  return total / sessions.length;
}

console.log(averageMinutes([]));
console.log(averageMinutes([{ minutes: 20 }, { minutes: 40 }]));
Check your prediction
0
30

Returning zero for an empty average is a product decision here. Another application might return null and display “No sessions yet”. Document whichever contract you choose.

Calculate each total from its stated rule

A report derives values from source records; it should not change those records while adding them up. Planned minutes include every session. Completed minutes include only sessions marked done. Remaining minutes are their difference, not the number of unfinished records.

Use a tiny fixture where the units are obvious. Two sessions lasting 10 and 40 minutes are useful because confusing a count with a duration produces visibly different answers. Include an empty list and an all-complete list before trusting a report on realistic data.

A count is not a duration JavaScript
const sessions = [
  { minutes: 10, done: true },
  { minutes: 40, done: false }
];
let count = 0;
let minutes = 0;

for (const session of sessions) {
  if (session.done) {
    count += 1;
    minutes += session.minutes;
  }
}

console.log(count);
console.log(minutes);
Check your prediction
1
10

The same condition updates both accumulators, but their units differ. A variable named completed without a unit can hide this distinction. Prefer completedCount or completedMinutes.

All complete means zero remaining JavaScript
const sessions = [
  { minutes: 10, done: true },
  { minutes: 20, done: true }
];
let planned = 0;
let completed = 0;

for (const session of sessions) {
  planned += session.minutes;

  if (session.done) {
    completed += session.minutes;
  }
}

console.log(planned - completed);
console.log(sessions[0].done);
Check your prediction
0
true

A legitimate zero result must not be replaced with a default message by truthiness. The source flag is still true because calculating the report did not need to mutate it.

Try the rule on new inputs

Check empty, none-done, some-done and all-done plans. Choose different durations so a count-based mistake cannot accidentally pass. Explain why toggling a session should change completed minutes but not planned minutes.

Your turn

Exercise

Reports & Tests: practise

Write completedMinutes(sessions). Test an empty array, an incomplete 20-minute session, and a completed 25-minute session. Return 0, 0, and 25 respectively.

Your working space JavaScript
// Write completedMinutes, then throw an error if any expectation fails.
Hint 1: the idea

Only add a record’s minutes when its done field is true.

Hint 2: a first step

Begin with zero before the loop. Compare three calls with !== and throw on a mismatch.

Show one solution
function completedMinutes(sessions) {
  let total = 0;

  for (const session of sessions) {
    if (session.done) {
      total = total + session.minutes;
    }
  }

  return total;
}

if (completedMinutes([]) !== 0) {
  throw new Error("empty");
}

if (completedMinutes([{ minutes: 20, done: false }]) !== 0) {
  throw new Error("incomplete");
}

if (completedMinutes([{ minutes: 25, done: true }]) !== 25) {
  throw new Error("completed");
}

console.log("3 checks passed");
3 checks passed

Try deleting the if condition: the incomplete case should catch the defect. You now have a small safety net for changing the implementation.

What you should be able to explain

  • What does each number in your report mean?
  • What should an empty report display?
  • Does your reporting function change any records?

Was this article helpful?

Your response is saved on this device.