go
go copied to clipboard
Lack of Information in Learning Exercise Elon's Toys
Couple if issues with this exercise:
-
Introduction section doesn't have an example for returning a pointer to struct
-
NewCar Function:
- Tests are not using it.
- Instructions don't have any information on it, even though it's being used in the code block of all tasks.
-
All methods have to be implemented to see the result of the test suite. This may be the only exercise where this happens. In other exercises, test suite results can be seen by implementing one function.
-
Task 1 hint: "Add a field to the Car struct that keeps track of the driven distance" is not needed because the
distance
field is already present in the Car struct. (It's not possible to editcar.go
. Can you confirm if you're able to do so, and this is my browser's issue?) -
Task 2 code block output should be
// Output: "Driven 5 meters"
.This is inconsistent with the code block for task 1 which does the same thing - initializes
NewCar(speed, batteryDrain)
with values 5 and 2 respectively - and outputs// car is now Car{speed: 5, batteryDrain: 2, battery: 98, distance: 5}
Near the beginning of the instructions, its stated that:
Cars start with full (100%) batteries. Each time you drive the car using the remote control, it covers the car's speed in meters and decreases the remaining battery percentage by its battery drain.
If a car's battery is below its battery drain percentage, you can't drive the car anymore.
This means that the hint should also be "Compute the value of the distance field" instead of "Show the value of the distance field"
-
Similarly in task 3, the code block output should be
// Output: "Battery at 98%"
-
Additional hints for task 4, similar to
need-for-speed
learning exercise's task 4 hints (bullet points 3 and 4)
I've come up with test suite:
package elon
import "testing"
func TestDrive(t *testing.T) {
var tests = []struct{
description string
initCar *Car
want Car
}{
{
description: "battery fully charged",
initCar: NewCar(5, 2),
want: Car{speed: 5, batteryDrain: 2, battery: 98, distance: 5},
},
{
description: "more than half battery used", // used car can finish race
initCar: &Car{speed: 5, batteryDrain: 7, battery: 46, distance: 0},
want: Car{speed: 5, batteryDrain: 7, battery: 39, distance: 5},
},
{
description: "not enough battery in used car to finish race",
initCar: &Car{speed: 5, batteryDrain: 7, battery: 3, distance: 0},
want: Car{speed: 5, batteryDrain: 7, battery: 3, distance: 0},
},
{
description: "impossible to finish race with single battery capacity",
initCar: NewCar(5, 102),
want: Car{speed: 5, batteryDrain: 102, battery: 100, distance: 0},
},
}
for _, test := range tests {
t.Logf("Testing for %s", test.description)
car := test.initCar
if car.drive() != test.want {
t.Errorf("Incorrect metrics ... got %v, want %v", car.drive(), test.want)
}
}
}
func TestDisplayDistance(t *testing.T) {
var tests = []struct{
description string
initCar *Car
want string
}{
{"can be driven", NewCar(10, 4), "Driven 10 meters"},
{"can't be driven", NewCar(2, 102), "Driven 0 meters"},
}
for _, test := range tests {
t.Logf("Testing car %s", test.description)
car := test.initCar
if car.displayDistance() != test.want {
t.Errorf("Incorrect display result ... got %v, want %v", car.displayDistance(), test.want)
}
}
}
func TestDisplayBatteryPercentage(t *testing.T) {
var tests = []struct{
description string
initCar *Car
want string
}{
{"new car", NewCar(5, 2), "Battery at 98"},
{"used car", &Car{speed: 10, batteryDrain: 2, battery: 32}, "Battery at 30"},
}
for _, test := range tests {
t.Logf("Testing battery percentage in %s", test.description)
car := test.initCar
if car.displayBatteryPercentage() != test.want {
t.Errorf("Incorrect battery info ... got %v, want %v", car.displayBatteryPercentage(), test.want)
}
}
}
func TestCanFinish(t *testing.T) {
var tests = []struct{
description string
initCar *Car
want bool
}{
{"can cover", NewCar(5, 2), true},
{"can't cover", &Car{speed: 5, batteryDrain: 2, battery: 35}, false},
}
for _, test := range tests {
t.Logf("Testing car %s race distance", test.description)
car := test.initCar
if car.canFinish(100) != test.want {
t.Errorf("Incorrect display result ... got %v, want %v", car.displayDistance(), test.want)
}
}
}
with implementation:
package elon
import "fmt"
type Car struct {
battery int
batteryDrain int
speed int
distance int
}
func NewCar(speed, batteryDrain int) *Car {
return &Car{
battery: 100,
batteryDrain: batteryDrain,
speed: speed,
distance: 0,
}
}
func (car Car) drive() Car {
if car.battery - car.batteryDrain < 0 {
return car
} else {
return Car{
battery: car.battery - car.batteryDrain,
speed: car.speed,
distance: car.distance + car.speed,
batteryDrain: car.batteryDrain,
}
}
}
func (car Car) displayDistance() string {
if car.battery - car.batteryDrain < 0 {
return fmt.Sprintf("Driven %d meters", car.distance)
} else {
return fmt.Sprintf("Driven %d meters", car.speed)
}
}
func (car Car) displayBatteryPercentage() string {
if car.battery - car.batteryDrain < 0 {
return fmt.Sprintf("Battery at %d", car.battery)
} else {
return fmt.Sprintf("Battery at %d", car.battery - car.batteryDrain)
}
}
func (car Car) canFinish(trackDistance int) bool {
driveLimit := car.battery / car.batteryDrain
carDistance := car.speed * driveLimit
return trackDistance <= carDistance
}
and ran these successfully on my local computer.
@junedev Please let me know your thoughts. If possible, I would like to work with @eklatzer on this issue for the "Pair Extraordinaire" badge.
Here my thoughts on the points you mentioned:
-
Introduction section doesn't have an example for returning a pointer to struct
This is taught in the exercise about pointers (election day) which is a prerequisite for working on Elons Toys. We cannot repeat all the things already taught earlier in the introduction.
-
NewCar Function: Tests are not using it.
True, but it is stated that the exercise is following up with Need for Speed where the NewCar function originates from. I don't think it does any harm to keep it. The tests cannot use it, as they need to modify other parameters then the ones in the constructor to test certain edge cases. Before this issue, I haven't heard from students being confused about this part.
-
NewCar Function: Instructions don't have any information on it, even though it's being used in the code block of all tasks.
Same as point one, NewCar is written by the student in Need for Speed which is a prerequisite for this exercise.
-
All methods have to be implemented to see the result of the test suite. This may be the only exercise where this happens. In other exercises, test suite results can be seen by implementing one function.
As this exercise is about writing methods, it does not make sense to provide stubs. Not seeing test results until there are at least some stubs written by the student is rooted in Go being a compiled language. It is not trivial to do something about it and not clear whether we should. You can find the discussion here: https://github.com/exercism/go-test-runner/issues/47
-
Task 2 code block output should be
// Output: "Driven 5 meters"
The code block sets up a fresh car on which the Drive method was never called. Why should it have a distance of 5? Same logic applies for task 3. In task 1, the Drive method was called. That is why the values are different there.
As you can see, I don't think any action is needed on the points above.
Feel free to create a PR to improve the hints file. I find it hard to give feedback to those ideas based on the text above but it sounds like there is really room for improvement.
Sidenote: In general, I am also not a big fan of the exercise overall. Not for the reasons above but more because it is strange that there is Need for Speed and then this variation on it. Also there are so many edge cases with how to interpret the battery, distance etc and the calculation also causes problems. I think a completely new exercise without those shortcomings with a different theme would be better. But that is a separate issue for another time.
TLDR
- [ ] The todo here is to double check/improve the hints.md file for Elon's toys.
As for the tests and code you posted, from those code blocks, I cannot see what you changed and why. What are you trying to illustrate with those? The points you mentioned were mostly not about tests and example solution so I don't understand how those relate.
This issue is for holistically improving this learning exercise, that's why it has suggestions for description and a test suite. I would have created PRs based on eventual suggestions.
As per your feedback for the first three points - about introduction, NewCar Function: Tests, and NewCar Function: Instructions, everything's covered as they can be implied, referred, etc. There's no point in adding additional hints.
Completely agree with your feedback on other points.
- [ ] The todo here is to double check/improve the hints.md file for Elon's toys.
@junedev
I reviewed the Elon's toys exercise and have the following recommendations to change hints.md
Remove line 5: “- Add a field to the Car
struct that keeps track of the driven distance”
- As noted earlier in this issue, distance already exists in the provided
car.go
file.
Remove line 20: - Remember the car has a [method][method] to retrieve the distance it has driven
- This hint is confusing since the referenced
DisplayDistance()
method returns a string which isn’t helpful inCanFinish()
- The test cases pass regardless of
distance
presence in the calculation. - The test cases don’t initialize
distance
, so always evaluate the zero value. - Enhancing the test cases to make
distance
relevant probably doesn’t improve the learning outcomes related to methods
Students have become accustomed to TDD-like test cases, which are progressively passed as they solve each exercise step. This exercise won’t compile and, therefore, can’t be tested until all of the required methods are appropriately declared. Should we consider additional advice to indicate that students can have the TDD-like experience if they first stub out the methods and then implement the logic for those methods?
@W8CYE Your suggestions sound very reasonable to me. Can you create a PR?
Regarding
Students have become accustomed to TDD-like test cases, which are progressively passed as they solve each exercise step. This exercise won’t compile and, therefore, can’t be tested until all of the required methods are appropriately declared. Should we consider additional advice to indicate that students can have the TDD-like experience if they first stub out the methods and then implement the logic for those methods?
This is a great point that was mentioned before and we have not yet tackled. (See also https://github.com/exercism/go-test-runner/issues/47 for a discussion of potential technical solutions.) My take on this is that somewhere in the beginning of the track we should have some kind of concept that explains what it means to work in a compiled language. We have many students coming from interpreted languages that struggle with this a lot. Once we have this new place, we could discuss the different strategies how to deal with the "tests not compiling" situation. I will create a separate issue for this.
In general though, learning exercises are not meant to be solved TDD style like practice exercises. The test cases don't really build on each other. But then again, that is another open issue to better communicate the difference between practice and learning exercises to students.