389379 (2) [Avatar] Offline
#1
Some formatting issues aside, I think I have it:

https://github.com/simplycycling/ticket_to_mars/blob/master/ticket_to_mars.go

Disclosure - I did glance at the answer in the back of the book, but only at the const that determined what spaceline was being used. And it turned out that I had done it correctly, myself, except I hadn't set an empty variable as a global scope (am I saying that right?), so I don't feel like I cheated, per se. And I'll be looking again to see how Nathan managed the formatting, because I just can't wrap my head around that.

146518 (1) [Avatar] Offline
#2
tickets.go
[ 1 KB ]
Solution to first challenge.
386193 (3) [Avatar] Offline
#3
How do I test functions which use rand.Intn?
I'm a new Gopher and I'm working through "Learn Go" by Nathan Youngman and trying to TDD the exercises to learn how to write testable Go code.

I have a function to return a random spaceline from a string array.

https://play.golang.org/p/g5JnrIFyjo

In Go, how do I test functions which depend on random numbers? And, yes, I know that "math/rand" isn't truly random.

Is it as simple as setting a seed right before I run my test, e.g. rand.Seed(1)? Do I set rand.Seed(1) at the top of the _test.go file, or at the beginning of each unit test?

Also, am I seeding math.rand correctly in the Init() function? Will seeding it in the Init() function override any seeding I do in my tests?

My only other thought is to create an interface somehow to mock rand.Intn(), but this seems like overkill and I don't know enough about interfaces to know if this is inadvisable.
Nathan Youngman (42) [Avatar] Offline
#4
389379 wrote:Some formatting issues aside, I think I have it:

https://github.com/simplycycling/ticket_to_mars/blob/master/ticket_to_mars.go

Disclosure - I did glance at the answer in the back of the book, but only at the const that determined what spaceline was being used. And it turned out that I had done it correctly, myself, except I hadn't set an empty variable as a global scope (am I saying that right?), so I don't feel like I cheated, per se. And I'll be looking again to see how Nathan managed the formatting, because I just can't wrap my head around that.



It looks good. The empty variable in your listing is in function scope.

The scope for variables outside of functions is the package scope. That's as global as it gets in Go, there isn't a truly global scope.

Thanks for the feedback on formatting. Maybe I can improve the coverage of fmt.Printf in some way? Any suggestions as to why you found it difficult to wrap your head around?
Nathan Youngman (42) [Avatar] Offline
#5
146518 wrote:Solution to first challenge.


Nice solution. It looks like you already know Go well.

A few tips.

You can declare a group constants by using parenthesis like an import statement. That will save you from repeating
const
again and again for 5 lines.

Identifiers such as variable names and constants are named with camelCase in Go rather than snake_case as used in Ruby or Python. It's just a convention, but if you run a lint tool it will complain, and most teams will use golint (it's much like the PEP8 style guide for Python).

In
getSpaceLine()
there is no need to assign and then evaluate n, since you're not using it in any of the case statements. So you can simplify that line to
switch rand.Intn(3) {
.

Maybe you can come up with a better name for
inRange
? That doesn't fully describe what it provides or does.

Maybe
printLine
could also be a method on Ticket? Or something along those lines...the Stringer interface is covered later in the book.

Cheers, Nathan.
Nathan Youngman (42) [Avatar] Offline
#6
How do I test functions which use rand.Intn?
386193 wrote:I'm a new Gopher and I'm working through "Learn Go" by Nathan Youngman and trying to TDD the exercises to learn how to write testable Go code.

I have a function to return a random spaceline from a string array.

https://play.golang.org/p/g5JnrIFyjo

In Go, how do I test functions which depend on random numbers? And, yes, I know that "math/rand" isn't truly random.

Is it as simple as setting a seed right before I run my test, e.g. rand.Seed(1)? Do I set rand.Seed(1) at the top of the _test.go file, or at the beginning of each unit test?

Also, am I seeding math.rand correctly in the Init() function? Will seeding it in the Init() function override any seeding I do in my tests?

My only other thought is to create an interface somehow to mock rand.Intn(), but this seems like overkill and I don't know enough about interfaces to know if this is inadvisable.


That's a great question. I haven't written tests for this one yet.

Seeding with a fixed value when running the test could work. Did you try it out? The init() function is called before main(), so if you seed within a Test* function, that should override any seed from init().

Another option would be to pass a random number into a function, and then test the function without the randomness. As in, only test functions with known values, and do the random number generation as part of main, leaving that (sadly) untested.

When mocking, it's best to mock things you own (something you wrote within the same package). You could write your own random() function that simply calls rand.Intn. Then use first-class functions to pass in that random() function or a "mock" function that returns the specific integer. If you want to use rand.Intn directly as a first-class function without wrapping it, that would also work. Since it's std library, it's not going to change on you.

Alternatively you could define some types and an interface that they satisfy to do the same thing, but that feels a little heavy for mocking a single function.
386193 (3) [Avatar] Offline
#7
How do I test functions which use rand.Intn?
Here's what I came up with:

func TestRandSpaceline(t *testing.T) {
	// seed random number generator with known seed to produce consistent results
	rand.Seed(1)
	got := randSpaceline()
	want := "Virgin Galactic"

	if got != want {
		t.Errorf("randSpaceline() = %v; want %v", got, want)
	}

}


func randSpaceline() string {
	spacelines := []string{
		"SpaceX",
		"Space Adventures",
		"Virgin Galactic",
	}

	n := rand.Intn(len(spacelines))
	return spacelines[n]
}
386193 (3) [Avatar] Offline
#8
Nathan,

I also came up with this solution which injects an instance of rand.Rand into the randSpacelines() function. Is this better than using the global rand.Intn()?

func randSpaceline(r *rand.Rand) string {
	spacelines := []string{
		"SpaceX",
		"Space Adventures",
		"Virgin Galactic",
	}

	n := r.Intn(len(spacelines))
	return spacelines[n]
}


func TestRandSpaceline(t *testing.T) {
	// seed random number generator with known seed to produce consistent results
	s := rand.NewSource(1)
	rand := rand.New(s)
	got := randSpaceline(rand)
	want := "Virgin Galactic"

	if got != want {
		t.Errorf("randSpaceline() = %v; want %v", got, want)
	}

}

Nathan Youngman (42) [Avatar] Offline
#9
386193 wrote:Nathan,
I also came up with this solution which injects an instance of rand.Rand into the randSpacelines() function. Is this better than using the global rand.Intn()?


Interesting. I don't think it's any better than using the rand.Intn() function. Your first example is simpler and it works.
422917 (1) [Avatar] Offline
#10
So much for my initial solution:
https://play.golang.org/p/HjiqkDnuCA

Feel free to point out any mistakes.
461364 (1) [Avatar] Offline
#11
Tickets to Mars challenge
tickets.go
[ 2 KB ]
Here's how I solved the challenge