Discussion:
Getting Scalatest to check multiple assertions, even if first fails?
Ken McDonald
2011-07-10 19:42:47 UTC
Permalink
As an example of some testing I'm doing, here's a fragment from a scalatest
FunSuite:

test("nextLeaf correctness.") {
val tree = new Line -> (
new Line -> (
"a",
new Line -> ("b", "c"),
new Line -> ("d"),
"e"
)
)
def leaf(s: String): Leaf = tree.findLeaves(_.text==s)(0)
assert(leaf("a").nextLeaf(1, Forward).get.text === "b")
assert(leaf("b").nextLeaf(1, Forward).get.text === "c")
assert(leaf("c").nextLeaf(1, Forward).get.text === "d")
}

At the moment, the first "assert" is failing; which means the next two don't
even get checked. Is there a way to have all asserts in a test checked, even
if one fails?

Thanks,
Ken
Bill Venners
2011-07-10 20:00:23 UTC
Permalink
Hi Ken,

Can I ask what you really want that motivates this question? Is it
that you want to see all failures at once to save you time fixing a
bug, if a bug arises? If so, how would that save you time? Is it that
the result of the others would help you understand the bug and how to
fix it?

To answer your question:

With assert you can && boolean expressions together, but this doesn't
work with ===, so you wouldn't get the failure message you are likely
looking for. Perhaps it should work with ===, but perhaps not.

If you use matcher expressions instead, you can and them together. I
don't short circuit in that case, so all will be executed, but I do
short circuit the error message to reduce clutter. In this case,
sounds like you want all the info, so that still wouldn't help. I have
been trying to gather user input on when and why people might want
more details, so this is an interesting question you've posed.

The way you can do this today is to put one assertion in each test. (I
think this is what many TDD/BDD folks would encourage you to do
anyway.)

Bill
Post by Ken McDonald
As an example of some testing I'm doing, here's a fragment from a scalatest
test("nextLeaf correctness.") {
val tree = new Line -> (
new Line -> (
"a",
new Line -> ("b", "c"),
new Line -> ("d"),
"e"
)
)
def leaf(s: String): Leaf = tree.findLeaves(_.text==s)(0)
assert(leaf("a").nextLeaf(1, Forward).get.text === "b")
assert(leaf("b").nextLeaf(1, Forward).get.text === "c")
assert(leaf("c").nextLeaf(1, Forward).get.text === "d")
}
At the moment, the first "assert" is failing; which means the next two don't
even get checked. Is there a way to have all asserts in a test checked, even
if one fails?
Thanks,
Ken
Ken McDonald
2011-07-10 20:39:22 UTC
Permalink
Hi Bill, thanks for getting back to me so quickly.

I've always done things in this manner, and found it useful. I group
multiple assertions into the same test for a couple of reasons:

1) When they all test slightly different aspects of the "same thing". For
example, in my original post, all of the assertions are testing navigation
in a tree structure, and each assertion exercises (if I've don't my job
right) a different condition in the code that travels the links between tree
nodes.

2) They all test different behavior on the same object; in this case, having
all of the assertions together makes it easy to see that all behaviors on
that object have been tested.

3) I more often than not try to make my tests "functional", in the standard
sense; there is no state change in the objects being tested. This means
multiple assertions can be meaningfully run in the same test, since an error
reported by one isn't caused by something that will cascade to other
assertions.

4) And yes, your point about having multiple assertions in a single test aid
in diagnosis is something I've certainly found to be true. Going back to my
original posting (where there will probably be a total of about eight
assertions), if say two failed and the others succeeded, that often lets me
construct in my head exactly the code path that must have caused the
problem.

I certainly could pull out local objects into fixtures and use only one
assertion per test, but that really expands out the code while (IMHO) making
it less readable because I cannot write closely related assertions adjacent
to one another, nor can see them right next to the data structure they are
testing on. Plus, of course, if it's easier to write more actual test code,
it's likely more of it will be written.


Cheers,
Ken
Bill Venners
2011-07-10 21:56:56 UTC
Permalink
Hi Ken,

Thanks for the detailed response. I have been considering adding a
verbose message alternative in TestFailedException, which would give
extra information if it exists. Your response adds momentum to that. I
will also consider allowing triple-equals expressions to be logically
combined, such as with &&, so it could be taken advantage of by
writing assertion as well as matcher expressions. I'd also provide a
way to indicate you want the verbose message copied to the regular
message, so it shows up in the results, as well as see if IDEs can
give you the option to see it either way.

One other place this has shown up is in table-driven property checks.
Users might want to see just the first row that failed, or might want
to see how the entire table did. This could be done via the same
mechanism.

For now, though, I think you're mostly stuck with seeing one failure
at a time, or splitting out your assertions into individual tests. I
think you could minimize the scope of the shared data with a block,
like this:

{ // Just open a block in the FunSuite constructor you're writing

val tree = new Line -> (
new Line -> (
"a",
new Line -> ("b", "c"),
new Line -> ("d"),
"e"
)
)

def leaf(s: String): Leaf = tree.findLeaves(_.text==s)(0)

// And define the tests inside the block
test("nextLeaf finds the next leaf inside a new line") {
assert(leaf("a").nextLeaf(1, Forward).get.text === "b")
}
test("nextLeaf finds the next leaf inside the same line") {
assert(leaf("b").nextLeaf(1, Forward).get.text === "c")
}
test("nextLeaf finds the next leaf inside a parent line") {
assert(leaf("c").nextLeaf(1, Forward).get.text === "d")
}
} // Close the block and continue with more tests below...

By the way I'd say that although doing it this way is a bit more
verbose, I think it does make more clear the three different scenarios
you were writing assertions for.

Bill
Post by Ken McDonald
Hi Bill, thanks for getting back to me so quickly.
I've always done things in this manner, and found it useful. I group
1) When they all test slightly different aspects of the "same thing". For
example, in my original post, all of the assertions are testing navigation
in a tree structure, and each assertion exercises (if I've don't my job
right) a different condition in the code that travels the links between tree
nodes.
2) They all test different behavior on the same object; in this case, having
all of the assertions together makes it easy to see that all behaviors on
that object have been tested.
3) I more often than not try to make my tests "functional", in the standard
sense; there is no state change in the objects being tested. This means
multiple assertions can be meaningfully run in the same test, since an error
reported by one isn't caused by something that will cascade to other
assertions.
4) And yes, your point about having multiple assertions in a single test aid
in diagnosis is something I've certainly found to be true. Going back to my
original posting (where there will probably be a total of about eight
assertions), if say two failed and the others succeeded, that often lets me
construct in my head exactly the code path that must have caused the
problem.
I certainly could pull out local objects into fixtures and use only one
assertion per test, but that really expands out the code while (IMHO) making
it less readable because I cannot write closely related assertions adjacent
to one another, nor can see them right next to the data structure they are
testing on. Plus, of course, if it's easier to write more actual test code,
it's likely more of it will be written.
Cheers,
Ken
--
Bill Venners
Artima, Inc.
http://www.artima.com
Ken McDonald
2011-07-10 22:49:58 UTC
Permalink
Thanks for looking into this, it's much appreciated.

By the way I'd say that although doing it this way [individual "test"s--Ken]
Post by Bill Venners
is a bit more
verbose, I think it does make more clear the three different scenarios
you were writing assertions for.
Part of the way I do things is habit--I started doing unit testing long
before BDD came along--but part of it is preference; unless what is being
tested is difficult to perceive, I prefer to simply see the logic, without
descriptions. I'm not sure why, maybe because that way there's no concern
about the description getting "out of synch" with the actual test.

Also, appended below is another set of examples (which mostly don't use
"===", so probably couldn't be fiddled anyway) which show a way I like to
group multiple asserts together. Basically, each assertion is simple to
read, and each grouping is logically consistent.

Cheers,
Ken

test("Special characters are escaped in char classes") {
assert(CharSet("[]-^&\\.{}")*>0 ~~= "[]-^&\\.{}")
assert(CharSet("-") ~~= "-")
}

test("CharSet functionality") {
assert(CharSet("abc")*>0 ~= "cde")
assert(CharSet("abc")*>0 !~~= "de")
assert(Chars.Any *> 0 ~~= "\na.,*")
assert(Chars.Any.pattern === "[\\s\\S]")
assert(Chars.Any.characters === "\\s\\S")
assert("[\\s\\S&&[\\p{ASCII}]]" === (Chars.Any /\ Chars.ASCII).pattern)
assert("[\\s\\S&&[^\\p{ASCII}]]" === (Chars.Any - Chars.ASCII).pattern)
}

test("CharSet negation") {
assert(!CharSet("abc")*>1 ~~= "def")
assert(!CharSet("abc")*>1 !~~= "dbf")
}

test("Char Class Intersection 1") {
val cc = CharSet("ab") /\ CharSet("bc")
assert(cc ~~= "b")
assert(cc !~~= "a")
assert(cc !~~= "c")
assert(cc !~~= "d")
}

test("Char Class Intersection 2") {
val cc = !CharSet("ab") /\ CharSet("bc")
assert(cc ~~= "c")
assert(cc !~~= "a")
assert(cc !~~= "b")
assert(cc !~~= "d")
}

test("Char class subtraction") {
assert((CharSet("abc") - CharSet("cde")) !~= "c")
assert((CharSet("abc") - "c") !~= "c")
}
Bill Venners
2011-07-10 23:42:17 UTC
Permalink
Hi Ken,
Post by Ken McDonald
Thanks for looking into this, it's much appreciated.
Post by Ken McDonald
By the way I'd say that although doing it this way [individual
"test"s--Ken] is a bit more
verbose, I think it does make more clear the three different scenarios
you were writing assertions for.
Part of the way I do things is habit--I started doing unit testing long
before BDD came along--but part of it is preference; unless what is being
tested is difficult to perceive, I prefer to simply see the logic, without
descriptions. I'm not sure why, maybe because that way there's no concern
about the description getting "out of synch" with the actual test.
Also, appended below is another set of examples (which mostly don't use
"===", so probably couldn't be fiddled anyway) which show a way I like to
group multiple asserts together. Basically, each assertion is simple to
read, and each grouping is logically consistent.
Cheers,
Ken
test("Special characters are escaped in char classes") {
assert(CharSet("[]-^&\\.{}")*>0 ~~= "[]-^&\\.{}")
assert(CharSet("-") ~~= "-")
}
test("CharSet functionality") {
assert(CharSet("abc")*>0 ~= "cde")
assert(CharSet("abc")*>0 !~~= "de")
assert(Chars.Any *> 0 ~~= "\na.,*")
assert(Chars.Any.pattern === "[\\s\\S]")
assert(Chars.Any.characters === "\\s\\S")
assert("[\\s\\S&&[\\p{ASCII}]]" === (Chars.Any /\ Chars.ASCII).pattern)
assert("[\\s\\S&&[^\\p{ASCII}]]" === (Chars.Any - Chars.ASCII).pattern)
}
test("CharSet negation") {
assert(!CharSet("abc")*>1 ~~= "def")
assert(!CharSet("abc")*>1 !~~= "dbf")
}
test("Char Class Intersection 1") {
val cc = CharSet("ab") /\ CharSet("bc")
assert(cc ~~= "b")
assert(cc !~~= "a")
assert(cc !~~= "c")
assert(cc !~~= "d")
}
test("Char Class Intersection 2") {
val cc = !CharSet("ab") /\ CharSet("bc")
assert(cc ~~= "c")
assert(cc !~~= "a")
assert(cc !~~= "b")
assert(cc !~~= "d")
}
test("Char class subtraction") {
assert((CharSet("abc") - CharSet("cde")) !~= "c")
assert((CharSet("abc") - "c") !~= "c")
}
These are concise and readable, except that I am not unsure I don't
not understand what !CharSet("ab") !~~= "dbf" would mean.

It almost seems like you have a one column table of expressions you
want to have checked. I wonder if something like this would serve your
needs best:

val cc = CharSet("ab") /\ CharSet("bc")
assertAll(
cc ~~= "b",
cc !~~= "a",
cc !~~= "c",
cc !~~= "d"
)

Maybe I could get assertAll to take either a === expression or a
boolean one, though that would likely require an implicit, which means
this would need to be mixed in from an AssertAll trait. The default
message could just say which one was the first to fail, and the long
message indicate all of which failed. The output could look like the
table output perhaps for both default and long messages.

Would such a construct better let you write your tests the way you
like to write them?

I think this discussion may be better hosted on scalatest-users. If
you're on that and want to continue, please switch over.

Thanks.

Bill
----
Bill Venners
Artima, Inc.
http://www.artima.com
Ken
2011-07-11 17:13:06 UTC
Permalink
Post by Bill Venners
These are concise and readable, except that I am not unsure I don't
not understand what !CharSet("ab") !~~= "dbf" would mean.
!~~= is a predicate in the package I constructed; it says, "the regex on the
left does not match exactly the string on the right."
Post by Bill Venners
It almost seems like you have a one column table of expressions you
want to have checked. I wonder if something like this would serve your
val cc = CharSet("ab") /\ CharSet("bc")
assertAll(
cc ~~= "b",
cc !~~= "a",
cc !~~= "c",
cc !~~= "d"
)
Maybe I could get assertAll to take either a === expression or a
boolean one, though that would likely require an implicit, which means
this would need to be mixed in from an AssertAll trait. The default
message could just say which one was the first to fail, and the long
message indicate all of which failed. The output could look like the
table output perhaps for both default and long messages.
Would such a construct better let you write your tests the way you
like to write them?
That sounds good, if you're willing to do it.

In one sense this is running into the curse of Scala's flexibility; In Java,
you can't overload stuff onto assert, instead you need assertEq, assertNeq,
etc. In Scala you can, but then you can only easily push the envelope so
far...

Anyway, if you think this feature is worthwhile and have time to add it,
please proceed! But I will survive with my one-assert-at-a time debugging,
if needs be.

Thanks,
Ken

<http://www.artima.com>

Loading...