Ad
  • Custom User Avatar

    yes, you totally got my idea. Only do a "end check".

    About the branching, yes, you're right. To keep that part of the requirements I'd have built andother kind of function/procedure that would build a chain, store the result, then build several other chains coming from it and check the final result (I guess you can do that with one single function and a/some default argument(s)).

    Maybe somethign like:

    def run_scenario(actual=None, previousChain='', depth=2):
        ...
        # build then archive the result and assert stuff, giving the complete chain call
        ...
        if depth:    # build a chain on the current result
          args = actual, previousChain+currentChainAsStr, depth-1
          run_scenario(*args)
          run_scenario(*args)
          run_scenario(*args)
    

    That's roughly what I'd try to achieve, I believe. Not saying it's better. Just another approach.

  • Default User Avatar

    I'd actually avoid the step by step testing/logging.

    Right. Yes that would be super silly! That's not what I'm doing. And you should totally be shutting me down if that was the intention. Not worth it in the slightest.

    If you look at the test output, it isn't linear. I am making random attacks on the available operations existing on the mutable state.

  • Default User Avatar

    I'd actually avoid the step by step testing/logging.

    now you have my interest. what do you mean that you would do instead?
    do you mean that, for this specific kata, you would just make all the calls and check the end result? I think you'd miss a few test requirements then but I'm also speculating about what you mean x)

    no need for the extra layer of exception handling

    I'd argue there's no exception handling :D There's a super-break, and there's a log dump. Nothing delicate or clever.

    The extra layer of information about "when"

    Right so you do mean, just fire off the linear calls all at once. BUT we actually need to treat it more like a tree and test different branches. And they can't be explored separately in isolation, because there is good reason to test them together to see that they do not affect one another. For example, it should be possible to call it five times, save the result, and then call that result with different inputs (branching). A BIG problem here is mutable state, without that, yeah, it could be a single check for a long chain of calls. Same with the user print debugging - they may need to know everything that was done, a straight path to where the problem happened might not reproduce it because of mutable state. I would for example not need to print out the assertions I made if not for the fact that they can mutate state when using == (as stupid as that is, clearly everyone should move over to haskell and abandon all else)
    You don't see me writing these for function-kata's, because they start over with fresh state on each invocation, and anything they do internally is none of my business. Good!

    Am I spoon feeding? Yeah.. no.. maybe? I'll complain about mutable state here again. It can be real tricky figuring out what was done. What I am in particular aiming for is reproducability. Think of all the "works in my IDE" posts.
    The user should never need to question the test code, they should be shown what happened. I think that's fair, not spoonfeeding? :D

    Am I done with it? So, just like with message posts I have a terrible habit of editing things after submitting. I think I'm through with that though, yeah.

  • Custom User Avatar

    Do you think simpler code produce the same/equivalent input?

    Not sure. If I had to deal with this, adding logs "somewhat like you did", I'd actually avoid the step by step testing/logging. This reduces a lot the amount of code needed and it's complexity: no need for var names anymore, no need for the extra layer of exception handling (the deepest one in run_scenario), and you still can give all the steps to the user.

    The extra layer of information about "when" the user's code is failing in the sequence is nice, but imo you're a bit spoon feeding the user: in most cases, if something must go wrong, it will be on the second or third call (Im talking about exceptions). Depth being rather low, the user can debug that rather easily with print stuff.

    btw, I got caught in the discussion and just forgot: we're good to go about approval, so? Or do you want to change some stuff yet? (on my side, green flag)

  • Default User Avatar

    well, the least we can say is that you don't like when things are simple... XD

    Do you think simpler code could produce the same/equivalent input?

    It's doing .. three things, right?

    Calculating the expected result - same as the solution.
    Logging.
    Testing.

    These always happen in sync. So - bundle them up into a single thing to avoid possibility of desynchronization.
    Maybe this bundling could be defined differently.

  • Custom User Avatar

    Yup!
    Or call the grumpyFace variable or whatever. ;)

  • Default User Avatar

    Right but that comes from trying to re-raise when there is nothing to re-raise. :D
    You may as well divide by zero.
    You're not raising nothing, you're failing to raise and therefore python will raise a RuntimeError

  • Custom User Avatar

    There is no such thing as bare raise.

    actually there is (sort of): if you type just raise, you actually raise an error (telling you this code cannot execute, but still x) So Stuff like this is usable (but bad, yeah):

    def f(x):
        if x:
            raise
    
    f(0)
    f(1)
    Traceback (most recent call last):
      File "<interactive input>", line 1, in <module>
      File "<module1>", line 3, in f
    RuntimeError: No active exception to reraise
    

    :)

    (but I guess that's a bad habit I got since I started JS... :( )

  • Default User Avatar

    Right so that IS re-raise. There is no such thing as bare raise.
    Also I'm changing to this:

    except:
        ...
    

    I previously used a linter that would complain about this but ... tbh I think that's misinterpretation of pep8 which says to be specific/narrow. But I specifically want to catch everything there. :P

  • Custom User Avatar

    Uh. But to be clear, both raise and raise e have the exact same effect

    oh, you actually just taught me something I didn't know about python... XD

    :+1:

  • Default User Avatar

    Uh. But to be clear, both raise and raise e have the exact same effect and all you're talking about here is that you prefer one syntax over the other?
    "bare raise" sounds a whole lot like something similar to raising a string in javascript, except, raising, but nothing. but it's a re-raise so bare sounds like what would happen if you type raise in python's repl, causing a RuntimeError since there is nothing to re-raise

    return -> break I'm okay with. yeah I thought you wanted to avoid exceptions as flow control

    but no exception thrown

    but this would be the exact same exception pattern/amount

  • Custom User Avatar

    those are two mostly unrelated things.

    yes I saw. ;)

    I would have to thread the comparison results out of the actions.

    No. Because I believe you're not talking about the same one I did.

    The user exceptions I simply let through.

    afaik, nope either ;)

    Code is simpler than sentences, so here is what I suggested:

    
        for action in actions:
            try:
                action()
            except AssertionFailed:
                # wrong answer
                test.fail("\n".join(log))
                break
            except Exception as e:
                # user crashed. show the log and re-raise
                log.append("CRASHED! See error below!")
                test.fail("\n".join(log))
                raise e                    # <<< that is "rerasising and not using a bare raise"
        else:
            test.expect(True)
    

    clearer now?

  • Default User Avatar

    well, the least we can say is that you don't like when things are simple... XD

    There's bound to be a quote somewhere about tests inheriting the complexity of the thing they're testing, I can feel it in my left ring toe.

    The exceptions.. those are two mostly unrelated things.
    AssertionFailed could be replaced by break, sure, yeah. Is that an improvement? I would have to thread the comparison results out of the actions. Having that super-break is awfully convenient and I don't think it's all that offensive or even unidiomatic. I am asserting stuff after all, bailing out is implied.
    The user exceptions I simply let through. I think when you say bare raise you mean something else because that is a re-raise.
    I wouldn't dream of messing with the user's exception, all I want from it is to know it happened so that I can dump the log.

  • Custom User Avatar

    well, the least we can say is that you don't like when things are simple... XD

    • why acc = add? I mean, why not just use add directly? (solution of the user) (edit: forget that, I just forgot to read the 3 next lines... x) )

    • I'd rather use this at the very end of run_scenario (that way, you can use break instead of return and raise in the loop. It's doing the same than your code, but no exception thrown (about that: if you keep the raise, reraise the original exception, don't use a bare raise) on the user side):

        for action in actions:
            ...
        else:
            test.expect(True)
    

    note: on second thoughts, it might be better to reraise the exceptions...

    note2: the for/else construct is a good indication to the reader that this statement will only execute if the loop succeeds. So it could be a good addition, to hint the logic without having to read the content of the loop. But nothing mandatory here.

    cheers

  • Custom User Avatar

    This comment is hidden because it contains spoiler information about the solution

  • Loading more items...