Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new chapter: testing #288

Merged
merged 18 commits into from
Nov 28, 2016
Merged

new chapter: testing #288

merged 18 commits into from
Nov 28, 2016

Conversation

steveklabnik
Copy link
Member

I took some content from the old testing (and documentation) chapters, but also did a lot of re-writing/editing. Whatcha think @carols10cents ?

@steveklabnik
Copy link
Member Author

(Added to https://github.com/rust-lang/book/projects/1 as well)

@carols10cents carols10cents self-assigned this Oct 7, 2016
Rust is a programming language that cares a lot about correctness. But
correctness is a complex topic, and isn't exactly easy to get right. Rust
places a lot of weight on its type system to help ensure that our programs do
what we intend, but it cannot help with everything. As such, Rust also includes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Namely, Rust can't figure out that our logic is doing what the users of our software need it to do, so testing those cases is still very important! This tweet feels relevant: https://twitter.com/WAWilsonIV/status/784891724866785280

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this tweet has since been deleted

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how to concretely improve this here, so if you have suggestions I'm up for it 😄

Copy link
Member

@carols10cents carols10cents Nov 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ugggghhhhhhh why didn't i copy paste the tweet....

I think I'd provide an example here, something like:

We can write a function called add_one, and it can have a signature of accepting an integer as an argument and returning an integer. We can implement that function, and Rust can do all the type checking and borrow checking that we've seen it's capable of doing. What Rust can't check for us is that we've implemented this function as arg + 1 and not arg + 10 or arg - 50! That's where tests come in: we can write a test that passes 3 to our function and checks that we get 4 back, and we can run this test whenever we make changes to our code to make sure we didn't break anything that we knew was previously working.

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
```

## Inverting failure with `should_panic`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we show an example of a test that asserts something about an actual function, maybe with assert_eq!, before we introduce should_panic?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So to me, the idea of the structure was "show how tests work, then show all the different kinds of assertions." This would mix that up a bit. Maybe that's just a bad organization though?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I think maybe +1 @carols10cents in the sense of "show me how to do testing, then show me what's going on" would be my pref.


## Testing equality

Rust provides a pair of macros, `assert_eq!` and `assert_ne!`, that compares
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah basically I think the "Testing equality" and "Inverting failure with should_panic" sections should be swapped: i test for equality waaaayyy more than i use should_panic.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here i find my past self disagreeing with my present self. sigh.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for swapping

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here i find my past self disagreeing with my present self. sigh.

what does your present self think?

fn it_works() {
assert_eq!(4, add_two(2));
}
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we show what a failure message for assert_eq! looks like, and show that it doesn't matter which order the arguments go in, really?


Because `common.rs` is in our `tests` directory, Cargo is also compiling it as
its own crate. Because `common.rs` is so simple, we didn't get an error, but
with more complex code, this might not work. So what can we do?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait but... if we have a problem in common/mod.rs, cargo test will still fail, right? Why is this better exactly? Is this a thing that has confused people?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something we get regular questions about in #rust and #rust-beginners, yeah.

The key is stuff like extern crate, if it's interpreted as its own crate, then it won't have stuff from extern crate foo in it, but if it's just a submodule, it will have it from the actual parent crate. make sense?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this section doesn't say anything about extern crate anywhere, just that you'll get 4 sections of test output instead of 3 if you use common.rs instead of mod/common.rs... I guess I'm not seeing what this text is saying might not work where.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I will clarify it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still confused about integration test files and crates and the relationship thereof :(

The beginning of the integration test section says:

If you make [a tests directory], and put .rs files inside, Cargo will compile each of [the .rs files] as an individual crate.

and:

We now have an extern crate adder at the top. This is because each test in the tests directory is
an entirely separate crate, and so we need to import our library.

I read this as "each file in tests is compiled into a crate that is independent of each of the other files in tests.

However, in this section, we're demonstrating that if you extract a module into tests/common.rs, because that's a .rs file in the tests directory too, common will be compiled into a crate and get its own section in the tests output.

But we were able to use common::helper() in tests/integration_test.rs without having to say extern crate helper, so does that mean the separate crates in tests are compiled in... like... the same context? Like they all have each other imported as extern crate would already? I'm going to research this too but wanted to confirm with you....

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok jake has helped me to understand that it's compiling tests/common.rs as a separate crate AND as a module of tests/integration_test.rs because of the mod common..... I'm still struggling with this because we say it's a problem but we don't demonstrate any problems.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found one example of someone doing this in IRC, but the problem is slightly different than the problem presented here: https://botbot.me/mozilla/rust/2016-03-02/?msg=61394193&page=20

They want to make one tests/lib.rs and then include other test files as mods in there, as well as have helper mods declared in tests/lib.rs and usable in the other test file mods, rather than what's presented here, which is having multiple top-level crates that could all havemod common and work just fine....


In this scenario, we have a non-`pub` function, `internal_adder`. Because tests
are just Rust code, and the `tests` module is just another module, we can
import and call `internal_adder` in a test just fine.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wdyt about showing how you can run just one test by specifying the test name to cargo test?

examples that don't actually work, because the code has changed since the
documentation has been written. To this end, Rust supports automatically
running examples in your documentation for library crates. Here's a fleshed-out
`src/lib.rs` with examples:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We haven't actually talked about doc comments yet, if this chapter is going to come before the "creating a library" chapter.... could we move this section to the doc comments section in that chapter and reference this chapter?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we had it in http://rust-lang.github.io/book/ch03-04-comments.html. This kind of split is something that always confuses some people, they look for advice about testing in the testing chapter, not the documentation chapter. But some people look in the documentation chapter...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cut doc comments from 03-04 since they only really make sense if you're creating a library and using cargo doc.

@carols10cents
Copy link
Member

also travis caught some /home/steves you've got in there <3 <3 <3

@steveklabnik
Copy link
Member Author

also travis caught some /home/steves you've got in there <3 <3 <3

Yay travis! I tried real hard to not let it happen, ugh. This is why we lint 🎊

@steveklabnik
Copy link
Member Author

Okay, resolved everything except this ordering bit.

trotter added a commit to trotter/rust that referenced this pull request Nov 10, 2016
The narrative flows better if we follow what @steveklabnik is doing in
rust-lang/book#288. Therefore, I completely copied it.
## The `assert!` macro

So why does our do-nothing test pass? Any test which doesn't `panic!` passes,
and any test that does `panic!` fails. Let's make our test fail:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we remind them where we originally talked about panic!?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with it, but am not sure we need to mention this stuff every time. @carols10cents ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like with the amount we talk about panic in the error handling chapter, we shouldn't have to mention it again here. It'll be in the table of contents too.

have to follow the usual visibility rules. Because we're in an inner module,
we need to bring our test function into scope. This can be annoying if you have
a large module, and so this is a common use of globs. Let's change our
`src/lib.rs` to make use of it:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we talk about needing to use use like this in a different chapter, like the one talking about modules? It seems that while this is review it's almost like we're talking about it for the first time.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, do you have any ideas on how to make it feel less first-timey?

@sophiajt
Copy link

Yeah, seems like the ordering bit, and the minor points of confusions, are the remaining tasks. As a whole this feels like it's already pretty far along and just needs the polish to wrap it up.

@steveklabnik
Copy link
Member Author

Got a PR with some stuff that i want to make sure is included here too rust-lang/rust#37766 (review)

@steveklabnik
Copy link
Member Author

Fixed some nits and added a tiny bit at the end, @carols10cents , take it away 😄

@carols10cents carols10cents self-assigned this Nov 14, 2016
@carols10cents carols10cents dismissed their stale review November 14, 2016 21:16

in my court now

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
```

#### Submodules in integration tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still not sure about this section. My force push ate my comments tho, luckily I had them in another window:

I'm still confused about integration test files and crates and the relationship thereof :(

The beginning of the integration test section says:

If you make [a tests directory], and put .rs files inside, Cargo will compile each of [the .rs files] as an individual crate.

and:

We now have an extern crate adder at the top. This is because each test in the tests directory is
an entirely separate crate, and so we need to import our library.

I read this as "each file in tests is compiled into a crate that is independent of each of the other files in tests.

However, in this section, we're demonstrating that if you extract a module into tests/common.rs, because that's a .rs file in the tests directory too, common will be compiled into a crate and get its own section in the tests output.

But we were able to use common::helper() in tests/integration_test.rs without having to say extern crate helper, so does that mean the separate crates in tests are compiled in... like... the same context? Like they all have each other imported as extern crate would already? I'm going to research this too but wanted to confirm with you....
@carols10cents
carols10cents 23 minutes ago collaborator

Ok jake has helped me to understand that it's compiling tests/common.rs as a separate crate AND as a module of tests/integration_test.rs because of the mod common..... I'm still struggling with this because we say it's a problem but we don't demonstrate any problems.
@carols10cents
carols10cents 17 minutes ago collaborator

I found one example of someone doing this in IRC, but the problem is slightly different than the problem presented here: https://botbot.me/mozilla/rust/2016-03-02/?msg=61394193&page=20

They want to make one tests/lib.rs and then include other test files as mods in there, as well as have helper mods declared in tests/lib.rs and usable in the other test file mods, rather than what's presented here, which is having multiple top-level crates that could all havemod common and work just fine....

And add a section about integration testing binary crates
@carols10cents
Copy link
Member

Ok, after thinking about this some more, I think the content about modules in the tests directory is valuable, but the example is muddying the point more than it's helping. So I've shortened that section.

In my research trying to find logs of people on IRC (in #rust only since #rust-beginners isn't logged, and I totally understand why it isn't) being in this situation and what they were doing, I actually found quite a few more people asking how to integration test binary crates, which isn't possible, so I decided to add a bit about what to do in that situation too.

So I think I'm done messing around with this and would love thoughts ❤️


```rust,ignore
// assert_eq
if left_val == right_val {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think assert_eq and assert_ne are mixed up here since they panic on the inverted == vs !=

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooooooh great catch!!! 👀 You're totally right!!!

@daschl
Copy link

daschl commented Nov 27, 2016

great work! One question for me came up when reading the "writing tests" chapter is that maybe a paragraph should clarify how that relates to the Eq trait and so forth - maybe show how to assert two custom structs?


test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured
```

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be also possible to expand this a little bit to explain how to run certain module tests? Since very often you want to run all the tests of a specific module/namespace.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be also possible to expand this a little bit to explain how to run certain module tests? Since very often you want to run all the tests of a specific module/namespace.

That's a great idea!! Will do!!

@carols10cents
Copy link
Member

One question for me came up when reading the "writing tests" chapter is that maybe a paragraph should clarify how that relates to the Eq trait and so forth - maybe show how to assert two custom structs?

Ooooh great point!!! I will work this in :) Thank you for reviewing this!!! ❤️

borrow checking that we've seen it's capable of doing. What Rust *can't* check
for us is that we've implemented this function to return the argument plus two
and not the argument plus 10 or the argument minus 50! That's where tests come
in: we can write tests that, for example, pass `3` to the `add_two` function

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might just want to write this as 'where tests come in. We can write tests that'
Having the : made the flow a bit weird. That or 'come in: we can write, for example' using tests as a word twice so close to each other trips the flow up.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

¯\_(ツ)_/¯ none of these versions bother me, so might as well change it :)

to our code to make sure we didn't change any existing behavior from what the
tests specify it should be.

Testing is a skill, and we cannot hope to cover everything about how to write

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comma after skill on this line is not needed. If it was a list then having the oxford comma would be good but this is just a conjunction of two sentences.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conjunctions of two sentences get commas, though. https://owl.english.purdue.edu/owl/resource/607/02

Copy link
Member Author

@steveklabnik steveklabnik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! One nit and one thing for the future...

didn't fail for a different reason than the one you were expecting. To help
with this, an optional `expected` parameter can be added to the `should_panic`
attribute. The test harness will make sure that the failure message contains
the provided text. A safer version of Listing 11-1 would be the following, in
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"safe" is probably a bad word here, given rust's focus on a different kind of safety. What about "more robust"?


```rust
#[test]
#[should_panic(expected = "do not lie on character boundary")]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(we should leave this for now, but it'll need fixed at some point)

@steveklabnik
Copy link
Member Author

:shipit:

@carols10cents carols10cents merged commit 9b14e37 into master Nov 28, 2016
@carols10cents carols10cents deleted the testing branch November 28, 2016 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants