开云体育

ctrl + shift + ? for shortcuts
© 2025 Groups.io

"bottom-up" TDD and common behaviors


 

Here’s some code I have:



Here’s a correctness claim about that code: “any function that returns one or more Animals must ensure that those animals have ‘updatable fields’”. I come from a mockish tradition, in which that claim would be stated directly by an assertion that `put_updatable_fields` would be called and be used as the return value.?

I’ve been trying to do non-mockish testing in this app, and I’ve gotten myself all bollixed up. Without mocks, what would tests for these four functions look like?

I think I once knew the answers to such questions, but I can’t remember them.


 

开云体育

What I’m guessing is that you invent an assertion that, when true of the return value, implies that it has been made “with updatable fields”. So it’s a property of the data rather than a property of how it was created.

Yeah, that probably makes sense. Forget I asked.

On Dec 1, 2019, at 6:07 PM, Brian Marick <marick@...> wrote:

Here’s some code I have:

<Screen Shot 2019-12-01 at 5.55.54 PM.png>


Here’s a correctness claim about that code: “any function that returns one or more Animals must ensure that those animals have ‘updatable fields’”. I come from a mockish tradition, in which that claim would be stated directly by an assertion that `put_updatable_fields` would be called and be used as the return value.?

I’ve been trying to do non-mockish testing in this app, and I’ve gotten myself all bollixed up. Without mocks, what would tests for these four functions look like?

I think I once knew the answers to such questions, but I can’t remember them.


 

The way I test if animals have updatable fields, I try to update a field and check that it updated.

Alan Baljeu
alanbaljeu@...



On Sunday, December 1, 2019, 07:16:01 p.m. EST, Brian Marick <marick@...> wrote:


What I’m guessing is that you invent an assertion that, when true of the return value, implies that it has been made “with updatable fields”. So it’s a property of the data rather than a property of how it was created.

Yeah, that probably makes sense. Forget I asked.

On Dec 1, 2019, at 6:07 PM, Brian Marick <marick@...> wrote:

Here’s some code I have:

<Screen Shot 2019-12-01 at 5.55.54 PM.png>


Here’s a correctness claim about that code: “any function that returns one or more Animals must ensure that those animals have ‘updatable fields’”. I come from a mockish tradition, in which that claim would be stated directly by an assertion that `put_updatable_fields` would be called and be used as the return value.?

I’ve been trying to do non-mockish testing in this app, and I’ve gotten myself all bollixed up. Without mocks, what would tests for these four functions look like?

I think I once knew the answers to such questions, but I can’t remember them.


 

开云体育

I was thinking I'd ask the object if it had updatable fields, but it'd depend on an understanding of the problem and solution that I don't presently have.

On Dec 2, 2019, at 7:26 AM, Alan Baljeu via Groups.Io <alanbaljeu@...> wrote:

The way I test if animals have updatable fields, I try to update a field and check that it updated.


Ron Jeffries
If another does not intend offense, it is wrong for me to seek it;
if another does indeed intend offense, it is foolish for me to permit it.
? -- Kelly Easterley


 

Best to test to behaviour, not implementation/capability, I think.

brought to you by the letters A, V, and I
and the number 47


On Mon, Dec 2, 2019 at 2:33 PM Ron Jeffries <ronjeffriesacm@...> wrote:
I was thinking I'd ask the object if it had updatable fields, but it'd depend on an understanding of the problem and solution that I don't presently have.

On Dec 2, 2019, at 7:26 AM, Alan Baljeu via Groups.Io <alanbaljeu@...> wrote:

The way I test if animals have updatable fields, I try to update a field and check that it updated.


Ron Jeffries
If another does not intend offense, it is wrong for me to seek it;
if another does indeed intend offense, it is foolish for me to permit it.
? -- Kelly Easterley


 

开云体育

Yes. And if I didn't mind modifying a field, testing it that way is OK.
If I knew the context I'd have a better idea but as it stands I'm thinking my object must know whether it can modify fields or not. I suppose a modifyField operation might return T/F ...

R

On Dec 2, 2019, at 8:18 AM, Avi Kessner <akessner@...> wrote:

Best to test to behaviour, not implementation/capability, I think.


Ron Jeffries
If it is more than you need, it is waste. -- Andy Seidl


 

Just because an object has a method implemented to "know" whether or not a field is modifiable?does not mean that that method is correctly implemented.? If there is such a method, you would still have to have at least two tests that verifies returns true only when that field is actually modifiable and false only when it is not.??

Unless the application itself needs such a method, I would have a test that gets the fields value, sets it to a different value, and gets the field value to verify it changes.

On Mon, Dec 2, 2019 at 6:30 AM Ron Jeffries <ronjeffriesacm@...> wrote:
Yes. And if I didn't mind modifying a field, testing it that way is OK.
If I knew the context I'd have a better idea but as it stands I'm thinking my object must know whether it can modify fields or not. I suppose a modifyField operation might return T/F ...

R

On Dec 2, 2019, at 8:18 AM, Avi Kessner <akessner@...> wrote:

Best to test to behaviour, not implementation/capability, I think.


Ron Jeffries
If it is more than you need, it is waste. -- Andy Seidl


 

开云体育

If we're gonna be like that, wouldn't you need a test for every modifiable field? :)

On Dec 2, 2019, at 11:16 AM, Steve Gordon <sgordonphd@...> wrote:

Just because an object has a method implemented to "know" whether or not a field is modifiable?does not mean that that method is correctly implemented.? If there is such a method, you would still have to have at least two tests that verifies returns true only when that field is actually modifiable and false only when it is not.??

Unless the application itself needs such a method, I would have a test that gets the fields value, sets it to a different value, and gets the field value to verify it changes.


Ron Jeffries

Master your instrument, master the music, then forget all that shit and just play. -- Charlie Parker


 

What motivates implementing a setter method under TDD unless there is a test that requires it to exist and work?? Extra tests would only be required if we also need to implement is<X>Modifiable() for some field X.


On Mon, Dec 2, 2019 at 12:26 PM Ron Jeffries <ronjeffriesacm@...> wrote:
If we're gonna be like that, wouldn't you need a test for every modifiable field? :)

On Dec 2, 2019, at 11:16 AM, Steve Gordon <sgordonphd@...> wrote:

Just because an object has a method implemented to "know" whether or not a field is modifiable?does not mean that that method is correctly implemented.? If there is such a method, you would still have to have at least two tests that verifies returns true only when that field is actually modifiable and false only when it is not.??

Unless the application itself needs such a method, I would have a test that gets the fields value, sets it to a different value, and gets the field value to verify it changes.


Ron Jeffries

Master your instrument, master the music, then forget all that shit and just play. -- Charlie Parker


 

开云体育

We still don't know the context, but it seems like we have some kind of a thing that either has fields that are sometimes mod and sometimes not, or sometimes has mod fields added to it. That could be built in lots of interesting ways, as with an adapter that covers up the fields making them unmodifiable, or a partner object adapter that knows the secret handshake.

My basic approach would be to ask the question I want to know the answer to, roughly, "are those fields modifiable", and then let the code tell me more about the implementation. If that question became more complex to answer, that might be just fine.

Without building the thing, I cannot know.?

On Dec 2, 2019, at 2:48 PM, Steve Gordon <sgordonphd@...> wrote:

What motivates implementing a setter method under TDD unless there is a test that requires it to exist and work?? Extra tests would only be required if we also need to implement is<X>Modifiable() for some field X.


Ron Jeffries
Perfectionism is the voice of the oppressor -- Anne Lamott


 

开云体育

TL;DR: For various reasons, I was trying to avoid the use of mocks in this app. I’m asking how a particular problem would be nicely solved without them.?

I should note that I have a guideline that a test should contain no words except those that highlight what’s special about it - about what the test is *for*.

------------------


Let me explain the issue better, or at least more. Here is a form:



That represents one row from an `animals` table. It also represents two rows from a `service_gaps` table, specifically two rows with a foreign key that points at the animal.?

Ecto is an Elixir structure-to-relational-table mapper. It’s easy to ask it for the animal plus its service gaps, in which case it does all the appropriate joins and produces a structure that looks like this (with some fields stripped for concision):


That looks different than what the form displays. Somehow, the nil `out_of_service_date` has been turned into the string “never”, and the `span` structure in the service gap has been “flattened” into two strings. That is, you could imagine that what’s actually rendered is a derivative structure like this:


If you imagined that, you’d be wrong. Ecto really wants you to put the actual database fields and also the “virtual” fields that hold form values into a single structure that looks like this:


***Whether it's a good idea for a database mapper to know a bunch of things about how the client works is NOT at issue here. Ecto is what it is.***

When you read an animal from the database, it comes back as the previous structure with the three highlighted lines set to `nil`. That’s suitable for lots of operations on animals, but it’s not suitable for producing a form that can be readily used to update an animal and its service gaps. For that, you need to derive the appropriate values for the highlighted fields (fill them in as shown). Once you do that, it’s *really easy* to write code that takes the result of an HTTP POST and updates the database: atomically applying all the changes from the form data, including creating a new “service gaps” row. (The rather grotty data structure makes for simpler code, which is why I still don’t know what to think about the design of the Ecto library.)

So let’s consider a function that converts an animal `id` into an initialized form. That function is called when a user clicks on a table element like this:


… and it produces an `Animal` structure like the one above. Here’s an implementation of that function:


It has two responsibilities:?

1. Fetch the corresponding animal in the normal way, including the associated service gaps (etc.)?
2. Augment that data to make it suitable for a form.

In a mockish approach, I’d TDD that with pseudocode like this:

? ?updatable(5, “test”) produces `..result..`
? ?provided
? ? ? ?Read.one([id: 5], “test”) produces `..animal..`, and
? ? ? ?Read.put_updatable_fields(`..animal..`) produces `..result..`

(The `..animal..` notation is for what I call “metaconstants”, which are values about which nothing is known except their name and that two instances of the same name refer to the same value. My Midje library for Clojure has metaconstants built in, so you *can’t* do anything else with them. In a dynamically-typed language, it’s easy to use strings or symbols for the same thing. Static typing makes metaconstants much harder.)

I like this test because it doesn't set up specific animal data that gets checked for facts that *imply* that updatability was handled correctly. It says it directly: ?“An updatable animal N is an existing animal that’s read and then modified to be updatable”. I like to think of such tests as being like proofs that use lemmas: *if* animal reading works correctly, and making an animal updatable works correctly, then fetching an updatable animal is works correctly.?

(That is, I’m working at the conceptual level, not the implementation level, so the “you’re just testing implementation” argument against mocks doesn’t apply. After all, *shouldn’t* our implementations be structured around relevant concepts?)

However, for various reasons, I was trying to avoid the use of mocks in this app. So, if you were doing this in a non-mockish way, what tests would you have? (Remember, there are four functions that do pretty much the same thing.)




 

If I understand correctly, the code here is specifically?for doing an Ecto transformation, correct? If you decide not to use Ecto the need for this abstraction is removed?

brought to you by the letters A, V, and I
and the number 47


On Tue, Dec 3, 2019 at 2:18 AM Brian Marick <marick@...> wrote:
TL;DR: For various reasons, I was trying to avoid the use of mocks in this app. I’m asking how a particular problem would be nicely solved without them.?

I should note that I have a guideline that a test should contain no words except those that highlight what’s special about it - about what the test is *for*.

------------------


Let me explain the issue better, or at least more. Here is a form:



That represents one row from an `animals` table. It also represents two rows from a `service_gaps` table, specifically two rows with a foreign key that points at the animal.?

Ecto is an Elixir structure-to-relational-table mapper. It’s easy to ask it for the animal plus its service gaps, in which case it does all the appropriate joins and produces a structure that looks like this (with some fields stripped for concision):


That looks different than what the form displays. Somehow, the nil `out_of_service_date` has been turned into the string “never”, and the `span` structure in the service gap has been “flattened” into two strings. That is, you could imagine that what’s actually rendered is a derivative structure like this:


If you imagined that, you’d be wrong. Ecto really wants you to put the actual database fields and also the “virtual” fields that hold form values into a single structure that looks like this:


***Whether it's a good idea for a database mapper to know a bunch of things about how the client works is NOT at issue here. Ecto is what it is.***

When you read an animal from the database, it comes back as the previous structure with the three highlighted lines set to `nil`. That’s suitable for lots of operations on animals, but it’s not suitable for producing a form that can be readily used to update an animal and its service gaps. For that, you need to derive the appropriate values for the highlighted fields (fill them in as shown). Once you do that, it’s *really easy* to write code that takes the result of an HTTP POST and updates the database: atomically applying all the changes from the form data, including creating a new “service gaps” row. (The rather grotty data structure makes for simpler code, which is why I still don’t know what to think about the design of the Ecto library.)

So let’s consider a function that converts an animal `id` into an initialized form. That function is called when a user clicks on a table element like this:


… and it produces an `Animal` structure like the one above. Here’s an implementation of that function:


It has two responsibilities:?

1. Fetch the corresponding animal in the normal way, including the associated service gaps (etc.)?
2. Augment that data to make it suitable for a form.

In a mockish approach, I’d TDD that with pseudocode like this:

? ?updatable(5, “test”) produces `..result..`
? ?provided
? ? ? ?Read.one([id: 5], “test”) produces `..animal..`, and
? ? ? ?Read.put_updatable_fields(`..animal..`) produces `..result..`

(The `..animal..` notation is for what I call “metaconstants”, which are values about which nothing is known except their name and that two instances of the same name refer to the same value. My Midje library for Clojure has metaconstants built in, so you *can’t* do anything else with them. In a dynamically-typed language, it’s easy to use strings or symbols for the same thing. Static typing makes metaconstants much harder.)

I like this test because it doesn't set up specific animal data that gets checked for facts that *imply* that updatability was handled correctly. It says it directly: ?“An updatable animal N is an existing animal that’s read and then modified to be updatable”. I like to think of such tests as being like proofs that use lemmas: *if* animal reading works correctly, and making an animal updatable works correctly, then fetching an updatable animal is works correctly.?

(That is, I’m working at the conceptual level, not the implementation level, so the “you’re just testing implementation” argument against mocks doesn’t apply. After all, *shouldn’t* our implementations be structured around relevant concepts?)

However, for various reasons, I was trying to avoid the use of mocks in this app. So, if you were doing this in a non-mockish way, what tests would you have? (Remember, there are four functions that do pretty much the same thing.)




 

开云体育

Hi Brian,

I certainly don't know ... I'll think aloud and in terms I understand and see what happens. I know less than nothing about Elixir.

On Dec 2, 2019, at 7:18 PM, Brian Marick <marick@...> wrote:

However, for various reasons, I was trying to avoid the use of mocks in this app. So, if you were doing this in a non-mockish way, what tests would you have? (Remember, there are four functions that do pretty much the same thing.)

I'm pretending that I just sat down to pair with you, all full of total ignorance but great curiosity.

It seems to me that this operation, read and make updatable (maybe form-compatible), would properly be a constructor.?

I guess that updatable! returns an updated animal (or hurls). I guess animal -> is magically the return result of the Read. I guess that functions return the last thing computed and that put_updatable_fields() will return the animal it updates.?

I want a test, probably, for put_updatable_fields, and it needs to check all the fields. Since that takes and returns an animal, it can be tested with a canned empty animal and a canned full one and an irritating compare equal of some kind. But in mocks, I think it'd be much the same, since we have to check each field to see if it has been changed.?

I guess if I were mocking, then I'd just do the mock thing you show that says that updatable! does in fact call the put. Since I wouldn't likely be mocking (Detroit school, y'know), what would I do? I'd write a test that would fail if some should-be-updatable field wasn't updatable. (Which I really think means displayable or something.)

I'm guessing that a non-updatable animal is still a valid animal. (That might be a bug.) It appears that updatable is a pure cosmetic thing to get the form to work, and that the animal has no reason to know whether it is updatable. I'm wondering why they aren't always made updatable out of the box. (Complete Constructor Method). If they were, would that change our thinking on how to do this? I think it might: we don't worry any more.

Anyway, now we're writing four functions which, I guess? could conceivably read animals wrongly, not calling updatable! and we want to be sure that they do?

Tell the truth, since this is a display issue (?), I think I'd just look. If I worry that someone will try to make these four functions more efficient by removing the updatable bit, though, I need tests.

So either A) I'd have only one way to get an animal and it would be updatable, end of story I think, or

B1) write tests for each of those four functions. Do whatever is_updatable checking one does, one or many fields, etc.?
B2) observe duplication and remove it. (def check_is_updatable(animal))?

I'm just about certain that I've missed some key thing here. What is it?

Ron Jeffries
Everything that needs to be said has already been said.
But since no one was listening, everything must be said again. -- Andre Gide


 

开云体育

Yes.

On Dec 3, 2019, at 5:31 AM, Avi Kessner <akessner@...> wrote:

If I understand correctly, the code here is specifically?for doing an Ecto transformation, correct? If you decide not to use Ecto the need for this abstraction is removed?


 

On Dec 3, 2019, at 6:49 AM, Ron Jeffries <ronjeffriesacm@...> wrote:
I hope this makes sense. I have a bad cold, and the old brain is not working well.



TL;DR: You gave me the idea that the non-mocking solution to the problem would be:

1. The `put_updatable_fields` function sets a field that says “this particular structure is known to be updatable”.
2. We believe the value of that field corresponds to reality because of tests.
3. The tests for the N various functions that return updatable animals just check that field.

The disadvantage is that I’d still have to arrange that reading an animal returns an animal that `put_updatable_fields` could handle, rather than a metaconstant. Not sure how I feel about the tradeoffs.

=======


It seems to me that this operation, read and make updatable (maybe form-compatible), would properly be a constructor.
Yes. I at one point had a variety of structures - they’d be subclasses in a language with inheritance - for different animal use cases. That caused various problems that made me decide I wasn’t going to rebel against what (I think) Ecto wants me to do.


I want a test, probably, for put_updatable_fields, and it needs to check all the fields. Since that takes and returns an animal, it can be tested with a canned empty animal and a canned full one and an irritating compare equal of some kind. But in mocks, I think it'd be much the same, since we have to check each field to see if it has been changed.
Yes. I wasn’t explicit, but the two functions called in `updatable!` have to be tested in a non-mockish way.


I guess if I were mocking, then I'd just do the mock thing you show that says that updatable! does in fact call the put. Since I wouldn't likely be mocking (Detroit school, y'know), what would I do? I'd write a test that would fail if some should-be-updatable field wasn't updatable. (Which I really think means displayable or something.)
What I’m now calling “updatable” used to be “showable”, but it turns out that what I really mean is “the animal structure contains populated fields that can both be shown in a form and that, when changed, provoke an Ecto `update` to generate the right SQL to update/create the right table rows.” So “updatable” was a better reminder.


I'm guessing that a non-updatable animal is still a valid animal. (That might be a bug.) It appears that updatable is a pure cosmetic thing to get the form to work, and that the animal has no reason to know whether it is updatable. I'm wondering why they aren't always made updatable out of the box. (Complete Constructor Method). If they were, would that change our thinking on how to do this? I think it might: we don't worry any more.
In effect, the four functions are Complete Constructors. What’s really being tested is that the four Complete Constructors work correctly with the lower level `Read` code. That code can only fetch on-disk data; there has to be another layer of processing that fills in the fields that don’t live on disk.

In a way, part of the issue here is that Ecto encourages you to have partially-constructed objects, anathema in the tradition you and I come from.

For example, when you read an `Animal`, you don’t have to read the entire tree of “included” (linked by a foreign key) structures. If you’re reading an animal for a purpose that has nothing to do with ServiceGaps, you can avoid the join and data transfer of that table’s data. I’ve chosen not to worry about that - this is a small app - but you could imagine an app that needed more efficiency.


Anyway, now we're writing four functions which, I guess? could conceivably read animals wrongly, not calling updatable! and we want to be sure that they do?

Tell the truth, since this is a display issue (?), I think I'd just look.
Yes, though there are some complications here. (Forms don’t actually display Animals, they display Changesets which can include error messages associated with the last attempt to process a field, and I find myself annoyingly prone to forgetting to add the HTML-generating code to show the error. Here, for example, is the code that annotates the `name` field with a message like `[name] is already taken”:

<%= text_input(f, :name) %> <%= error_tag f, :name %> <br/>

I forgot to add the `error_tag` for the service gap fields.

But what’s needed here is self-discipline and manual testing, not error-prevention code. Probably.


If I worry that someone will try to make these four functions more efficient by removing the updatable bit, though, I need tests.

So either A) I'd have only one way to get an animal and it would be updatable, end of story I think, or

B1) write tests for each of those four functions. Do whatever is_updatable checking one does, one or many fields, etc.
B2) observe duplication and remove it. (def check_is_updatable(animal))?

B2 is the solution I would probably choose, see note at the top of the page.


 

By the way. I’m happy to pair with people on this codebase. I’m using it to learn, and pairing would help me learn. And I do hope someday to explain what I learn, and pairing would be practice for that.

This is Elixir code, which is not object-oriented. In practice, I don’t think that makes a huge difference. I’ve found the dynamically-typed functional languages similar enough to OO languages. Giving up on mutable data isn’t a big deal. A class isn’t *that* different from a namespace for a single data structure (like `Animal`) that includes a collection of functions. Instead of writing:

animal.operation(5)

… you write:

Animal.operation(animal, 5)

There’s even usually polymorphic function names (so you can drop the `Animal` and let the runtime figure it out), but without inheritance.


 

开云体育

Very helpful, thanks. I'd pair with you but I don't need a cold. :)

R

On Dec 3, 2019, at 6:50 PM, Brian Marick <marick@...> wrote:

I hope this makes sense. I have a bad cold, and the old brain is not working well.


Ron Jeffries
I'm really pissed off by what people are passing off as "agile" these days.
You may have a red car, but that does not make it a Ferrari.
? -- Steve Hayes


 

So this is definitely required behavior and it will be your own, so it needs to be tested.
On the other hand, it's a very specific implementation detail which could change when the technology changes and we don't want that to impact actual behavior related tests about animals.

What I would do is make a transformation decorator and then test sample data to see that it gets transformed correctly.? If Ecto ever changes, that transformation decorator can do nothing if needed.
You basically need a map of fields that can be changed and the transformation needed for that field.

A bit more work, but it allows for a change in Ecto or a change in understanding of Ecto to not impact the main test suite.

On Wed, Dec 4, 2019, 01:50 Brian Marick <marick@...> wrote:
Yes.

On Dec 3, 2019, at 5:31 AM, Avi Kessner <akessner@...> wrote:

If I understand correctly, the code here is specifically?for doing an Ecto transformation, correct? If you decide not to use Ecto the need for this abstraction is removed?


 

I don't know why, but it seems this thread?is completely separated, at least in my Gmail inbox, from the original Brian's one.
Am I the only one seeing?this? :|

On Wed, 4 Dec 2019 at 07:44, Avi Kessner <akessner@...> wrote:
So this is definitely required behavior and it will be your own, so it needs to be tested.
On the other hand, it's a very specific implementation detail which could change when the technology changes and we don't want that to impact actual behavior related tests about animals.

What I would do is make a transformation decorator and then test sample data to see that it gets transformed correctly.? If Ecto ever changes, that transformation decorator can do nothing if needed.
You basically need a map of fields that can be changed and the transformation needed for that field.

A bit more work, but it allows for a change in Ecto or a change in understanding of Ecto to not impact the main test suite.

On Wed, Dec 4, 2019, 01:50 Brian Marick <marick@...> wrote:
Yes.

On Dec 3, 2019, at 5:31 AM, Avi Kessner <akessner@...> wrote:

If I understand correctly, the code here is specifically?for doing an Ecto transformation, correct? If you decide not to use Ecto the need for this abstraction is removed?



--
?
?


 

I see it as well. I've noticed other threads being split as well.


On Thu, Dec 5, 2019, 6:42 AM Giorgio Vespucci <giorgio.vespucci@...> wrote:
I don't know why, but it seems this thread?is completely separated, at least in my Gmail inbox, from the original Brian's one.
Am I the only one seeing?this? :|

On Wed, 4 Dec 2019 at 07:44, Avi Kessner <akessner@...> wrote:
So this is definitely required behavior and it will be your own, so it needs to be tested.
On the other hand, it's a very specific implementation detail which could change when the technology changes and we don't want that to impact actual behavior related tests about animals.

What I would do is make a transformation decorator and then test sample data to see that it gets transformed correctly.? If Ecto ever changes, that transformation decorator can do nothing if needed.
You basically need a map of fields that can be changed and the transformation needed for that field.

A bit more work, but it allows for a change in Ecto or a change in understanding of Ecto to not impact the main test suite.

On Wed, Dec 4, 2019, 01:50 Brian Marick <marick@...> wrote:
Yes.

On Dec 3, 2019, at 5:31 AM, Avi Kessner <akessner@...> wrote:

If I understand correctly, the code here is specifically?for doing an Ecto transformation, correct? If you decide not to use Ecto the need for this abstraction is removed?



--
?
?