-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
new chapter: testing #288
Conversation
(Added to https://github.com/rust-lang/book/projects/1 as well) |
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 😄
There was a problem hiding this comment.
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 asarg + 1
and notarg + 10
orarg - 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` |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 for swapping
There was a problem hiding this comment.
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)); | ||
} | ||
``` |
There was a problem hiding this comment.
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? |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 thetests
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....
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 mod
s in there, as well as have helper mod
s 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. |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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
.
also travis caught some |
Yay travis! I tried real hard to not let it happen, ugh. This is why we lint 🎊 |
Okay, resolved everything except this ordering bit. |
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: |
There was a problem hiding this comment.
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!
?
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
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. |
Got a PR with some stuff that i want to make sure is included here too rust-lang/rust#37766 (review) |
Fixed some nits and added a tiny bit at the end, @carols10cents , take it away 😄 |
that goes in the 'creating a library' section
Reorganize a bit, make some of the examples a bit more concrete
bb20eda
to
d1107ec
Compare
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured | ||
``` | ||
|
||
#### Submodules in integration tests |
There was a problem hiding this comment.
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
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 { |
There was a problem hiding this comment.
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 !=
There was a problem hiding this comment.
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!!!
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 | ||
``` | ||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!!
Ooooh great point!!! I will work this in :) Thank you for reviewing this!!! ❤️ |
Also make the pseudocode a bit more accurate
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this 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 |
There was a problem hiding this comment.
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")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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)
I took some content from the old testing (and documentation) chapters, but also did a lot of re-writing/editing. Whatcha think @carols10cents ?