Peter,

I see you clearly understand what I wanted to get into and don't give up because of my aggressive tone in the first place.

Am 26.10.15 um 23:53 schrieb Peter Uhnák:
On Mon, Oct 26, 2015 at 8:27 AM, jtuc...@objektfabrik.de <mailto:jtuc...@objektfabrik.de> <jtuc...@objektfabrik.de <mailto:jtuc...@objektfabrik.de>> wrote:

    Am 25.10.15 um 16:33 schrieb Peter Uhnák:

        > assert:equals:  it's just more typing than #= with no
        additional outcome


    I also disagree, but that may be also because maybe we write
    tests for different purpse.
    If you write tests just to test your code, then whatever... I
    don't do that so I can't comment on that.

    However if you do TDD, then tests are to provide feedback, and
    from that perspective you want to see immediately what is the
    difference. If I see that the assertion failed I have to start
    digging to find out what is the difference, which is extra work
    and bad from feedback perspective. If I instead immediately see
    what I need to see it allows me to faster deduce the source of
    the problem and resolve it.

    I'm sorry, but what you are saying doesn't make any sense. Even if
    I "only" want to test code (which is exactly what you do in TDD,
    btw.), I need good feedback.


Well, that was a bit of generalization on my part. The point was, that many people write tests as an afterthought... which means they have different means of getting feedback from the system (e.g. logging, running manually the code, etc.). In such situation they often end up testing already mostly working code, and thus the feedback from the tests is not as important, because you end up less digging up problems (since large portion were found and resolved during manual testing).

I don't think there really is a difference between test first and tests as an afterthought. You always need good and precise feedback. What you say about having learned much more during development of a body of code is not necessarily true in teams or when you maintain software that has grown for years or decades, Remember, there are Smalltalk systems out there whose development started in the late 80s or early 90s. In fact, most commercial Smalltalk projects out in the wild are rather old. If you have to make sure you don't break anything by putting a system under test, you need precise feeddback, because errors can happen in areas you haven't touched in the last 5 or 10 years or even never before.

There is, btw, another area in which SUnit can be extremely helpful: Understanding an existing body of code and proving your theories about code to yourself while you dive deeper. Here, feedback is also very important.

So let's just assume that feedback of SUnit can never be too specific and is one of the very most important aspects of the framework.

    We could think about subclassing TestFailure and a way to hand
    information to the TestFailure so that a nice String can be
    produced. Like a method like cull: that adds arguments'
    printString representation into the failure description.


You mean #assert:description: ? Because we already have that.
Well, I actually mean a new generation of #assert:description: that concentrates more on the #description: part. Remember: some assertion methods have been added as a way to provide "more suitable" feedback, like handing in an object or two to be mentioned in the description string. So the problem at hand wasn't that the standard assert: method was insufficient, just its output. I clearly understand how this can happen. You want to provide a better log output and since you are testing something that even others might need, you just add that damn method that writes a nice "expecte $A but got $B". In fact, this solves a problem in the wrong way, because it will result in many more or less well-named assertion methods that basically are there to provide a better failure String.

So what we have is a way to display a description without parameters. But we sometimes want more than that. In case of #assert:equals: we wanted a String that says "Expected $A and got $B". Let's not speculate whether this String really saves us much time if we are in a TDD scenario or on a build server. Let's just assume it is never bad to know as much as possible about a problem to do something about it.

Let's also not forget that Kent Beck had this clear idea from the very beginning of SUnit that it must be lightweight and easy to understand to not shy people away from trying. You must be able to use SUnit within an hour and it must be easy and provide instant feedback. If I have to type too much, or keep too many things in mind, I won't use it.


    Please step back for a second and think again: these are two very
    different things. The job of an Assertion is to make a problem
    visible. The representation of the problem is something else, even
    if these are closely related. This is object thinking lesson #2 or so.


Object thinking lesson #3 tells me that I should not care about what is going on behind the curtains.
Well, that is probably not really true for unit tests, because you may want to test something in a very specific way to prove your thesis about how to solve a problem is true under several circumstances.

And while I could explicitly separate the two, if I do it all the time I don't see what's bad about having a convenience method. (And by looking at Object or String protocol, Pharo is a lot about having convenience over engineering rigidness).
Convenience methods are not bad per se, as long as they add value and clearly communicate what they do. Pharo and other Smalltalks are stuffed with lots of bad examples here. Sometimes Convenience methods are a symptom of design problems or the lack of multiple inheritance. Sometimes they are just a good idea.


    But: adding more and more misnamed and misleading assertion
    methods makes the use of SUnit frustrating and will make it
    obsolete over time. If I have to hunt for design problems in SUnit
    because it assumes something to be wrong even though my
    understanding of waht I tested is different, I lose way more time
    than I am ready to accept. This doesn't happen to me often. If
    finding that I misunderstood an assertion method means I lost a
    few hours, the best thing that may happen is that I never use that
    method again. In the worst case, I decide I think SUnit is useless
    for me. That would be really bad, don't you think?


I am not sure if we are talking about the same SUnit. Sure, there are 32 methods in the "asserting" protocol, however most of them are either opposites of one another "#assert: vs #deny:", or they provide some customization such has "#assert:description:", "#should:raise:" ... so if I count only meaningfully different methods the number 7 (not to mention that some of the methods are not even used). But if you have trouble understanding the purpose of seven methods, then the problem is on your end, and don't blame SUnit for it.

I think should:/shouldnt are great examples of convenience methods that are not named good enough. I know nobody who can use them without taking a second to think about them. So what you are saying is that SUnit already has roughly 25 convenience methods that aren't used or hard to understand. And the ones we understand are used because they are a good shortcut to a better description string (assert:equals:). So we are not far away from each other... ;-)

     > assertCollection:hasSameElements:

        > So, let's start by asking what the question really means.
        Does it mean that one collection is a subset of the other?
        What about ordering then? Does it mean both contain the same
        elements at the same position or just the fact that if they
        both were Sets contained the exact same elements. The
        question itself is not exact, so how could an answer possibly be?


    There is no question about this method, since this is implemented
    in code there is nothing ambiguous. This method effectively
    converts both arguments to sets and compares them like that.

    Sorry to say that, but this is ambiguity by design: you define
    hasSameElements: as "both result in the same Set". So the name of
    this assertion method is a great example of bad naming, IMO.


Yes, the naming is confusing. My point was, that instead of philosophizing about the meaning you can look at the code. Of course if you use the method for the first time (like I did), you will get burned by it (as I did).

Hmm - just by reading these 3 last sentences, you see that naming is important. But we don't disagree here. Let's forget about the concrete naming of the method from now on. It's an example. My point was that if the naming of a method opens room for speculation and philosophy, it is most likely either named badly or it is a bad idea in itself. Maybe it is not a candidate for addition as a core method in SUnit. SUnit should make you think about your code, not about itself.



        self assert: result asOrderedCollection asSortedCollection
        equals: (1 to: 10) asOrderedCollection


    This is what I usually do now (although I convert to Array, not
    OrderedCollection, because the expected one is usually created by
    hand with #() or {} ).

    I don't really care. If what you try t say is that the testing
    code can be ugly and long, then I agree. If you need tests like
    this very often and want something to make this easier, I
    understand and agree that some additions to SUnit can be helpful.
    But the way this has been tried so far seems completely wrong to me.


How would you test it then? Some problem domains deal with certain kind of problems more than others and thus benefit more from appropriate assertions.
This is not about how to test for something. Our discussion is about the question whether a certain wayto test something should be added to the framework or not. Is the test generic and important enough so that it should make its way into SUnit. And if it should be added, what is a good name for it, so that others find the method as the right thing to use for their purpose or will it disturb them in their proces more than it helps.

You can always use helper objects and non-SUnit-classes in your test packages for generalizations of your project's needs. If you need to walk trees a lot for your purposes, you can add Visitors and stuff, to your test package, put them under test to make sure they work and use them in your assertions. This is not necessarily a candidate for inclusion in SUnit, just because chances are others need to test Trees as well.



        > Just a few weeks ago, we discussed something similar about
        the equality of two Collections. Endless discussions where
        people try convince others that their definition of equality
        and/or sameness is correct with no outcome.


    I don't see a problem with that because collections truly can
    have different equalities based on the context and their purpose.
    And while you can call this rat poison, it effectively tells what
    kind of behavior you expect from the collections, which seems ok.
    So what, again, was the point of naming a method after a general
    Collection class and use a question that is very unspecific? A
    Collection has the same elements as another does not necessarily
    mean they both result in the same set. Can we agree on that? All
    the question asks if all Elements in Collection A can also be
    found in Collection B. The method name states nothing more than that.


We agree that the method is badly named. (However what equality of two Collections means is context-dependent.)
Phew. Thank you. I am glad we agree on this. So let's carry on with the SUnit related stuff.

    My point here is that a general purpose framework like SUnit
    should be free of such debatable things. SUnit has to be reliable
    and understandable.
    There is nothing wrong with providing some "plugins" for problems
    like Collections that make life easier.
    It would be desirable to have more control over SUnit's feedback
    with little typing.


Well but we need a way to provide context for the assertion. If I could type less then I would be happier user, however currently I don't see a way how to make it more general (so I don't need to type) and more precise (it still understands context) at the same time.
Now I have you in the corner I want you in ;-)
We should talk about ways to make it easier to provide clearer feedback Strings

So going back to your earlier suggestion with the class... you imagine something like this? self assert: a equals: b strategy: CollectionHasExactlyTheSameElementsAsAnotherAtTheSamePosition
No. For several reasons (based on how I interpret the names you provide as an example):

* You are back to the idea that you need a way to specify a specialized testing strategy. That is exactly the wrong thought here. What and how to test is highly dependent on your code base and needs. A simple #asserts: and your very special way of testing formulated either by writing complex code or using your own specialized helpers is the only way to keep SUnit lean and mean (BTW, there is nobody keeping us from providing SUnit helper packages that make life easier, like the Tree example - but not as part of core SUnit)

* what if I need a test to prove/check that two collections are NOT the same in a very special case? would you add a #deny:equals:strategy: also? It will blow the testing protocol up from 32 to 100 very soon.

I would like to start a discussion on ways to separate the assertion stuff from the feedback generation. The first steps I can think of are not sufficient in the "little typing" arena, and I must admit I have not yet found a good idea.

But to start with something, I'd like to start with the assumption that we only have #assert: and all we want is a second parameter, like in #assert:description:

So I am now just brainstorming, not saying this is a good solution already.

Let's assume we have some way to add parameters into Strings, like #bindWith: and friends (or Mustache, or whatever). Let's also assume there is something like Block>>#cull: that makes it easy to fill such a String with any number of arguments (e.g. 'I accept up to %1 arguments' cull: {arg1, arg2}). Couldn't we just put some machinery into the test runner that uses this facility to provide better feedback? like in: #assert:failureText: 'Expected %1 but got %2'.

At first sight, this is not so easy, because we need a place to store the arguments into, and since assert: just accepts a Block or simply the result of executing an expression, there is no such thing at hand (if it was easy, SUnit would have had such a feature from the beginning).

So the assertion needs to be at least a Block or an object which offers its arguments to the "feedback machine" as well.

The api might change from assert: to something like #assertExecuting: aBlockOrObject with: listOfArguments resultsIn: expectedResult

Typing this, I see that this is not nice, because you cannot just write some expression like self assert: myObject *3 = 15. You'd have to write assertExecuting: [:myObject| myObject * 3] with: 5 resultsIn: 15 Adding the feedback generation into makes the API even more complicated, because you'd have to ar least pass a String as a fourth parameter.
So we'd end up with:


assertExecuting: aBlock with: listOfArguments resultsIn: expectedResult otherwise: failureString.

In this form, I am sure nobody would use it. Or just for very special and important tests. So all that I can come up with for now is insufficient, unfortunately. Sure, there can be variants that provide some standard parameters for most of the arguments, but the base problem remains that the API is clumsy.

So what I am just showing is that what has happened with the assert: variants was the best thing one could possibly do without making SUnit heavy-weight and unpleasant to use. Does it mean I am wrong? Does it mean it would be a good idea to put some more thought into this? Does it mean we should just go on and add more convenience methods? Does it mean we shouldn't change SUnit and keep it as it is?

I think it means we shouldn't just go on adding new convenience methods, but think about ways to improve its internals and make it more flexible in its feedback generation facilities. Maybe we'll end up in something that you can use as a base implementation for today's #assert: method...

1. #assertCollection:hasSameElements: is a bad name, because it treats arguments as sets, not generic collections

it could be renamed or removed (no code in Pharo itself uses it (only Spec, but the use there is wrong))
let's put a check mark on this one. It was just an example

2. Providing selectors such as #assertCollection:equals: and similar just to get different description may not be the best idea, which leads to point 3
I think I made it clear that this will be a less than perfect solution that doesn't jump far enough

3. An easy way to extend printing/assertions/plugins; but what would that look like?

I am convinced this is the way to go. But I have no good answer to it. Just a spark for now.
Maybe
#assert: a = b description: [ Diff collectionDiffBetween: a and: b ]

Possibly the best thing we can come up with for now. By repeating the arguments in the feedback generation part, we keep the complexity out of the test runner's internals

or
#assert: a equals: b strategy: CollectionHasExactlyTheSameElementsAsAnotherAtTheSamePosition
See above. Better than another assert:-variation, but still too heavy

or
something else...


Peter

Joachim

--
-----------------------------------------------------------------------
Objektfabrik Joachim Tuchel          mailto:jtuc...@objektfabrik.de
Fliederweg 1                         http://www.objektfabrik.de
D-71640 Ludwigsburg                  http://joachimtuchel.wordpress.com
Telefon: +49 7141 56 10 86 0         Fax: +49 7141 56 10 86 1

Reply via email to