busterjs / buster

Abandoned - A powerful suite of automated test tools for JavaScript.
http://docs.busterjs.org
Other
448 stars 37 forks source link

"Deffert" - assert with promises in asynchronous tests #339

Closed johlrogge closed 8 years ago

johlrogge commented 11 years ago

Buster has a really nice feature: return a promise for an asynchronous test:

"some test" : function() {
   return when(asyncThingy()).
              then(function(actual){
                 assert.equals(actual, "expected");
              });
}

I came up with a pattern to make asserting simpler (I know at least one other person has done something similar so probably pretty obvious):

"some test" : function() {
   return when(asyncThingy()).
              then(deffert.equals("expected"));
}

Basically it allows splitting asserts in two parts: first declare your expectations. Each expectation returns a function that accepts the actual value and performs the assert.

Each assert also returns the actual value so that several assertions can be made in sequence:

"some test" : function() {
   return when(asyncThingy()).
              then(deffert.match({key:"value"})).
              then(pluck('otherKey')).
              then(defert.equals("other value"));
   });
}

I don't know if there is any other way I missed to do this with existing assertions or if this would be a good extension (or part of the buster core assertions)?

meisl commented 11 years ago

I like the idea, especially the sequencing is quite elegant. I've been thinking about testing promises too, but my main concern is with the two code paths that can be taken, resolve and reject, and how to properly cover that.

So, how would you test for an expected rejection? What about guarding the unexpected path? I mean s.t. you'd get a failure with informative message (as opposed to a timeout) in case the code under test, say, rejects while you had expected it to resolve with some value to assert on, and vice versa?

johlrogge commented 11 years ago
"some test" : function() {
   return when(rejectedThingy()).
              then(undefined, defert.equals("expected"));
}

or

"some test" : function() {
   return when(asyncThingy()).
              error(defert.equals("expected"));
}

Buster will detect if no assertion is made. But one could be explicit about rejecting a resolve:

"some test" : function() {
   return when(asyncThingy()).
              then(defert.never("asyncThingy must be rejected!"),
                      defert.equals("expected"));
}
meisl commented 11 years ago

I see, thx. I'l have to think about it a bit, as I've been always thinking in terms of the alternative form for async tests, a done arg.

Just one more question, maybe stupid: what is "defert" or "deffert"? My first guess is kinda portmanteau of "assert" and "deferred"..?

johlrogge commented 11 years ago

Your first guess is correct :)

It was sort of a working name that stuck with me and I forgot that it's not obvious. The name can be changed to whatever makes the most sense to the most people. What I'm mainly interested in is the mechanics of deferring an assert in this way. I would like something that plays a bit better with promises than the normal asserts. I'm also interested in if there is already some way to address asserts with promises that I haven missed.

johlrogge commented 11 years ago

Come to think of it there is also a third path (onProgress). I think defert lends itself well for testing expected progress updates:

return when(someThingWithProgress()).then(
   defert.equals('final value'),
   undefined,
   defert.inSequence(
      defert.equals('firstProgress'),
      defert.equals('secondProgress'),
      defert.equals('finalProgress')));
meisl commented 11 years ago

Your first guess is correct :)

Glad to hear that, my next was something re "de-fertilizing"...

Buster will detect if no assertion is made. But one could be explicit about rejecting a resolve:

In an async test you wouldn't get a failure "No assertions", just a timeout. Correct me if I'm wrong, but that's one thing I found particularly annoying. So I guess what I wanna say is: not only could you be more explicit, you must.

I'm also interested in if there is already some way to address asserts with promises that I haven missed.

Had been thinking about something like assert.rejects(promise, ...) and @cjohansen said he had too. So from that I think no, there's currently no better way. I do think, however, there ought to be one.

Here's a suggestion: let's first try to make precise what's to be addressed / what the problems are with what's currently available. At best without having any specific design idea in mind. That is, let's dive into "mechanics", or specs thereof. Once we have that we should be able to judge/compare specific ideas better. As a starter, here's some from my side:

Come to think of it there is also a third path (onProgress). I think defert leds itself well for testing expected progress updates

Right, hadn't been thinking of this. It's rather tricky/specific though, as e.g. how many progress updates would you require exactly? What really (practically) would be the thing to (reasonably) assert on in it? progress pct being between 0 and 100?

What's not so elegant IMO is adding defert as a third main "verb" next to assert and refute. I'd prefer to be able to put it together from things just one level below, as on the level of assertions. Not sure how that sorts out though. It's pretty possibly that a substantial simplification does require a top-level addition.

Note that these are all just some first thoughts of mine, TBD.

johlrogge commented 11 years ago

About detecting assertions. When I return a promise I never get a timeout (unless the promise is never either resolved or rejected) but I do sometimes get a weird behavior. It may be related to no assertions.

       buster.testCase("promises", {
           "rejected test" : function() {
           var defer = when.defer();
           window.setTimeout(function(){  defer.reject("hello")}, 100);
           return defer.promise;
           },

           "resolved test" : function() {
           var defer = when.defer();
           window.setTimeout(function() {defer.resolve("hello")}, 100);
           return defer.promise;
           },

           "other" : function() {
           return when("hello");
           }
       });

This results in:

[johlrogge@kirishima phloem]$ buster test promises -r specification Chrome 23.0.1271.97, Linux ✖ promises rejected test undefined: undefined ✖ promises resolved test undefined: undefined ✖ promises other undefined: undefined 1 test case, 3 tests, 0 assertions, 0 failures, 3 errors, 0 timeouts WARNING: No assertions

As you can see, no timeout and WARNING: No assertions. The undefined:undefined is new to me.

I know that I have seen this work at some point so it may be a new bug (I recently updated buster). I do think that the timeout you experienced has to do with the "done" way of doing things.

I'll address your other points after work. This one took longer than I thought :)

johlrogge commented 11 years ago

These tests behave correctly

           "resolved assert" : function() {
           return when("hello").then(function(val){buster.assert.equals(val, "hello");})
           },
           "resolved assert" : function() {
           return when("hello").then(function(val){})
           },
           "resolved assert" : function() {
           return when("hello").then(function(val){throw new Error("hello")})
           }
cjohansen commented 11 years ago

First of all: I'm loving this idea! Looks very nice. Not crazy about the name "defert" though - I like the idea of it, but it looks too much like a spelling mistake to me :)

I'm thinking this could be built as a standalone module, an extension to referee. Generating a deferred wrapper can probably be done in a unified way (e.g. a loop and one function) once we settle on the API.

Can we find some way of expressing this within the existing API? E.g. assert.deferred.equals(42)/refute.deferred.equals(13) or something along those lines? Or maybe defert is just as good, I just need some time with it? :) Will it then also be defute? :) What about expect(24).toBe(24)?

johlrogge commented 11 years ago

Thanks! Yes, defert is not the essence of the idea. I think assert.deferred or deferred.assert works well. Perhaps deferred.* would be a good place to collect async utils? Expect to be is sort of wired in the wrong way for splitting the value up since the actual goes first and the API makes sure you cannot get it "wrong". Also future tense does not fit in the promise vocabulary. The "defer.assert.*" keeps some of the benefits of expect (the dictated order of actual and expected) but loses some of the BDD "words matter"-beef.

Perhaps something like this is in the right direction:

when(24).then(expect.actual.toBe(24)); when(24).then(expect.result.toBe(24)); when(24).then(expect.value.toBe(24)); when(24).then(expect.toBe(24));

Of course not all of them, just not locking onto the first word I thought of. I do think the 4th alternative is not good enough. I would like a noun in between...

johlrogge commented 11 years ago

@meisl

About something for both camps:

The benefit is more obvious in the promise camp. As it is now the current tools do leave promises at a disadvantage. However, what you do get in either camp (and that maybe makes "deferred" a bad choise of concept to package the idae in) is: composability

You can build higher level asserts with this technique even if you don't use promises:

var assertSuccessful = defer.assert.match({status:"successful"});

that can be used like this:

assertSuccessful(service.getResult());

Also, as seen in the progress these kind of asserts can be composed. Here is another example:

var definedSuccess = defer.assert.and(
  defer.assert.defined,
  defer.assert.equals("success")
)

The example is a bit contreived but you get the idea. This kind of composability is most useful again when you test asynchronous code with promises. It is however not useless in "normal" tests. As it stands today I think the promise variant of testing has a bit of an unfair disadvantage which probably makes it less popular than it should be.

@cjohansen I could start a project for an extension if you don't have something else in mind (feel free to use the idea in referee and integrate it in buster that way).

cjohansen commented 11 years ago

@johlrogge: Sounds like a good way to start. Btw, I would love for this module to be part of the default Buster stack. My suggestion to develop it as a separate module is just because I think that makes sense organizational-wise. When you have some code we can decide whether it's most beneficial to keep it a separate module or just include it in referee.

I totally agree on promises being at an unfair disadvantage. Solving this will be a great win.

johlrogge commented 11 years ago

Any good ideas for a module name. I know what we don't want :)

cjohansen commented 11 years ago

Something simple and straight-forward? deferred-referee. Something clever? referred? :)

meisl commented 11 years ago

BDD: expect(promise).toBe.sometime(42) or expect(promise).toBe.eventually(42)? "sometime" has the disadvantage that it might be mistaken for "sometimes". Or maybe .toBe.finally(42)?

Module name: referee-async?

Composability is a big asset, that's for sure. Just so I can get my head wrapped around it: the basic idea is partially applying the assertion, but not from left to right as usual. Rather it's the first argument ("actual") which is "postponed" since that is what we get last. Is this an adequate description?

The main problem with async tests is IMHO this: the nice linear (and flat) sequence of arrange-act-assert tends to be lost. Mostly it's the "act" and "assert" that get interleaved, and often things become nested at several levels. If you can agree with that then I'd say we have our main objective. Or maybe other things should be regarded more important, e.g. reduce clutter (stuff to write) or (near-) automatic guarding of unexpected code paths or... ?

cjohansen commented 11 years ago

@meisl A partial application that starts from the second argument is sort of what's going on I think.

Also, this is mainly intended to help with promised-based APIs, for which it is highly useful. I think we have to target various specific usecases like that to eventually be allround good for async tests.

BDD: expect(promise).eventually.toBe(42)?

johlrogge commented 11 years ago

@meisl Sorry for the late reply. I was really busy yesterday.

The main goal of this is to "get inside" the promise resolution and assert there. Partial application of what we know before executing the test (such as which values we expect the test to yeild) is what I'm suggesting. (You need to get a one argument function into the then part of the promise. The main objective is to get the boilerplate out of the specification function(value){assert,equals(value, "expected"); return value}. Once that is accomplished one can address the unnatural interleaving and nesting in quite innovative ways:

return sequential(promise,
                            deferred.assert.defined,
                            deferred.assert.matches({key:'value',
                                                                     id: deferred.assert,greaterThan(0)}));

Notice the nested assert inside the matched structure also notice that such an assert can be saved for reuse:

var assertValidObject = deferred.assert.matches({id: deferred.assert,greaterThan(0)});

You can also to nifty things with lodash or underscorewith partially applied asserts:

return when(promise).
  then(_.tap(_.pluck('key', deferred.assert.greaterThan(0))).
  then(...)

Partially applied asserts opens up for very innovative (and easy to understand) testing styles that IMHO are way underused in testing frameworks. Most frameworks start off in the xUnit imperative school and tends to get stuck there. The imperative style tends to get really messy for asynchronous stuff but actually also hard to reuse parts even in synchronous tests.

expect(promise).eventuallyviolates the "getting inside" the promise chain. You will need to set up the whole promise flow and then watch what falls out of it.

To have expect automatically resolve promises is also a good suggestion. Actually making referee promise-aware in this way is another good idea.

When in cujojs has a lot of overloading going on so that you can either do when(12) or when(promise). Making assert, refute and expect work this way would be a cool thing. I'm not quite clear about how the assert would signal that the test is finished though (as is usually done with done. Buster would have to keep track of all started asserts and consider the test done when one of them fails or all of them succeeds. That is however a different feature that is kind of orthogonal to this one.

My $500 ;)

johlrogge commented 11 years ago

I noticed we started to get to the core of the usefulness of the suggested feature (Thanks @meisl for poking them out with questions). Partially applied asserts are not only useful for asynchronous testing which is what I associate defer with (even if what we technically do is deferring the "actual").

I'm tossing referee-partial or partial-referee into the pile of possible names. More implementation specific, less application associated (?)

cjohansen commented 11 years ago

referee-partial or partial-referee

Interesting. I like both of those. Maybe naming magician @magnars has a suggestion that captures this essence in some creative way? :)

magnars commented 11 years ago

Hey, this looks pretty nice. I like the deferred.assert and deferred.refute API.

If you want some clever name, how about impartial-referee, something all referees strive to be, of course. There also the promising-referee, but I think I like the first one better.

cjohansen commented 11 years ago

@magnars impartial-referee - the functionality is partial? :)

magnars commented 11 years ago

@cjohansen Then I'd say that partial-referee is a clever, clever name. :)

johlrogge commented 11 years ago

Ok, partial-referee it is then? I'm assuming I should get away with depending on referee? Do I just monkey patch them in on the buster-object from within the module or is there some clever way to extend buster that I should be aware of? Should it be a plugin like buster-amd?

/J

meisl commented 11 years ago

how about impartial-referee, something all referees strive to be

:D that's a good one! Although I'm not so sure about it in view of the recent scandal in football bets... But seriously, one criterion for a good name is IMHO how clear it makes the purpose, not how that is achieved. So I'd still vote for a combination of referee and async (assuming it would comprise both, promises and the done style). Btw: is there a convention/meaning conveyed by whether it's referee-xxx or xxx-referee?

@johlrogge: thanks for the explanations and examples. Glad I'm not getting on your nerves too much :) Your observation re imperative vs functional in testing is a good one. I've always wanted to take a closer look at what they do about it over in Haskell world, maybe now is a good time. There's HUnit but the quasi standard is QuickCheck I believe.

deferred.assert.matches({id: deferred.assert,greaterThan(0)}) - the comma , should be a dot ., right? That's interesting. Observe what it does to have the "actual" implicit - reminds me of how things get turned "inside-out" in CPS (continuation passing style) transformation (implicit = there's nothing to .id on). Hmm, but then, isn't it replacing one kind of boilerplate with another? That's not to say it's no good, just what I was wondering at first sight.

expect(promise).eventually violates the "getting inside" the promise chain. You will need to set up the whole promise flow and then watch what falls out of it.

I see. But still, can't we have it construct a thing with asserts inside, only under the hood? Wildly speculating:

expect(promise).eventually.toBeDefined()
    .and.itsProperty("key").toBe("aKey")
    .and.itsProperty("id").toBeGreaterThan(42);

The point being: instead of sequencing several expects we only have one, and stack assertions via ., just like your variadic and(...) or sequential(...) above. Might be pretty bogus but you know, I'm deliberately trying to take an opposite point of view (= here: NOT start from how to implement it). But well, you've already pointed out that you won't mistake it :)

meisl commented 11 years ago

Maybe I should have said that of course I expect expect to be a little more savvy than it probably is now. What we can't do, however, is solving this by overloading (alone) because it would overlap with "traditional" asserts on promises. For instance: expect(p1).toBeSameAs(p2) - same promise or both yielding same thing? Rather there'll be an explicit (but ideally compact, non-repeating) indicator that'll take you "to the other side". Here it is the .eventually, further above it's the deferred.

@cjohansen: could you give me some recommendation where to look for how the BDD stuff is working (internally)?

@johlrogge: I'm not sure I understand what you meant by orthogonal (~> signal that test is finished). Does it mean that your proposal, as it stands, and without my speculations, could be used with buster as is? That is, would not require to make buster track started assertions and check if all eventually succeed?

johlrogge commented 11 years ago

@meisl about orthogonal, it got pushed out of context. What I meant is that being clever about promises is orthogonal to inserting inside promise chains. I can see a value in both. Stay tuned for answers to your other comments

meisl commented 11 years ago

Yep, thx. No need to haste :)

@cjohansen: nevermind, stupid q. expect.js in referee is what I want.

meisl commented 11 years ago

Remark on expect.[sometime|eventually|finally]: there's a bit of a conflict with it vs itEventually as used in specs (itEventually equiv to prepend test with //). I have not spent much time in BDD/specs land, though; so it might well be no problem in practice.

cjohansen commented 11 years ago

@johlrogge I'm thinking depend on referee, then export a module that extends it? So, to use these assertions, one would require("partial-referee") instead of require("referee")? It doesn't need to be a plugin, as there's a special package for integrating referee and buster. I'll handle that stuff, you just focus on getting the functionality in place :)

johlrogge commented 11 years ago

one criterion for a good name is IMHO how clear it makes the purpose, not how that is achieved. So I'd still vote for a combination of referee and async (assuming it would comprise both, promises and the done style).

opinion That is exactly why I didn't want async. The purpose is to be able to build partially applied assertions. Partially applied assertions, I have come to realize, very much applicable outside of the asynchronous domain. A good name, IMO, should not limit the end users imagination. That is the problem with abstract concepts, they are applicable over a plethora of domains and hence should be called exactly what they are: the name of the abstract concept.

Glad I'm not getting on your nerves too much :)

Never ;)

deferred.assert.matches({id: deferred.assert,greaterThan(0)}) - the comma , should be a dot ., right?

Yes (sorry)

That's interesting. Observe what it does to have the "actual" implicit - reminds me of how things get turned "inside-out" in CPS (continuation passing style) transformation (implicit = there's nothing to .id on).

Just to be clear, this has nothing to do with implicit (at least from what I know from scala and what the word in general means to me). Left out is more correct IMO.

Hmm, but then, isn't it replacing one kind of boilerplate with another? That's not to say it's no good, just what I was wondering at first sight.

It looks like that but remember that this kind of boiler plate can be reduced one char and a dot var a = buster.partial.assert; a.equals(10) while var a = buster.asert;function(v){a.equals(v, 12); return v} is the best I can do with the existing way.

It is also important to note the number of things to keep in mind without partial asserts:

But most important is that partially applied asserts is a different kind of animal with more levels of flexibility. If this was scala and not javascript it wouldn't be needed, I could just do this: assert.equals(_, expected) and I would be good to go.

I if expected was the first argument (which it is in junit for instance) I could get a way with _.partial(assert.equals, expected) which is a bit better.

Actually none of the two would solve chaining unless we changed assert.* to return their actuals...

expect(promise).eventually.toBeDefined() .and.itsProperty("key").toBe("aKey") .and.itsProperty("id").toBeGreaterThan(42); The point being: instead of sequencing several expects we only have one, and stack assertions via ., just like your variadic and(...) or sequential(...) above. Might be pretty bogus but you know, I'm deliberately trying to take an opposite point of view (= here: NOT start from how to implement it). But well, you've already pointed out that you won't mistake it :)

Yes the above is the object oriented builder way of doing things. Nothing wrong with that. It generally requires some meta programing voodo to make alla options availible for proper chaining. I'm not a fan of this style since it is pretty constrained to what the framework author had in mind and requires reading a manual to extend it. That is just my opinion though.

I think the functional approach composable in a natural way.

You mentioned specs, did you mean the scala testing framework or specs as opposed to tests? I think rspec is probably a more suitable source of inspiration for various reasons, one of them being that it is written in a dynamic language with more similar constraints/posibilities.

conclusion

I'm leaning towards partial-referee for general applicability of the partial pattern reasons. I don't have much stake in the expect side of things but with expect.value.toBe(42) one can use the exact same implementation as for asserts (partials under value).

I can whip something together pretty soon I think but my evenings are a little crammed so not sure about this week. I think I have enough to get started anyway :)

johlrogge commented 11 years ago

@cjohansen Ok, I'll do that :)

meisl commented 11 years ago

Ok, quite some things. For now I'll go for those that only need clarification. Easy ones first. And in the end, beg your pardon, kind of a rant of mine about naming. I do mean what I'm saying there but I have to apologize in advance for the possibly harsh tone. Just take it as another "advocatus diaboli" thing, but plz, it's not at all only joking.

Just to be clear, this has nothing to do with implicit (at least from what I know from scala and what the word in general means to me). Left out is more correct IMO.

Didn't have anything Scala in mind. Just as opposed to "explicit", ie. no symbol for it, but still being a crucial part, through context. No problem with calling this "left out". More important is that at the very core of your approach is, well, to actually leave it out. That's what IMO makes your approach genuine (and promising). What I was trying to point out is that it resembles a dualization (in the sense of category theory), where, of course, there are duals of all the problems as well. I'm not at all able to give a formal characterization, just saying "resembles" :)

You mentioned specs, did you mean the scala testing framework or specs as opposed to tests?

Specs as opposed to tests, no Scala.

It looks like that but remember that this kind of boiler plate can be reduced one char and a dot [...] It is also important to note the number of things to keep in mind without partial asserts: [...]

Very good points, thx for pointing them out. Didn't see it.

If this was scala and not javascript it wouldn't be needed, I could just do this: assert.equals(, expected) and I would be good to go. [...] I if expected was the first argument [...] I could get a way with .partial(...)

Well, higher order functions and composability are known to go together pretty well. Scala's conditions of coming into existence are undoubtedly a lot more fortunate than those of JS. Nevertheless, your comparing the two is valuable. Anyways, that makes me think of something rather out-of-fashion: combinators. So this is more an idea than a "clarification". I, for my part, will give it a try, ie. try to think of "the thing"(TM) in terms of combinators.


THE RANT:

one criterion for a good name is IMHO how clear it makes the purpose, not how that is achieved. So I'd still vote for a combination of referee and async (assuming it would comprise both, promises and the done style).

opinion That is exactly why I didn't want async. The purpose is to be able to build partially applied assertions. Partially applied assertions, I have come to realize, very much applicable outside of the asynchronous domain.

Good point, especially the last sentence. So if the module in fact only comprises the partial app stuff - which makes a lot of sense - then "async" is inappropriate. It has to be differentiated from other options to deal with async tests.

A good name, IMO, should not limit the end users imagination.

Well, it must to some extent, if it's to convey anything. Take for example an extreme: module-x - no limit whatsoever. So let's just say it should point the end user in the right direction, capture the essence. Now, what that really is - is quite hard a thing to get right. That's because "right direction" or "essence" aren't anything remotely objective per se. They depend on the recipient's experience / context. Therefore choosing a name is much more a question of defining the audience than anything else. Put more simply: "whom do you intend to understand you?".

Now let's put partial-referee to this test: First off, notice that it took me quite a while* to think of it in terms of partial application, and that it was only after I had asked if that would be somehow adequate - that partial became the major candidate. Before it was "defert" which I luckily happened to guess right. Then there's the combination with referee (I'll take referee as fixed). To me, the (funny!) joking around a "partial" vs an "impartial" referee is enough to rule it out as a serious option. Too much distraction, too much of an insider lingo. Depends, of course, on how one defines "clever". Or, again, "whom do you intend to understand you?" Put differently: in order to understand, should it be necessary to have read this thread (in exchange for a manual)?

There's a tendency, especially in the JS community, that I, personally, find pretty hard to cope with. It's simply that it seems everyone's trying to be particularly "clever" with names for their stuff. With "clever", you'll guess it, being not exactly defined as I would define it... __ *: no "advocatus diaboli" here!

magnars commented 11 years ago

@meisl I applaud you, good sir. That rant was spot on. Thanks!

johlrogge commented 11 years ago

combinators

Excellent observation! I really see a resemblance between parser combinators and "assertion combinators" to the point that I am very excited by the combinators angle of it and am starting to think that "combinator" actually captures the essence of the idea. It just happens to also be very applicable for promises.

naming

I should have said "should not unnecessarily limit the users imagination". Because of the path into this thread (pain when testing with promises) I initially took the spin of "defer" which I feel overly constrained my own imagination about the concepts applicability. That was my point.

The first rule of names: Until you know, pick a bad one or it will stick :) Defert was maybe too good to start with but it was never something I argued strongly for :)

About cleverness: it was not until impartial-referee was suggested that I actually thought of the duality of partial-referee. I didn't intend to be clever I wanted to convey: relationship with referee hence referee, and partial as in *partial application hence partial. The cleverness made me lean towards partial-referee over referee-partial since it is more memorable

Lets not forget: names should be descriptive but they are not descriptions. There is no way we can pick a name that conveys the whole purpose of the framework but a name certainly does lock some neurons which is why I wanted to be more open than just defer.

The tendency in the JS community to be funny is not a problem for external names and it is not only in the JS community. Take Java for instance: Spring, Guava, Guice, Hamcrest to name a few. Are they bad names? No, they are memorable and googleable just like busterjs and there is no way that a single word or even two can be picked to properly describe guava for instance. And to be fair: clever names are more memorable once you get the cleverness.

In this case however we are talking about an internal name. An extension to referee so we can piggyback on referee. So relationship should therefore perhaps be primary referee-x - fair enough. That would give referee-partial.

Now I have to suggest refreree-combinators, my current favorite.

My opinions of names are strongly phrased weakly held :) (But I /DO/ care about the naming or I would not spend this much time debating it)

cjohansen commented 11 years ago

Now I have to suggest refreree-combinators, my current favorite.

+1

Re: Naming strategies. I agree with what @meisl writes to a large extent when it comes to naming concepts in code. However, as @johlrogge says - module names are a little bit different. It is not a full description. Module names also take on the role of the "identity" or "personality" of the module. Generally I try to pick names that have a "namey" sound to them ("Buster", "Sinon", "Referee" etc) and that has some back-story/semantics that is meaningful within the context of what the module does. The hope is that these two aspects help me find memorable names.

And to be fair: clever names are more memorable once you get the cleverness.

I agree a million times with this.

meisl commented 11 years ago

Thanks to all, what a great discussion!

referee-combinators[1] is quite good, although it might turn out hard to live up to the expectations it possibly creates. In any case, it's way better than all the others in that it fits my criterion and all the ones that you have pointed out.

Btw: here's where the (my) problems with partial-referee or reversed come from: "partial" is just an adjective, and on top of that it has more than one meaning. If you exchange the "application" in "partial application" for something different then, well, you get something quite different[2].

Re combinators: in fact I was thinking only of combinatory logic (CL) but parser combinators - or maybe even general monads? - might provide even more/better inspiration. For a start, I'm trying to formulate defert[3] etc in terms of CL - and already this is quite instructive, makes you see possibilities to turn things around etc. Of course it's neither necessary nor reasonable to do this all-formally, just informally is enough.

My opinions of names are strongly phrased weakly held :)

Excellently phrased, in my opinion!

[1]: with 2 "r"s in referee, not 3 [2]: my realizing this is also due to CL; a (mathematical) function can be partial, it can also be recursive. How would you call one that is both? ("partial recursive function" is standard but somehow "recursive partial function" appears more correct to me). It's no big insight, yet I didn't see it up til now. [3]: using defert here so you will know what thing I mean. What to expose to the user is a different matter.

johlrogge commented 11 years ago

although it might turn out hard to live up to the expectations it possibly creates.

Lets set the bar high! And if anyone whines we make them help us :)

For a start, I'm trying to formulate defert[3] etc in terms of CL - and already this is quite instructive,

That is very interesting. I'll try to set up a project now and get defert in there (but not call it that [3]. As soon as I have something I can create a project so we can start colaborating on this one. I think this one has a huge potential. I have done some parsing with parser combinators both in scala and in JS and I was actually struck by how they can be used to parse anything, not only text. I also see a potential solution to some of the issues I had with defert in it's simplest form by borrowing some concepts from parser combinators (that seemingly have a theoretical foundation in CL (?)).

Starting now, hope to have something this weekend.

johlrogge commented 11 years ago

Had an issue depening on referee:

[johlrogge@kirishima referee-combinators]$ npm install referee --save
npm WARN package.json referee-combinators@0.0.0 No README.md file found!
npm http GET https://registry.npmjs.org/referee
npm http 200 https://registry.npmjs.org/referee
npm http GET https://registry.npmjs.org/referee/-/referee-0.11.0.tgz
npm http 200 https://registry.npmjs.org/referee/-/referee-0.11.0.tgz
npm http GET https://registry.npmjs.org/underscore
npm http GET https://registry.npmjs.org/samsam
npm http GET https://registry.npmjs.org/bane
npm http 200 https://registry.npmjs.org/bane
npm http 200 https://registry.npmjs.org/samsam
npm http GET https://registry.npmjs.org/bane/-/bane-0.1.0.tgz
npm http GET https://registry.npmjs.org/samsam/-/samsam-0.1.1.tgz
npm http 200 https://registry.npmjs.org/underscore
npm ERR! Error: No compatible version found: underscore@'>=0.4.0- <0.5.0-'
npm ERR! Valid install targets:
npm ERR! ["1.0.3","1.0.4","1.1.0","1.1.1","1.1.2","1.1.3","1.1.4","1.1.5","1.1.6","1.1.7","1.2.0","1.2.1","1.2.2","1.2.3","1.2.4","1.3.0","1.3.1","1.3.2","1.3.3","1.4.0","1.4.1","1.4.2","1.4.3","1.4.4"]
npm ERR!     at installTargetsError (/usr/lib/node_modules/npm/lib/cache.js:563:10)
npm ERR!     at next (/usr/lib/node_modules/npm/lib/cache.js:542:17)
npm ERR!     at /usr/lib/node_modules/npm/lib/cache.js:522:5
npm ERR!     at saved (/usr/lib/node_modules/npm/node_modules/npm-registry-client/lib/get.js:138:7)
npm ERR!     at /usr/lib/node_modules/npm/node_modules/graceful-fs/graceful-fs.js:218:7
npm ERR!     at Object.oncomplete (fs.js:297:15)
npm ERR! If you need help, you may report this log at:
npm ERR!     <http://github.com/isaacs/npm/issues>
npm ERR! or email it to:
npm ERR!     <npm-@googlegroups.com>

npm ERR! System Linux 3.6.10-1-ARCH
npm ERR! command "/usr/bin/node" "/usr/bin/npm" "install" "referee" "--save"
npm ERR! cwd /home/johlrogge/repos/buster/referee-combinators
npm ERR! node -v v0.8.16
npm ERR! npm -v 1.1.69
npm http 200 https://registry.npmjs.org/bane/-/bane-0.1.0.tgz
npm http 200 https://registry.npmjs.org/samsam/-/samsam-0.1.1.tgz
npm ERR! 
npm ERR! Additional logging details can be found in:
npm ERR!     /home/johlrogge/repos/buster/referee-combinators/npm-debug.log
npm ERR! not ok code 0

The checked in version depends on lodash but when installing from npm it tries to install underscore. Perhaps release 0.11.1 ?

johlrogge commented 11 years ago

I have a skeleton up and running. The basic case works in node.js

I had to clone referee manually and npm link it before I could get my package.json to work. See my error in the previous comment (I think referee is broken in NPM) /cc @cjohansen

Anyway, The case that started this discussion is implemented. I have not tested all asserts but I tested one with expected actual and one with just actual.

I think I should also override add so that extending referee with assertions will automatically add combinator versions.

I have grand plans for making combinators that makes it easy to dig into structures and poke out some data you want to verify. I think it will become kind of a match on steroids.

Like:

assert.structure({id: assert.greaterThan(12),
                             posts: assert.some(assert.structure({read:true}))});

Asserting "everything at once" in a structure assert is probably overkill and bat practice. The benefit is that you can poke out a small amount of data from a structure (pretty much like with the power of match) but be more specific about how to handle nested structures with arrays etc.

The structure assert should pretty much just be sugar that generates several attribute-asserts. I think dusting off my parser combinators will help a great deal to find the right basic elements.

One can even imagine that with combinable asserts one can build schemas for validating forms etc which could extend the usefulness of referee... That is however secondary.

Have a look :)

meisl commented 11 years ago

Fantastic :D will check out ASAP!

Re npm I'm not the right person, but I think it's fine to go with the git HEAD of referee for now. It's not unlikely that some adjustments in referee will be needed. Also, not even the latest buster on npm is depending on it (still buster-assertions there).

Re "steroids": what's that? I'm thinking "Lance Armstrong" / anabolica but I guess that's not quite what you mean...?

Re CL: it's the fraternal twin of lambda calculus, so it's the foundation of everything, so to speak. It's also very basic[1]... Actually I have indeed moved over to monads now and it looks very promising! The parsers in "parser combinators"[2] really are instances of a certain monad (called "Parser") and the "combinators" are sequencing and alternation, for combining simple parsers into complex ones. Remember when I said "especially the sequencing is quite elegant"? Now I'm saying "monadic bind"... There's a lot for me to sort out but I think it's more than one monad involved here and we're really talking about monad transformers which are for composing different monads.

"combinable asserts" - that's exactly the point! In my speak it's "there must be something like an Assertion monad".

Since you mentioned Scala, where do you know "parser combinators" from?

Oh, this is really getting exciting :D I really have to finish now the stuff I was working on in ramp-resources...

[1]: I think I will turn my scribblings into a little "CL 101" and post it here. One can always ignore it if it's nothing new or boring. [2]: not sure if he coined the term but I thought of Graham Hutton, "Higher-Order Functions for Parsing" (1992) where the term "combinator" is used as above. There's also his excellent 2007 book "Programming in Haskell" where on pp 74 it appears as "functional parsers". He's avoiding the term "combinators" though. I suspect "combinators" turned out to be not so fortunate a name ... ;)

meisl commented 11 years ago

Have a look :)

For everyone who's similarly stupid as I am - it's here: https://github.com/busterjs/referee-combinators

johlrogge commented 11 years ago

About steroids I mean assert.match turned up to 11 :) So nothing fancy :)

When I put some effort into learning parser combinators I was using Scala and I found this blog particularly useful

meisl commented 11 years ago

Re steroids: sorry, looks like there's something wrong with me. Had to watch and wonder what they are on in order to get it. But it does show that my first guessings aren't always that bad, after all...

Re the blog on parser combinators in Scala: thanks a lot for that! Looks very interesting and well-written, albeit quite voluminous. So...

meisl commented 11 years ago

"Theory - that's what you don't understand. Practice is what you cannot explain." (I refuse to be on twitter, so I'm forced to tweet here)

cjohansen commented 11 years ago

@johlrogge Sorry about the referee screwup. Publishing 0.11.1 as we speak. Also, exciting work. Really looking forward to that assert.structure thing :)

meisl commented 11 years ago

Congratz @johlrogge, you've got your first pull request ;)

I also added a README where I wrote down how I worked around the referee issue - should be obsolete by now.

meisl commented 11 years ago

Hi guys. I've pushed the tests a bit further in the direction started by @johlrogge, namely (more) similar to how assertions are tested in referee itself. I turned around a few things so the appearance is now a bit different.

I made preliminary pull request #2 out of what I did so far. But beware, it's far from finished! Rather so we have a place to discuss details / how to proceed. Plz have a look.

meisl commented 11 years ago

referee-combinators has its own issues for discussion so this can be closed.

meisl commented 8 years ago

...not really understanding anymore what this was about - "finally" is probably what I want to say :)