Ideas on a testing focussed code transform
You're describing a variant of the interpreter pattern. I've used similar in task scheduling contexts, and the key drawback is you no longer have useful stack traces.
I haven't done any formal research on the approach, though. I will caution you that async functions are already widely used enough (particularly in Node) they're unlikely to go anywhere beyond something drastic.
You're describing a variant of the interpreter pattern. I've used similar in task scheduling contexts, and the key drawback is you no longer have useful stack traces. I haven't done any formal research on the approach, though. I will caution you that async functions are already widely used enough (particularly in Node) they're unlikely to go anywhere beyond something drastic. On Sat, Dec 16, 2017, 00:07 Pranay Prakash <pranay.gp at gmail.com> wrote: > Hey all, > > I was just reading the docs for `redux-saga` where I encountered a nice > design pattern for a saga which is (correct me if I'm wrong) a > regular javascript generator function that yields the intent to call a > function (instead of actually calling a function) until it goes through all > the steps. If that doesn't make sense, consider this simple function: > > ``` > async function findFriends() { > const myId = getMyID(); > const myUser = await fetchUser(myId); > return myUser.friends; > } > ``` > > instead of actually making the function calls needed, we can instead have > a function that does something like this: > > > ``` > function* findFriends() { > const myId = yield { fn: getMyID }; > const myUser = yield { fn: fetchUser, args: [myId] }; > return myUser.friends; > } > ``` > > This is a pure generator function that doesn't actually do anything, but > has all the necessary information to recreate the original function[1] (or > have a library "trampoline" through the function and make all the necessary > calls for you) > > A HUGE plus of the second version of the function is that it's *easily* > testable (unlike the first one). Pure functions are easier to test. Testing > this is simply a matter of calling `.next()`, getting the "intent", making > sure the right intent was yielded (good enough for unit testing) and > calling `.next()` again with the "mock" value, you want to return and > continue to test. > > An observation to make here is that you can transform the original version > of this code (easy, normal code to write) to the latter (easy code to > test). So, what about having some sort of babel transform perhaps that can > convert the first to the second but only in the context of unit tests. You > write code the normal way as you would for your application and don't worry > about test suite implementation details (mocking/dependency > injection/etc.), and when you want to test, simply import your function > (which gets converted to a generator) and step through it to test different > scenarios. > > I personally think this is a super clean way to do testing since the tests > never interfere with how you actually write the code AND you don't have to > explicitly mock. > > Does anyone have thoughts on this / prior research (or knows about an > existing implementation of this)? > > Cheers, > Pranay > _______________________________________________ > es-discuss mailing list > es-discuss at mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20171216/1369fb2f/attachment-0001.html>
Hmm, I suppose the stack traces drawback doesn't matter as long you're only using this within the context of testing. The emphasis here is that the actual application code that you run in production is what you wrote (the imperative function calls), but when you 'require' the function into your testing code, it gets transformed into this pure generator function that's easier to test.
I don't see how the state/future of async functions affects this. In fact, in my example, I use an async function to try and show that this transform can even work for async functions (and will convert them to a synchronous generator function rather easily)
Hmm, I suppose the stack traces drawback doesn't matter as long you're only using this within the context of testing. The emphasis here is that the actual application code that you run in production is what you wrote (the imperative function calls), but when you 'require' the function into your testing code, it gets transformed into this pure generator function that's easier to test. I don't see how the state/future of async functions affects this. In fact, in my example, I use an async function to try and show that this transform can even work for async functions (and will convert them to a synchronous generator function rather easily) On Fri, 15 Dec 2017 at 23:14 Isiah Meadows <isiahmeadows at gmail.com> wrote: > You're describing a variant of the interpreter pattern. I've used similar > in task scheduling contexts, and the key drawback is you no longer have > useful stack traces. > > I haven't done any formal research on the approach, though. I will caution > you that async functions are already widely used enough (particularly in > Node) they're unlikely to go anywhere beyond something drastic. > > On Sat, Dec 16, 2017, 00:07 Pranay Prakash <pranay.gp at gmail.com> wrote: > >> Hey all, >> >> I was just reading the docs for `redux-saga` where I encountered a nice >> design pattern for a saga which is (correct me if I'm wrong) a >> regular javascript generator function that yields the intent to call a >> function (instead of actually calling a function) until it goes through all >> the steps. If that doesn't make sense, consider this simple function: >> >> ``` >> async function findFriends() { >> const myId = getMyID(); >> const myUser = await fetchUser(myId); >> return myUser.friends; >> } >> ``` >> >> instead of actually making the function calls needed, we can instead have >> a function that does something like this: >> >> >> ``` >> function* findFriends() { >> const myId = yield { fn: getMyID }; >> const myUser = yield { fn: fetchUser, args: [myId] }; >> return myUser.friends; >> } >> ``` >> >> This is a pure generator function that doesn't actually do anything, but >> has all the necessary information to recreate the original function[1] (or >> have a library "trampoline" through the function and make all the necessary >> calls for you) >> >> A HUGE plus of the second version of the function is that it's *easily* >> testable (unlike the first one). Pure functions are easier to test. Testing >> this is simply a matter of calling `.next()`, getting the "intent", making >> sure the right intent was yielded (good enough for unit testing) and >> calling `.next()` again with the "mock" value, you want to return and >> continue to test. >> >> An observation to make here is that you can transform the original >> version of this code (easy, normal code to write) to the latter (easy code >> to test). So, what about having some sort of babel transform perhaps that >> can convert the first to the second but only in the context of unit tests. >> You write code the normal way as you would for your application and don't >> worry about test suite implementation details (mocking/dependency >> injection/etc.), and when you want to test, simply import your function >> (which gets converted to a generator) and step through it to test different >> scenarios. >> >> I personally think this is a super clean way to do testing since >> the tests never interfere with how you actually write the code AND you >> don't have to explicitly mock. >> >> Does anyone have thoughts on this / prior research (or knows about an >> existing implementation of this)? >> >> Cheers, >> Pranay >> > _______________________________________________ >> es-discuss mailing list >> es-discuss at mozilla.org >> https://mail.mozilla.org/listinfo/es-discuss >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20171216/d0a47fc8/attachment.html>
I think what you're looking for is dependency injection, which is simply
the functions you care about passed as an argument rather than sent from
yield
. In your case, the injections would be recording what method is
called when and with what, but that's what I typically do. As an added
bonus, I don't need to rely on how the functions are structured - I can
even record actions done from iterables. (That's one area yours won't work.)
The only catch is that you can't asynchronously block, but that's far more rare in practice (automated testing doesn't need it, and you can set breakpoints when debugging, removing the need to asynchronously hook into it).
I think what you're looking for is dependency injection, which is simply the functions you care about passed as an argument rather than sent from `yield`. In your case, the injections would be recording what method is called when and with what, but that's what I typically do. As an added bonus, I don't need to rely on how the functions are structured - I can even record actions done from iterables. (That's one area yours won't work.) The only catch is that you can't asynchronously block, but that's far more rare in practice (automated testing doesn't need it, and you can set breakpoints when debugging, removing the need to asynchronously hook into it). On Sat, Dec 16, 2017, 00:19 Pranay Prakash <pranay.gp at gmail.com> wrote: > Hmm, I suppose the stack traces drawback doesn't matter as long you're > only using this within the context of testing. The emphasis here is that > the actual application code that you run in production is what you wrote > (the imperative function calls), but when you 'require' the function into > your testing code, it gets transformed into this pure generator function > that's easier to test. > > I don't see how the state/future of async functions affects this. In fact, > in my example, I use an async function to try and show that this transform > can even work for async functions (and will convert them to a synchronous > generator function rather easily) > > On Fri, 15 Dec 2017 at 23:14 Isiah Meadows <isiahmeadows at gmail.com> wrote: > >> You're describing a variant of the interpreter pattern. I've used similar >> in task scheduling contexts, and the key drawback is you no longer have >> useful stack traces. >> >> I haven't done any formal research on the approach, though. I will >> caution you that async functions are already widely used enough >> (particularly in Node) they're unlikely to go anywhere beyond something >> drastic. >> >> On Sat, Dec 16, 2017, 00:07 Pranay Prakash <pranay.gp at gmail.com> wrote: >> >>> Hey all, >>> >>> I was just reading the docs for `redux-saga` where I encountered a nice >>> design pattern for a saga which is (correct me if I'm wrong) a >>> regular javascript generator function that yields the intent to call a >>> function (instead of actually calling a function) until it goes through all >>> the steps. If that doesn't make sense, consider this simple function: >>> >>> ``` >>> async function findFriends() { >>> const myId = getMyID(); >>> const myUser = await fetchUser(myId); >>> return myUser.friends; >>> } >>> ``` >>> >>> instead of actually making the function calls needed, we can instead >>> have a function that does something like this: >>> >>> >>> ``` >>> function* findFriends() { >>> const myId = yield { fn: getMyID }; >>> const myUser = yield { fn: fetchUser, args: [myId] }; >>> return myUser.friends; >>> } >>> ``` >>> >>> This is a pure generator function that doesn't actually do anything, but >>> has all the necessary information to recreate the original function[1] (or >>> have a library "trampoline" through the function and make all the necessary >>> calls for you) >>> >>> A HUGE plus of the second version of the function is that it's *easily* >>> testable (unlike the first one). Pure functions are easier to test. Testing >>> this is simply a matter of calling `.next()`, getting the "intent", making >>> sure the right intent was yielded (good enough for unit testing) and >>> calling `.next()` again with the "mock" value, you want to return and >>> continue to test. >>> >>> An observation to make here is that you can transform the original >>> version of this code (easy, normal code to write) to the latter (easy code >>> to test). So, what about having some sort of babel transform perhaps that >>> can convert the first to the second but only in the context of unit tests. >>> You write code the normal way as you would for your application and don't >>> worry about test suite implementation details (mocking/dependency >>> injection/etc.), and when you want to test, simply import your function >>> (which gets converted to a generator) and step through it to test different >>> scenarios. >>> >>> I personally think this is a super clean way to do testing since >>> the tests never interfere with how you actually write the code AND you >>> don't have to explicitly mock. >>> >>> Does anyone have thoughts on this / prior research (or knows about an >>> existing implementation of this)? >>> >>> Cheers, >>> Pranay >>> >> _______________________________________________ >>> es-discuss mailing list >>> es-discuss at mozilla.org >>> https://mail.mozilla.org/listinfo/es-discuss >>> >> -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20171216/07535c6b/attachment.html>
I don't understand what you mean by "record actions done by iterables". Could you please expand on that :)
My main reason for being weary of dependency injection is that I don't want to change how I write application code just to make it more testable. That's a pretty well shared concern about dependency injection whereby it affects the entire structure of your codebase, or adds additional arguments to simple functions for the sole purpose of testing it.
With the method I'm proposing, you wouldn't have to change how you code at all, and I think that's really nice
I don't understand what you mean by "record actions done by iterables". Could you please expand on that :) My main reason for being weary of dependency injection is that I don't want to change how I write application code just to make it more testable. That's a pretty well shared concern about dependency injection whereby it affects the entire structure of your codebase, or adds additional arguments to simple functions for the sole purpose of testing it. With the method I'm proposing, you wouldn't have to change how you code at all, and I think that's really nice On Sat, 16 Dec 2017, 10:32 Isiah Meadows, <isiahmeadows at gmail.com> wrote: > I think what you're looking for is dependency injection, which is simply > the functions you care about passed as an argument rather than sent from > `yield`. In your case, the injections would be recording what method is > called when and with what, but that's what I typically do. As an added > bonus, I don't need to rely on how the functions are structured - I can > even record actions done from iterables. (That's one area yours won't work.) > > The only catch is that you can't asynchronously block, but that's far more > rare in practice (automated testing doesn't need it, and you can set > breakpoints when debugging, removing the need to asynchronously hook into > it). > > On Sat, Dec 16, 2017, 00:19 Pranay Prakash <pranay.gp at gmail.com> wrote: > >> Hmm, I suppose the stack traces drawback doesn't matter as long you're >> only using this within the context of testing. The emphasis here is that >> the actual application code that you run in production is what you wrote >> (the imperative function calls), but when you 'require' the function into >> your testing code, it gets transformed into this pure generator function >> that's easier to test. >> >> I don't see how the state/future of async functions affects this. In >> fact, in my example, I use an async function to try and show that this >> transform can even work for async functions (and will convert them to a >> synchronous generator function rather easily) >> >> On Fri, 15 Dec 2017 at 23:14 Isiah Meadows <isiahmeadows at gmail.com> >> wrote: >> >>> You're describing a variant of the interpreter pattern. I've used >>> similar in task scheduling contexts, and the key drawback is you no longer >>> have useful stack traces. >>> >>> I haven't done any formal research on the approach, though. I will >>> caution you that async functions are already widely used enough >>> (particularly in Node) they're unlikely to go anywhere beyond something >>> drastic. >>> >>> On Sat, Dec 16, 2017, 00:07 Pranay Prakash <pranay.gp at gmail.com> wrote: >>> >>>> Hey all, >>>> >>>> I was just reading the docs for `redux-saga` where I encountered a nice >>>> design pattern for a saga which is (correct me if I'm wrong) a >>>> regular javascript generator function that yields the intent to call a >>>> function (instead of actually calling a function) until it goes through all >>>> the steps. If that doesn't make sense, consider this simple function: >>>> >>>> ``` >>>> async function findFriends() { >>>> const myId = getMyID(); >>>> const myUser = await fetchUser(myId); >>>> return myUser.friends; >>>> } >>>> ``` >>>> >>>> instead of actually making the function calls needed, we can instead >>>> have a function that does something like this: >>>> >>>> >>>> ``` >>>> function* findFriends() { >>>> const myId = yield { fn: getMyID }; >>>> const myUser = yield { fn: fetchUser, args: [myId] }; >>>> return myUser.friends; >>>> } >>>> ``` >>>> >>>> This is a pure generator function that doesn't actually do anything, >>>> but has all the necessary information to recreate the original function[1] >>>> (or have a library "trampoline" through the function and make all the >>>> necessary calls for you) >>>> >>>> A HUGE plus of the second version of the function is that it's *easily* >>>> testable (unlike the first one). Pure functions are easier to test. Testing >>>> this is simply a matter of calling `.next()`, getting the "intent", making >>>> sure the right intent was yielded (good enough for unit testing) and >>>> calling `.next()` again with the "mock" value, you want to return and >>>> continue to test. >>>> >>>> An observation to make here is that you can transform the original >>>> version of this code (easy, normal code to write) to the latter (easy code >>>> to test). So, what about having some sort of babel transform perhaps that >>>> can convert the first to the second but only in the context of unit tests. >>>> You write code the normal way as you would for your application and don't >>>> worry about test suite implementation details (mocking/dependency >>>> injection/etc.), and when you want to test, simply import your function >>>> (which gets converted to a generator) and step through it to test different >>>> scenarios. >>>> >>>> I personally think this is a super clean way to do testing since >>>> the tests never interfere with how you actually write the code AND you >>>> don't have to explicitly mock. >>>> >>>> Does anyone have thoughts on this / prior research (or knows about an >>>> existing implementation of this)? >>>> >>>> Cheers, >>>> Pranay >>>> >>> _______________________________________________ >>>> es-discuss mailing list >>>> es-discuss at mozilla.org >>>> https://mail.mozilla.org/listinfo/es-discuss >>>> >>> -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20171216/4ce8a93b/attachment-0001.html>
You're still changing code as evidenced by your original email.
- With dependency injection, you're changing the call sites (name -> method
call) and argument count or state size (to hold the injection).
- With your idea, you're changing the call sites (name+args to
yield
) and return value (value ->yield
result).
To give you an idea what I mean by dependency injection, take a look at this:
util
uses here: isiahmeadows/thallium/blob/master/lib/cli/run.jsstate.util
uses here: isiahmeadows/thallium/blob/master/lib/cli/loader.js- Main
state.util
definition: isiahmeadows/thallium/blob/master/lib/cli/util.js - Testing
state.util
definition (Mock
class): isiahmeadows/thallium/blob/master/test-util/cli/cli.js
I mean small functional dependency injection, not the boilerplatey
Java-style one. Mine is a bit more complex, because I also am passing
shared state with it (like parsed args and config), but it's just an extra
object property on that state, as defined in the State
constructor in
lib/cli/run.js
.
You're still changing code as evidenced by your original email. - With dependency injection, you're changing the call sites (name -> method call) and argument count or state size (to hold the injection). - With your idea, you're changing the call sites (name+args to `yield`) and return value (value -> `yield` result). To give you an idea what I mean by dependency injection, take a look at this: - `util` uses here: https://github.com/isiahmeadows/thallium/blob/master/lib/cli/run.js - `state.util` uses here: https://github.com/isiahmeadows/thallium/blob/master/lib/cli/loader.js - Main `state.util` definition: https://github.com/isiahmeadows/thallium/blob/master/lib/cli/util.js - Testing `state.util` definition (`Mock` class): https://github.com/isiahmeadows/thallium/blob/master/test-util/cli/cli.js I mean small functional dependency injection, not the boilerplatey Java-style one. Mine is a bit more complex, because I also am passing shared state with it (like parsed args and config), but it's just an extra object property on that state, as defined in the `State` constructor in `lib/cli/run.js`. On Sat, Dec 16, 2017, 11:51 Pranay Prakash <pranay.gp at gmail.com> wrote: > I don't understand what you mean by "record actions done by iterables". > Could you please expand on that :) > > My main reason for being weary of dependency injection is that I don't > want to change how I write application code just to make it more testable. > That's a pretty well shared concern about dependency injection whereby it > affects the entire structure of your codebase, or adds additional arguments > to simple functions for the sole purpose of testing it. > > With the method I'm proposing, you wouldn't have to change how you code at > all, and I think that's really nice > > On Sat, 16 Dec 2017, 10:32 Isiah Meadows, <isiahmeadows at gmail.com> wrote: > >> I think what you're looking for is dependency injection, which is simply >> the functions you care about passed as an argument rather than sent from >> `yield`. In your case, the injections would be recording what method is >> called when and with what, but that's what I typically do. As an added >> bonus, I don't need to rely on how the functions are structured - I can >> even record actions done from iterables. (That's one area yours won't work.) >> >> The only catch is that you can't asynchronously block, but that's far >> more rare in practice (automated testing doesn't need it, and you can set >> breakpoints when debugging, removing the need to asynchronously hook into >> it). >> >> On Sat, Dec 16, 2017, 00:19 Pranay Prakash <pranay.gp at gmail.com> wrote: >> >>> Hmm, I suppose the stack traces drawback doesn't matter as long you're >>> only using this within the context of testing. The emphasis here is that >>> the actual application code that you run in production is what you wrote >>> (the imperative function calls), but when you 'require' the function into >>> your testing code, it gets transformed into this pure generator function >>> that's easier to test. >>> >>> I don't see how the state/future of async functions affects this. In >>> fact, in my example, I use an async function to try and show that this >>> transform can even work for async functions (and will convert them to a >>> synchronous generator function rather easily) >>> >>> On Fri, 15 Dec 2017 at 23:14 Isiah Meadows <isiahmeadows at gmail.com> >>> wrote: >>> >>>> You're describing a variant of the interpreter pattern. I've used >>>> similar in task scheduling contexts, and the key drawback is you no longer >>>> have useful stack traces. >>>> >>>> I haven't done any formal research on the approach, though. I will >>>> caution you that async functions are already widely used enough >>>> (particularly in Node) they're unlikely to go anywhere beyond something >>>> drastic. >>>> >>>> On Sat, Dec 16, 2017, 00:07 Pranay Prakash <pranay.gp at gmail.com> wrote: >>>> >>>>> Hey all, >>>>> >>>>> I was just reading the docs for `redux-saga` where I encountered a >>>>> nice design pattern for a saga which is (correct me if I'm wrong) a >>>>> regular javascript generator function that yields the intent to call a >>>>> function (instead of actually calling a function) until it goes through all >>>>> the steps. If that doesn't make sense, consider this simple function: >>>>> >>>>> ``` >>>>> async function findFriends() { >>>>> const myId = getMyID(); >>>>> const myUser = await fetchUser(myId); >>>>> return myUser.friends; >>>>> } >>>>> ``` >>>>> >>>>> instead of actually making the function calls needed, we can instead >>>>> have a function that does something like this: >>>>> >>>>> >>>>> ``` >>>>> function* findFriends() { >>>>> const myId = yield { fn: getMyID }; >>>>> const myUser = yield { fn: fetchUser, args: [myId] }; >>>>> return myUser.friends; >>>>> } >>>>> ``` >>>>> >>>>> This is a pure generator function that doesn't actually do anything, >>>>> but has all the necessary information to recreate the original function[1] >>>>> (or have a library "trampoline" through the function and make all the >>>>> necessary calls for you) >>>>> >>>>> A HUGE plus of the second version of the function is that it's >>>>> *easily* testable (unlike the first one). Pure functions are easier to >>>>> test. Testing this is simply a matter of calling `.next()`, getting the >>>>> "intent", making sure the right intent was yielded (good enough for unit >>>>> testing) and calling `.next()` again with the "mock" value, you want to >>>>> return and continue to test. >>>>> >>>>> An observation to make here is that you can transform the original >>>>> version of this code (easy, normal code to write) to the latter (easy code >>>>> to test). So, what about having some sort of babel transform perhaps that >>>>> can convert the first to the second but only in the context of unit tests. >>>>> You write code the normal way as you would for your application and don't >>>>> worry about test suite implementation details (mocking/dependency >>>>> injection/etc.), and when you want to test, simply import your function >>>>> (which gets converted to a generator) and step through it to test different >>>>> scenarios. >>>>> >>>>> I personally think this is a super clean way to do testing since >>>>> the tests never interfere with how you actually write the code AND you >>>>> don't have to explicitly mock. >>>>> >>>>> Does anyone have thoughts on this / prior research (or knows about an >>>>> existing implementation of this)? >>>>> >>>>> Cheers, >>>>> Pranay >>>>> >>>> _______________________________________________ >>>>> es-discuss mailing list >>>>> es-discuss at mozilla.org >>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>> >>>> -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20171216/23d757bc/attachment.html>
Oh, I'll check out those links. However, the key part of my idea is to NOT change the way write application code.
So, when I described the second version of the codebase (the one with yields), that's not how I want you, the developer, to write code. I simply propose a static code transform that convert the code you originally wrote (first example) to the pure version with yield calls.
The trick here is, the original version of the code will be used when you test your program, but the transformed code is what gets used solely when you're testing (meaning your tests have to account for the code being transformed, but I can image a good library to abstract some of those details away)
I'll have to read up more about the dependency injection links you sent before I can comment further on that comparison between this idea and dependency injection
Oh, I'll check out those links. However, the key part of my idea is to NOT change the way write application code. So, when I described the second version of the codebase (the one with yields), that's not how I want you, the developer, to write code. I simply propose a static code transform that convert the code you originally wrote (first example) to the pure version with yield calls. The trick here is, the original version of the code will be used when you test your program, but the transformed code is what gets used solely when you're testing (meaning your tests have to account for the code being transformed, but I can image a good library to abstract some of those details away) I'll have to read up more about the dependency injection links you sent before I can comment further on that comparison between this idea and dependency injection On Sat, 16 Dec 2017, 11:07 Isiah Meadows, <isiahmeadows at gmail.com> wrote: > You're still changing code as evidenced by your original email. > > - With dependency injection, you're changing the call sites (name -> > method call) and argument count or state size (to hold the injection). > - With your idea, you're changing the call sites (name+args to `yield`) > and return value (value -> `yield` result). > > To give you an idea what I mean by dependency injection, take a look at > this: > > - `util` uses here: > https://github.com/isiahmeadows/thallium/blob/master/lib/cli/run.js > - `state.util` uses here: > https://github.com/isiahmeadows/thallium/blob/master/lib/cli/loader.js > - Main `state.util` definition: > https://github.com/isiahmeadows/thallium/blob/master/lib/cli/util.js > - Testing `state.util` definition (`Mock` class): > https://github.com/isiahmeadows/thallium/blob/master/test-util/cli/cli.js > > I mean small functional dependency injection, not the boilerplatey > Java-style one. Mine is a bit more complex, because I also am passing > shared state with it (like parsed args and config), but it's just an extra > object property on that state, as defined in the `State` constructor in > `lib/cli/run.js`. > > On Sat, Dec 16, 2017, 11:51 Pranay Prakash <pranay.gp at gmail.com> wrote: > >> I don't understand what you mean by "record actions done by iterables". >> Could you please expand on that :) >> >> My main reason for being weary of dependency injection is that I don't >> want to change how I write application code just to make it more testable. >> That's a pretty well shared concern about dependency injection whereby it >> affects the entire structure of your codebase, or adds additional arguments >> to simple functions for the sole purpose of testing it. >> >> With the method I'm proposing, you wouldn't have to change how you code >> at all, and I think that's really nice >> >> On Sat, 16 Dec 2017, 10:32 Isiah Meadows, <isiahmeadows at gmail.com> wrote: >> >>> I think what you're looking for is dependency injection, which is simply >>> the functions you care about passed as an argument rather than sent from >>> `yield`. In your case, the injections would be recording what method is >>> called when and with what, but that's what I typically do. As an added >>> bonus, I don't need to rely on how the functions are structured - I can >>> even record actions done from iterables. (That's one area yours won't work.) >>> >>> The only catch is that you can't asynchronously block, but that's far >>> more rare in practice (automated testing doesn't need it, and you can set >>> breakpoints when debugging, removing the need to asynchronously hook into >>> it). >>> >>> On Sat, Dec 16, 2017, 00:19 Pranay Prakash <pranay.gp at gmail.com> wrote: >>> >>>> Hmm, I suppose the stack traces drawback doesn't matter as long you're >>>> only using this within the context of testing. The emphasis here is that >>>> the actual application code that you run in production is what you wrote >>>> (the imperative function calls), but when you 'require' the function into >>>> your testing code, it gets transformed into this pure generator function >>>> that's easier to test. >>>> >>>> I don't see how the state/future of async functions affects this. In >>>> fact, in my example, I use an async function to try and show that this >>>> transform can even work for async functions (and will convert them to a >>>> synchronous generator function rather easily) >>>> >>>> On Fri, 15 Dec 2017 at 23:14 Isiah Meadows <isiahmeadows at gmail.com> >>>> wrote: >>>> >>>>> You're describing a variant of the interpreter pattern. I've used >>>>> similar in task scheduling contexts, and the key drawback is you no longer >>>>> have useful stack traces. >>>>> >>>>> I haven't done any formal research on the approach, though. I will >>>>> caution you that async functions are already widely used enough >>>>> (particularly in Node) they're unlikely to go anywhere beyond something >>>>> drastic. >>>>> >>>>> On Sat, Dec 16, 2017, 00:07 Pranay Prakash <pranay.gp at gmail.com> >>>>> wrote: >>>>> >>>>>> Hey all, >>>>>> >>>>>> I was just reading the docs for `redux-saga` where I encountered a >>>>>> nice design pattern for a saga which is (correct me if I'm wrong) a >>>>>> regular javascript generator function that yields the intent to call a >>>>>> function (instead of actually calling a function) until it goes through all >>>>>> the steps. If that doesn't make sense, consider this simple function: >>>>>> >>>>>> ``` >>>>>> async function findFriends() { >>>>>> const myId = getMyID(); >>>>>> const myUser = await fetchUser(myId); >>>>>> return myUser.friends; >>>>>> } >>>>>> ``` >>>>>> >>>>>> instead of actually making the function calls needed, we can instead >>>>>> have a function that does something like this: >>>>>> >>>>>> >>>>>> ``` >>>>>> function* findFriends() { >>>>>> const myId = yield { fn: getMyID }; >>>>>> const myUser = yield { fn: fetchUser, args: [myId] }; >>>>>> return myUser.friends; >>>>>> } >>>>>> ``` >>>>>> >>>>>> This is a pure generator function that doesn't actually do anything, >>>>>> but has all the necessary information to recreate the original function[1] >>>>>> (or have a library "trampoline" through the function and make all the >>>>>> necessary calls for you) >>>>>> >>>>>> A HUGE plus of the second version of the function is that it's >>>>>> *easily* testable (unlike the first one). Pure functions are easier to >>>>>> test. Testing this is simply a matter of calling `.next()`, getting the >>>>>> "intent", making sure the right intent was yielded (good enough for unit >>>>>> testing) and calling `.next()` again with the "mock" value, you want to >>>>>> return and continue to test. >>>>>> >>>>>> An observation to make here is that you can transform the original >>>>>> version of this code (easy, normal code to write) to the latter (easy code >>>>>> to test). So, what about having some sort of babel transform perhaps that >>>>>> can convert the first to the second but only in the context of unit tests. >>>>>> You write code the normal way as you would for your application and don't >>>>>> worry about test suite implementation details (mocking/dependency >>>>>> injection/etc.), and when you want to test, simply import your function >>>>>> (which gets converted to a generator) and step through it to test different >>>>>> scenarios. >>>>>> >>>>>> I personally think this is a super clean way to do testing since >>>>>> the tests never interfere with how you actually write the code AND you >>>>>> don't have to explicitly mock. >>>>>> >>>>>> Does anyone have thoughts on this / prior research (or knows about an >>>>>> existing implementation of this)? >>>>>> >>>>>> Cheers, >>>>>> Pranay >>>>>> >>>>> _______________________________________________ >>>>>> es-discuss mailing list >>>>>> es-discuss at mozilla.org >>>>>> https://mail.mozilla.org/listinfo/es-discuss >>>>>> >>>>> -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20171216/94e8e555/attachment.html>
To add to this --- a Monad is a useful concept for separating the description of execution versus actually running the execution: www.wikiwand.com/en/Monad_(functional_programming)
To add to this --- a Monad is a useful concept for separating the description of execution versus actually running the execution: https://www.wikiwand.com/en/Monad_(functional_programming) -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20171218/d36a2547/attachment.html>
Yup, that applies perfectly to what I'm describing Dante (and is some great reading) :)
Yup, that applies perfectly to what I'm describing Dante (and is some great reading) :) On Mon, 18 Dec 2017 at 13:38 dante federici <c.dante.federici at gmail.com> wrote: > To add to this --- a Monad is a useful concept for separating the > description of execution versus actually running the execution: > https://www.wikiwand.com/en/Monad_(functional_programming) > _______________________________________________ > es-discuss mailing list > es-discuss at mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20171218/44af04c8/attachment.html>
Hey all,
I was just reading the docs for
redux-saga
where I encountered a nice design pattern for a saga which is (correct me if I'm wrong) a regular javascript generator function that yields the intent to call a function (instead of actually calling a function) until it goes through all the steps. If that doesn't make sense, consider this simple function:instead of actually making the function calls needed, we can instead have a function that does something like this:
This is a pure generator function that doesn't actually do anything, but has all the necessary information to recreate the original function[1] (or have a library "trampoline" through the function and make all the necessary calls for you)
A HUGE plus of the second version of the function is that it's easily testable (unlike the first one). Pure functions are easier to test. Testing this is simply a matter of calling
.next()
, getting the "intent", making sure the right intent was yielded (good enough for unit testing) and calling.next()
again with the "mock" value, you want to return and continue to test.An observation to make here is that you can transform the original version of this code (easy, normal code to write) to the latter (easy code to test). So, what about having some sort of babel transform perhaps that can convert the first to the second but only in the context of unit tests. You write code the normal way as you would for your application and don't worry about test suite implementation details (mocking/dependency injection/etc.), and when you want to test, simply import your function (which gets converted to a generator) and step through it to test different scenarios.
I personally think this is a super clean way to do testing since the tests never interfere with how you actually write the code AND you don't have to explicitly mock.
Does anyone have thoughts on this / prior research (or knows about an existing implementation of this)?