Struggling with cyclical dependencies in unit testsWhat is the value of checking in failing unit tests?Unit test strategy for layered (or derived) method callsAre HSQLDB unit tests an anti pattern?Designing unit tests for a stateful systemWhat's the idea behind mocking data access in unit testsUnit testing implementation vs behaviourHow to reconcile “not mocking what you don't own” with “expectations” in unit tests?Unit testing trivial casesUnit test a generic floating point equality functionDoes it matter how I setup test data when creating unit tests?
Not understanding how the gain works in the 1st stage of an instrumentation amplifier
What are these mathematical groups in U.S. universities?
Can ads on a page read my password?
Sparse matrix processing: flip sign of top-left entries of the matrix
Capacitors with a "/" on schematic
Why do private jets such as Gulfstream fly higher than other civilian jets?
sytemctl status log output
Why does putting a dot after the URL remove login information?
Finish the Mastermind
Can a character take on additional backgrounds, or at least their benefits?
Does the Voyager team use a wrapper (Fortran(77?) to Python) to transmit current commands?
Independent table row spacing
What was the first multiprocessor x86 motherboard?
Does this put me at risk for identity theft?
Does this smartphone photo show Mars just below the Sun?
How to realistically deal with a shield user?
Our group keeps dying during the Lost Mine of Phandelver campaign. What are we doing wrong?
Do other countries guarantee freedoms that the United States does not have?
Looking for a new job because of relocation - is it okay to tell the real reason?
ESTA declined to the US
WordCloud: do not eliminate duplicates
Is it allowed and safe to carry a passenger / non-pilot in the front seat of a small general aviation airplane?
Why do proponents of guns oppose gun competency tests?
Will a paper be retracted if a flaw in released software code invalidates its central idea?
Struggling with cyclical dependencies in unit tests
What is the value of checking in failing unit tests?Unit test strategy for layered (or derived) method callsAre HSQLDB unit tests an anti pattern?Designing unit tests for a stateful systemWhat's the idea behind mocking data access in unit testsUnit testing implementation vs behaviourHow to reconcile “not mocking what you don't own” with “expectations” in unit tests?Unit testing trivial casesUnit test a generic floating point equality functionDoes it matter how I setup test data when creating unit tests?
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;
I'm trying to practice TDD, by using it to develop a simple like Bit Vector. I happen to be using Swift, but this is a language-agnostic question.
My BitVector is a struct that stores a single UInt64, and presents an API over it that lets you treat it like a collection. The details don't matter much, but it's pretty simple. The high 57 bits are storage bits, and the lower 6 bits are "count" bits, which tells you how many of the storage bits actually store a contained value.
So far, I have a handful of very simple capabilities:
- An initializer that constructs empty bit vectors
- A
countproperty of typeInt - An
isEmptyproperty of typeBool - An equality operator (
==). NB: this is a value-equality operator akin toObject.equals()in Java, not a reference equality operator like==in Java.
I'm running into a bunch of cyclical dependancies:
The unit test that tests my initializer need to verify that the newly constructed
BitVector. It can do so in one of 3 ways:- Check
bv.count == 0 - Check
bv.isEmpty == true - Check that
bv == knownEmptyBitVector
Method 1 relies on
count, method 2 relies onisEmpty(which itself relies oncount, so there's no point using it), method 3 relies on==. In any case, I can't test my initializer in isolation.- Check
The test for
countneeds to operate on something, which inevitably tests my initializer(s)The implementation of
isEmptyrelies oncountThe implementation of
==relies oncount.
I was able to partly solve this problem by introducing a private API that constructs a BitVector from an existing bit pattern (as a UInt64). This allowed me to initialize values without testing any other initializers, so that I could "boot strap" my way up.
For my unit tests to truly be unit tests, I find myself doing a bunch of hacks, which complicate my prod and test code substantially.
How exactly do you get around these sorts of issues?
unit-testing tdd swift-language
add a comment |
I'm trying to practice TDD, by using it to develop a simple like Bit Vector. I happen to be using Swift, but this is a language-agnostic question.
My BitVector is a struct that stores a single UInt64, and presents an API over it that lets you treat it like a collection. The details don't matter much, but it's pretty simple. The high 57 bits are storage bits, and the lower 6 bits are "count" bits, which tells you how many of the storage bits actually store a contained value.
So far, I have a handful of very simple capabilities:
- An initializer that constructs empty bit vectors
- A
countproperty of typeInt - An
isEmptyproperty of typeBool - An equality operator (
==). NB: this is a value-equality operator akin toObject.equals()in Java, not a reference equality operator like==in Java.
I'm running into a bunch of cyclical dependancies:
The unit test that tests my initializer need to verify that the newly constructed
BitVector. It can do so in one of 3 ways:- Check
bv.count == 0 - Check
bv.isEmpty == true - Check that
bv == knownEmptyBitVector
Method 1 relies on
count, method 2 relies onisEmpty(which itself relies oncount, so there's no point using it), method 3 relies on==. In any case, I can't test my initializer in isolation.- Check
The test for
countneeds to operate on something, which inevitably tests my initializer(s)The implementation of
isEmptyrelies oncountThe implementation of
==relies oncount.
I was able to partly solve this problem by introducing a private API that constructs a BitVector from an existing bit pattern (as a UInt64). This allowed me to initialize values without testing any other initializers, so that I could "boot strap" my way up.
For my unit tests to truly be unit tests, I find myself doing a bunch of hacks, which complicate my prod and test code substantially.
How exactly do you get around these sorts of issues?
unit-testing tdd swift-language
20
You are taking a too narrow view on the term "unit".BitVectoris a perfectly fine unit size for unit testing and immediately resolves your issues that public members ofBitVectorneed each other to make meaningful tests.
– Bart van Ingen Schenau
Jul 29 at 9:09
You know too much implementation details up front. Is your development really test-driven?
– herby
Jul 30 at 20:44
@herby No, that's why I'm practicing. Although that seems like a really unattainable standard. I don't thing I've ever programmed anything without a pretty clear mental approximation of what the implementation will entail.
– Alexander
Jul 30 at 20:50
@Alexander You should try to relax that, otherwise it will be test-first, but not test-driven. Just say vague "I'll do a bit vector with one 64bit int as a backing store" and that's it; from that point on do TDD red-green-refactor one after another. Implementation details, as well as API, should emerge from trying to make tests run (the former), and from writing those tests in the first place (the latter).
– herby
Jul 30 at 20:54
add a comment |
I'm trying to practice TDD, by using it to develop a simple like Bit Vector. I happen to be using Swift, but this is a language-agnostic question.
My BitVector is a struct that stores a single UInt64, and presents an API over it that lets you treat it like a collection. The details don't matter much, but it's pretty simple. The high 57 bits are storage bits, and the lower 6 bits are "count" bits, which tells you how many of the storage bits actually store a contained value.
So far, I have a handful of very simple capabilities:
- An initializer that constructs empty bit vectors
- A
countproperty of typeInt - An
isEmptyproperty of typeBool - An equality operator (
==). NB: this is a value-equality operator akin toObject.equals()in Java, not a reference equality operator like==in Java.
I'm running into a bunch of cyclical dependancies:
The unit test that tests my initializer need to verify that the newly constructed
BitVector. It can do so in one of 3 ways:- Check
bv.count == 0 - Check
bv.isEmpty == true - Check that
bv == knownEmptyBitVector
Method 1 relies on
count, method 2 relies onisEmpty(which itself relies oncount, so there's no point using it), method 3 relies on==. In any case, I can't test my initializer in isolation.- Check
The test for
countneeds to operate on something, which inevitably tests my initializer(s)The implementation of
isEmptyrelies oncountThe implementation of
==relies oncount.
I was able to partly solve this problem by introducing a private API that constructs a BitVector from an existing bit pattern (as a UInt64). This allowed me to initialize values without testing any other initializers, so that I could "boot strap" my way up.
For my unit tests to truly be unit tests, I find myself doing a bunch of hacks, which complicate my prod and test code substantially.
How exactly do you get around these sorts of issues?
unit-testing tdd swift-language
I'm trying to practice TDD, by using it to develop a simple like Bit Vector. I happen to be using Swift, but this is a language-agnostic question.
My BitVector is a struct that stores a single UInt64, and presents an API over it that lets you treat it like a collection. The details don't matter much, but it's pretty simple. The high 57 bits are storage bits, and the lower 6 bits are "count" bits, which tells you how many of the storage bits actually store a contained value.
So far, I have a handful of very simple capabilities:
- An initializer that constructs empty bit vectors
- A
countproperty of typeInt - An
isEmptyproperty of typeBool - An equality operator (
==). NB: this is a value-equality operator akin toObject.equals()in Java, not a reference equality operator like==in Java.
I'm running into a bunch of cyclical dependancies:
The unit test that tests my initializer need to verify that the newly constructed
BitVector. It can do so in one of 3 ways:- Check
bv.count == 0 - Check
bv.isEmpty == true - Check that
bv == knownEmptyBitVector
Method 1 relies on
count, method 2 relies onisEmpty(which itself relies oncount, so there's no point using it), method 3 relies on==. In any case, I can't test my initializer in isolation.- Check
The test for
countneeds to operate on something, which inevitably tests my initializer(s)The implementation of
isEmptyrelies oncountThe implementation of
==relies oncount.
I was able to partly solve this problem by introducing a private API that constructs a BitVector from an existing bit pattern (as a UInt64). This allowed me to initialize values without testing any other initializers, so that I could "boot strap" my way up.
For my unit tests to truly be unit tests, I find myself doing a bunch of hacks, which complicate my prod and test code substantially.
How exactly do you get around these sorts of issues?
unit-testing tdd swift-language
unit-testing tdd swift-language
edited Jul 30 at 20:33
Jost
1033 bronze badges
1033 bronze badges
asked Jul 28 at 20:30
AlexanderAlexander
1,2128 silver badges16 bronze badges
1,2128 silver badges16 bronze badges
20
You are taking a too narrow view on the term "unit".BitVectoris a perfectly fine unit size for unit testing and immediately resolves your issues that public members ofBitVectorneed each other to make meaningful tests.
– Bart van Ingen Schenau
Jul 29 at 9:09
You know too much implementation details up front. Is your development really test-driven?
– herby
Jul 30 at 20:44
@herby No, that's why I'm practicing. Although that seems like a really unattainable standard. I don't thing I've ever programmed anything without a pretty clear mental approximation of what the implementation will entail.
– Alexander
Jul 30 at 20:50
@Alexander You should try to relax that, otherwise it will be test-first, but not test-driven. Just say vague "I'll do a bit vector with one 64bit int as a backing store" and that's it; from that point on do TDD red-green-refactor one after another. Implementation details, as well as API, should emerge from trying to make tests run (the former), and from writing those tests in the first place (the latter).
– herby
Jul 30 at 20:54
add a comment |
20
You are taking a too narrow view on the term "unit".BitVectoris a perfectly fine unit size for unit testing and immediately resolves your issues that public members ofBitVectorneed each other to make meaningful tests.
– Bart van Ingen Schenau
Jul 29 at 9:09
You know too much implementation details up front. Is your development really test-driven?
– herby
Jul 30 at 20:44
@herby No, that's why I'm practicing. Although that seems like a really unattainable standard. I don't thing I've ever programmed anything without a pretty clear mental approximation of what the implementation will entail.
– Alexander
Jul 30 at 20:50
@Alexander You should try to relax that, otherwise it will be test-first, but not test-driven. Just say vague "I'll do a bit vector with one 64bit int as a backing store" and that's it; from that point on do TDD red-green-refactor one after another. Implementation details, as well as API, should emerge from trying to make tests run (the former), and from writing those tests in the first place (the latter).
– herby
Jul 30 at 20:54
20
20
You are taking a too narrow view on the term "unit".
BitVectoris a perfectly fine unit size for unit testing and immediately resolves your issues that public members of BitVector need each other to make meaningful tests.– Bart van Ingen Schenau
Jul 29 at 9:09
You are taking a too narrow view on the term "unit".
BitVectoris a perfectly fine unit size for unit testing and immediately resolves your issues that public members of BitVector need each other to make meaningful tests.– Bart van Ingen Schenau
Jul 29 at 9:09
You know too much implementation details up front. Is your development really test-driven?
– herby
Jul 30 at 20:44
You know too much implementation details up front. Is your development really test-driven?
– herby
Jul 30 at 20:44
@herby No, that's why I'm practicing. Although that seems like a really unattainable standard. I don't thing I've ever programmed anything without a pretty clear mental approximation of what the implementation will entail.
– Alexander
Jul 30 at 20:50
@herby No, that's why I'm practicing. Although that seems like a really unattainable standard. I don't thing I've ever programmed anything without a pretty clear mental approximation of what the implementation will entail.
– Alexander
Jul 30 at 20:50
@Alexander You should try to relax that, otherwise it will be test-first, but not test-driven. Just say vague "I'll do a bit vector with one 64bit int as a backing store" and that's it; from that point on do TDD red-green-refactor one after another. Implementation details, as well as API, should emerge from trying to make tests run (the former), and from writing those tests in the first place (the latter).
– herby
Jul 30 at 20:54
@Alexander You should try to relax that, otherwise it will be test-first, but not test-driven. Just say vague "I'll do a bit vector with one 64bit int as a backing store" and that's it; from that point on do TDD red-green-refactor one after another. Implementation details, as well as API, should emerge from trying to make tests run (the former), and from writing those tests in the first place (the latter).
– herby
Jul 30 at 20:54
add a comment |
3 Answers
3
active
oldest
votes
You're worrying about implementation details too much.
It doesn't matter that in your current implementation, isEmpty relies on count (or whatever other relationships you might have): all you should be caring about is the public interface. For example, you can have three tests:
- That a newly initialized object has
count == 0. - That a newly initialized object has
isEmpty == true - That a newly initialized object equals the known empty object.
These are all valid tests, and become especially important if you ever decide to refactor the internals of your class so that isEmpty has a different implementation that doesn't rely on count - so long as your tests all still pass, you know you haven't regressed anything.
Similar stuff applies to your other points - remember to test the public interface, not your internal implementation. You may find TDD useful here, as you'd then be writing the tests you need for isEmpty before you'd written any implementation for it at all.
6
@Alexander You sound like a man in need of a clear definition of unit testing. The best one I know comes from Michael Feathers
– candied_orange
Jul 29 at 1:16
14
@Alexander you are treating each method as an independently testable piece of code. That is the source of your difficulties. These difficulties disappear if you test the object as a whole, without trying to divide it into smaller parts. Dependencies between objects are not comparable with dependencies between methods.
– amon
Jul 29 at 7:36
9
@Alexander "a piece of code" is an arbitrary measurement. Just by initializing a variable you are using many "pieces of code." What matters is that you are testing a cohesive behavioural unit as defined by you.
– Ant P
Jul 29 at 8:40
9
"From what I've read, I got that the impression that if you break only piece of code, only unit tests directly relating to that code should fail." That seems to be a very hard rule to follow. (e.g. if you write a vector class, and you make an error on the index method, you will probably have tons of breakage across all the code that uses that vector class)
– jhominal
Jul 29 at 9:45
4
@Alexander Also, look into the "Arrange, Act, Assert" pattern for tests. Basically, you set up the object in whatever state it needs to be in (Arrange), call the method you're actually testing (Act) and then verify that its state changed according to your expectations. (Assert). Stuff you set up in Arrange would be "preconditions" for the test.
– GalacticCowboy
Jul 29 at 15:18
|
show 5 more comments
How exactly do you get around these sorts of issues?
You revise your thinking on what a "unit test" is.
An object that manages a mutable data in memory is fundamentally a state machine. So any valuable use case is going to, at a minimum, invoke a method to put information into the object, and invoke a method to read a copy of information out of the object. In the interesting use cases, you are also going to be invoking additional methods that change the data structure.
In practice, this often looks like
// GIVEN
obj = new Object(...)
// THEN
assert object.read(...)
or
// GIVEN
obj = new Object(...)
// WHEN
object.change(...)
// THEN
assert object.read(...)
The "unit test" terminology -- well, it has a long history of not being very good.
I call them unit tests, but they don't match the accepted definition of unit tests very well -- Kent Beck, Test Driven Development by Example
Kent wrote the first version of SUnit in 1994, the port to JUnit was in 1998, the first draft of the TDD book was early 2002. The confusion had a lot of time to spread.
The key idea of these tests (more accurately called "programmer tests" or "developer tests") is that the tests are isolated from each other. The tests don't share any mutable data structures, so they can be run concurrently. There are no worries that the tests must be run in a specific order to correctly measure the solution.
The primary use case for these tests is that they are run by the programmer between edits to her own source code. If you are performing the red green refactor protocol, an unexpected RED always indicates a fault in your last edit; you revert that change, verify that the tests are GREEN, and try again. There isn't a lot of advantage in trying to invest in a design where each and every possible bug is caught by only one test.
Of course, is a merge introduces a fault, then finding that fault is no longer trivial. There are various steps you can take to ensure that faults are easy to localize. See
- Parnas: On the criteria to be used in decomposing systems into modules
- Rainsberger: Integrated tests are a scam
- Shore: Testing Without Mocks: A Pattern Language
add a comment |
In general (even if not using TDD) you should strive to write tests as much as possible while pretending you don't know how it is implemented.
If you're actually doing TDD that should already be the case. Your tests are an executable specification of the program.
How the call graph looks underneath the tests is irrelevant, as long as the tests themselves are sensible and well maintained.
I think your problem is your understanding of TDD.
Your problem in my opinion is that you are "mixing" your TDD personas. Your "test", "code", and "refactor" personas operate completely independently of each other, ideally.
In particular your coding and refactoring personas have no obligations to the tests other than to make/keep them running green.
Sure, in principle, it would be best if all tests were orthogonal and independent of each other. But that is not a concern of your other two TDD personas, and it is definitely not a strict or even necessarily realistic hard requirement of your tests. Basically: Don't throw out your common sense feelings about code quality to try to fulfill a requirement that nobody is asking of you.
add a comment |
protected by gnat Jul 30 at 11:25
Thank you for your interest in this question.
Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).
Would you like to answer one of these unanswered questions instead?
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
You're worrying about implementation details too much.
It doesn't matter that in your current implementation, isEmpty relies on count (or whatever other relationships you might have): all you should be caring about is the public interface. For example, you can have three tests:
- That a newly initialized object has
count == 0. - That a newly initialized object has
isEmpty == true - That a newly initialized object equals the known empty object.
These are all valid tests, and become especially important if you ever decide to refactor the internals of your class so that isEmpty has a different implementation that doesn't rely on count - so long as your tests all still pass, you know you haven't regressed anything.
Similar stuff applies to your other points - remember to test the public interface, not your internal implementation. You may find TDD useful here, as you'd then be writing the tests you need for isEmpty before you'd written any implementation for it at all.
6
@Alexander You sound like a man in need of a clear definition of unit testing. The best one I know comes from Michael Feathers
– candied_orange
Jul 29 at 1:16
14
@Alexander you are treating each method as an independently testable piece of code. That is the source of your difficulties. These difficulties disappear if you test the object as a whole, without trying to divide it into smaller parts. Dependencies between objects are not comparable with dependencies between methods.
– amon
Jul 29 at 7:36
9
@Alexander "a piece of code" is an arbitrary measurement. Just by initializing a variable you are using many "pieces of code." What matters is that you are testing a cohesive behavioural unit as defined by you.
– Ant P
Jul 29 at 8:40
9
"From what I've read, I got that the impression that if you break only piece of code, only unit tests directly relating to that code should fail." That seems to be a very hard rule to follow. (e.g. if you write a vector class, and you make an error on the index method, you will probably have tons of breakage across all the code that uses that vector class)
– jhominal
Jul 29 at 9:45
4
@Alexander Also, look into the "Arrange, Act, Assert" pattern for tests. Basically, you set up the object in whatever state it needs to be in (Arrange), call the method you're actually testing (Act) and then verify that its state changed according to your expectations. (Assert). Stuff you set up in Arrange would be "preconditions" for the test.
– GalacticCowboy
Jul 29 at 15:18
|
show 5 more comments
You're worrying about implementation details too much.
It doesn't matter that in your current implementation, isEmpty relies on count (or whatever other relationships you might have): all you should be caring about is the public interface. For example, you can have three tests:
- That a newly initialized object has
count == 0. - That a newly initialized object has
isEmpty == true - That a newly initialized object equals the known empty object.
These are all valid tests, and become especially important if you ever decide to refactor the internals of your class so that isEmpty has a different implementation that doesn't rely on count - so long as your tests all still pass, you know you haven't regressed anything.
Similar stuff applies to your other points - remember to test the public interface, not your internal implementation. You may find TDD useful here, as you'd then be writing the tests you need for isEmpty before you'd written any implementation for it at all.
6
@Alexander You sound like a man in need of a clear definition of unit testing. The best one I know comes from Michael Feathers
– candied_orange
Jul 29 at 1:16
14
@Alexander you are treating each method as an independently testable piece of code. That is the source of your difficulties. These difficulties disappear if you test the object as a whole, without trying to divide it into smaller parts. Dependencies between objects are not comparable with dependencies between methods.
– amon
Jul 29 at 7:36
9
@Alexander "a piece of code" is an arbitrary measurement. Just by initializing a variable you are using many "pieces of code." What matters is that you are testing a cohesive behavioural unit as defined by you.
– Ant P
Jul 29 at 8:40
9
"From what I've read, I got that the impression that if you break only piece of code, only unit tests directly relating to that code should fail." That seems to be a very hard rule to follow. (e.g. if you write a vector class, and you make an error on the index method, you will probably have tons of breakage across all the code that uses that vector class)
– jhominal
Jul 29 at 9:45
4
@Alexander Also, look into the "Arrange, Act, Assert" pattern for tests. Basically, you set up the object in whatever state it needs to be in (Arrange), call the method you're actually testing (Act) and then verify that its state changed according to your expectations. (Assert). Stuff you set up in Arrange would be "preconditions" for the test.
– GalacticCowboy
Jul 29 at 15:18
|
show 5 more comments
You're worrying about implementation details too much.
It doesn't matter that in your current implementation, isEmpty relies on count (or whatever other relationships you might have): all you should be caring about is the public interface. For example, you can have three tests:
- That a newly initialized object has
count == 0. - That a newly initialized object has
isEmpty == true - That a newly initialized object equals the known empty object.
These are all valid tests, and become especially important if you ever decide to refactor the internals of your class so that isEmpty has a different implementation that doesn't rely on count - so long as your tests all still pass, you know you haven't regressed anything.
Similar stuff applies to your other points - remember to test the public interface, not your internal implementation. You may find TDD useful here, as you'd then be writing the tests you need for isEmpty before you'd written any implementation for it at all.
You're worrying about implementation details too much.
It doesn't matter that in your current implementation, isEmpty relies on count (or whatever other relationships you might have): all you should be caring about is the public interface. For example, you can have three tests:
- That a newly initialized object has
count == 0. - That a newly initialized object has
isEmpty == true - That a newly initialized object equals the known empty object.
These are all valid tests, and become especially important if you ever decide to refactor the internals of your class so that isEmpty has a different implementation that doesn't rely on count - so long as your tests all still pass, you know you haven't regressed anything.
Similar stuff applies to your other points - remember to test the public interface, not your internal implementation. You may find TDD useful here, as you'd then be writing the tests you need for isEmpty before you'd written any implementation for it at all.
edited Jul 29 at 18:13
answered Jul 28 at 22:11
Philip KendallPhilip Kendall
7,3173 gold badges24 silver badges30 bronze badges
7,3173 gold badges24 silver badges30 bronze badges
6
@Alexander You sound like a man in need of a clear definition of unit testing. The best one I know comes from Michael Feathers
– candied_orange
Jul 29 at 1:16
14
@Alexander you are treating each method as an independently testable piece of code. That is the source of your difficulties. These difficulties disappear if you test the object as a whole, without trying to divide it into smaller parts. Dependencies between objects are not comparable with dependencies between methods.
– amon
Jul 29 at 7:36
9
@Alexander "a piece of code" is an arbitrary measurement. Just by initializing a variable you are using many "pieces of code." What matters is that you are testing a cohesive behavioural unit as defined by you.
– Ant P
Jul 29 at 8:40
9
"From what I've read, I got that the impression that if you break only piece of code, only unit tests directly relating to that code should fail." That seems to be a very hard rule to follow. (e.g. if you write a vector class, and you make an error on the index method, you will probably have tons of breakage across all the code that uses that vector class)
– jhominal
Jul 29 at 9:45
4
@Alexander Also, look into the "Arrange, Act, Assert" pattern for tests. Basically, you set up the object in whatever state it needs to be in (Arrange), call the method you're actually testing (Act) and then verify that its state changed according to your expectations. (Assert). Stuff you set up in Arrange would be "preconditions" for the test.
– GalacticCowboy
Jul 29 at 15:18
|
show 5 more comments
6
@Alexander You sound like a man in need of a clear definition of unit testing. The best one I know comes from Michael Feathers
– candied_orange
Jul 29 at 1:16
14
@Alexander you are treating each method as an independently testable piece of code. That is the source of your difficulties. These difficulties disappear if you test the object as a whole, without trying to divide it into smaller parts. Dependencies between objects are not comparable with dependencies between methods.
– amon
Jul 29 at 7:36
9
@Alexander "a piece of code" is an arbitrary measurement. Just by initializing a variable you are using many "pieces of code." What matters is that you are testing a cohesive behavioural unit as defined by you.
– Ant P
Jul 29 at 8:40
9
"From what I've read, I got that the impression that if you break only piece of code, only unit tests directly relating to that code should fail." That seems to be a very hard rule to follow. (e.g. if you write a vector class, and you make an error on the index method, you will probably have tons of breakage across all the code that uses that vector class)
– jhominal
Jul 29 at 9:45
4
@Alexander Also, look into the "Arrange, Act, Assert" pattern for tests. Basically, you set up the object in whatever state it needs to be in (Arrange), call the method you're actually testing (Act) and then verify that its state changed according to your expectations. (Assert). Stuff you set up in Arrange would be "preconditions" for the test.
– GalacticCowboy
Jul 29 at 15:18
6
6
@Alexander You sound like a man in need of a clear definition of unit testing. The best one I know comes from Michael Feathers
– candied_orange
Jul 29 at 1:16
@Alexander You sound like a man in need of a clear definition of unit testing. The best one I know comes from Michael Feathers
– candied_orange
Jul 29 at 1:16
14
14
@Alexander you are treating each method as an independently testable piece of code. That is the source of your difficulties. These difficulties disappear if you test the object as a whole, without trying to divide it into smaller parts. Dependencies between objects are not comparable with dependencies between methods.
– amon
Jul 29 at 7:36
@Alexander you are treating each method as an independently testable piece of code. That is the source of your difficulties. These difficulties disappear if you test the object as a whole, without trying to divide it into smaller parts. Dependencies between objects are not comparable with dependencies between methods.
– amon
Jul 29 at 7:36
9
9
@Alexander "a piece of code" is an arbitrary measurement. Just by initializing a variable you are using many "pieces of code." What matters is that you are testing a cohesive behavioural unit as defined by you.
– Ant P
Jul 29 at 8:40
@Alexander "a piece of code" is an arbitrary measurement. Just by initializing a variable you are using many "pieces of code." What matters is that you are testing a cohesive behavioural unit as defined by you.
– Ant P
Jul 29 at 8:40
9
9
"From what I've read, I got that the impression that if you break only piece of code, only unit tests directly relating to that code should fail." That seems to be a very hard rule to follow. (e.g. if you write a vector class, and you make an error on the index method, you will probably have tons of breakage across all the code that uses that vector class)
– jhominal
Jul 29 at 9:45
"From what I've read, I got that the impression that if you break only piece of code, only unit tests directly relating to that code should fail." That seems to be a very hard rule to follow. (e.g. if you write a vector class, and you make an error on the index method, you will probably have tons of breakage across all the code that uses that vector class)
– jhominal
Jul 29 at 9:45
4
4
@Alexander Also, look into the "Arrange, Act, Assert" pattern for tests. Basically, you set up the object in whatever state it needs to be in (Arrange), call the method you're actually testing (Act) and then verify that its state changed according to your expectations. (Assert). Stuff you set up in Arrange would be "preconditions" for the test.
– GalacticCowboy
Jul 29 at 15:18
@Alexander Also, look into the "Arrange, Act, Assert" pattern for tests. Basically, you set up the object in whatever state it needs to be in (Arrange), call the method you're actually testing (Act) and then verify that its state changed according to your expectations. (Assert). Stuff you set up in Arrange would be "preconditions" for the test.
– GalacticCowboy
Jul 29 at 15:18
|
show 5 more comments
How exactly do you get around these sorts of issues?
You revise your thinking on what a "unit test" is.
An object that manages a mutable data in memory is fundamentally a state machine. So any valuable use case is going to, at a minimum, invoke a method to put information into the object, and invoke a method to read a copy of information out of the object. In the interesting use cases, you are also going to be invoking additional methods that change the data structure.
In practice, this often looks like
// GIVEN
obj = new Object(...)
// THEN
assert object.read(...)
or
// GIVEN
obj = new Object(...)
// WHEN
object.change(...)
// THEN
assert object.read(...)
The "unit test" terminology -- well, it has a long history of not being very good.
I call them unit tests, but they don't match the accepted definition of unit tests very well -- Kent Beck, Test Driven Development by Example
Kent wrote the first version of SUnit in 1994, the port to JUnit was in 1998, the first draft of the TDD book was early 2002. The confusion had a lot of time to spread.
The key idea of these tests (more accurately called "programmer tests" or "developer tests") is that the tests are isolated from each other. The tests don't share any mutable data structures, so they can be run concurrently. There are no worries that the tests must be run in a specific order to correctly measure the solution.
The primary use case for these tests is that they are run by the programmer between edits to her own source code. If you are performing the red green refactor protocol, an unexpected RED always indicates a fault in your last edit; you revert that change, verify that the tests are GREEN, and try again. There isn't a lot of advantage in trying to invest in a design where each and every possible bug is caught by only one test.
Of course, is a merge introduces a fault, then finding that fault is no longer trivial. There are various steps you can take to ensure that faults are easy to localize. See
- Parnas: On the criteria to be used in decomposing systems into modules
- Rainsberger: Integrated tests are a scam
- Shore: Testing Without Mocks: A Pattern Language
add a comment |
How exactly do you get around these sorts of issues?
You revise your thinking on what a "unit test" is.
An object that manages a mutable data in memory is fundamentally a state machine. So any valuable use case is going to, at a minimum, invoke a method to put information into the object, and invoke a method to read a copy of information out of the object. In the interesting use cases, you are also going to be invoking additional methods that change the data structure.
In practice, this often looks like
// GIVEN
obj = new Object(...)
// THEN
assert object.read(...)
or
// GIVEN
obj = new Object(...)
// WHEN
object.change(...)
// THEN
assert object.read(...)
The "unit test" terminology -- well, it has a long history of not being very good.
I call them unit tests, but they don't match the accepted definition of unit tests very well -- Kent Beck, Test Driven Development by Example
Kent wrote the first version of SUnit in 1994, the port to JUnit was in 1998, the first draft of the TDD book was early 2002. The confusion had a lot of time to spread.
The key idea of these tests (more accurately called "programmer tests" or "developer tests") is that the tests are isolated from each other. The tests don't share any mutable data structures, so they can be run concurrently. There are no worries that the tests must be run in a specific order to correctly measure the solution.
The primary use case for these tests is that they are run by the programmer between edits to her own source code. If you are performing the red green refactor protocol, an unexpected RED always indicates a fault in your last edit; you revert that change, verify that the tests are GREEN, and try again. There isn't a lot of advantage in trying to invest in a design where each and every possible bug is caught by only one test.
Of course, is a merge introduces a fault, then finding that fault is no longer trivial. There are various steps you can take to ensure that faults are easy to localize. See
- Parnas: On the criteria to be used in decomposing systems into modules
- Rainsberger: Integrated tests are a scam
- Shore: Testing Without Mocks: A Pattern Language
add a comment |
How exactly do you get around these sorts of issues?
You revise your thinking on what a "unit test" is.
An object that manages a mutable data in memory is fundamentally a state machine. So any valuable use case is going to, at a minimum, invoke a method to put information into the object, and invoke a method to read a copy of information out of the object. In the interesting use cases, you are also going to be invoking additional methods that change the data structure.
In practice, this often looks like
// GIVEN
obj = new Object(...)
// THEN
assert object.read(...)
or
// GIVEN
obj = new Object(...)
// WHEN
object.change(...)
// THEN
assert object.read(...)
The "unit test" terminology -- well, it has a long history of not being very good.
I call them unit tests, but they don't match the accepted definition of unit tests very well -- Kent Beck, Test Driven Development by Example
Kent wrote the first version of SUnit in 1994, the port to JUnit was in 1998, the first draft of the TDD book was early 2002. The confusion had a lot of time to spread.
The key idea of these tests (more accurately called "programmer tests" or "developer tests") is that the tests are isolated from each other. The tests don't share any mutable data structures, so they can be run concurrently. There are no worries that the tests must be run in a specific order to correctly measure the solution.
The primary use case for these tests is that they are run by the programmer between edits to her own source code. If you are performing the red green refactor protocol, an unexpected RED always indicates a fault in your last edit; you revert that change, verify that the tests are GREEN, and try again. There isn't a lot of advantage in trying to invest in a design where each and every possible bug is caught by only one test.
Of course, is a merge introduces a fault, then finding that fault is no longer trivial. There are various steps you can take to ensure that faults are easy to localize. See
- Parnas: On the criteria to be used in decomposing systems into modules
- Rainsberger: Integrated tests are a scam
- Shore: Testing Without Mocks: A Pattern Language
How exactly do you get around these sorts of issues?
You revise your thinking on what a "unit test" is.
An object that manages a mutable data in memory is fundamentally a state machine. So any valuable use case is going to, at a minimum, invoke a method to put information into the object, and invoke a method to read a copy of information out of the object. In the interesting use cases, you are also going to be invoking additional methods that change the data structure.
In practice, this often looks like
// GIVEN
obj = new Object(...)
// THEN
assert object.read(...)
or
// GIVEN
obj = new Object(...)
// WHEN
object.change(...)
// THEN
assert object.read(...)
The "unit test" terminology -- well, it has a long history of not being very good.
I call them unit tests, but they don't match the accepted definition of unit tests very well -- Kent Beck, Test Driven Development by Example
Kent wrote the first version of SUnit in 1994, the port to JUnit was in 1998, the first draft of the TDD book was early 2002. The confusion had a lot of time to spread.
The key idea of these tests (more accurately called "programmer tests" or "developer tests") is that the tests are isolated from each other. The tests don't share any mutable data structures, so they can be run concurrently. There are no worries that the tests must be run in a specific order to correctly measure the solution.
The primary use case for these tests is that they are run by the programmer between edits to her own source code. If you are performing the red green refactor protocol, an unexpected RED always indicates a fault in your last edit; you revert that change, verify that the tests are GREEN, and try again. There isn't a lot of advantage in trying to invest in a design where each and every possible bug is caught by only one test.
Of course, is a merge introduces a fault, then finding that fault is no longer trivial. There are various steps you can take to ensure that faults are easy to localize. See
- Parnas: On the criteria to be used in decomposing systems into modules
- Rainsberger: Integrated tests are a scam
- Shore: Testing Without Mocks: A Pattern Language
answered Jul 29 at 12:22
VoiceOfUnreasonVoiceOfUnreason
20k1 gold badge27 silver badges51 bronze badges
20k1 gold badge27 silver badges51 bronze badges
add a comment |
add a comment |
In general (even if not using TDD) you should strive to write tests as much as possible while pretending you don't know how it is implemented.
If you're actually doing TDD that should already be the case. Your tests are an executable specification of the program.
How the call graph looks underneath the tests is irrelevant, as long as the tests themselves are sensible and well maintained.
I think your problem is your understanding of TDD.
Your problem in my opinion is that you are "mixing" your TDD personas. Your "test", "code", and "refactor" personas operate completely independently of each other, ideally.
In particular your coding and refactoring personas have no obligations to the tests other than to make/keep them running green.
Sure, in principle, it would be best if all tests were orthogonal and independent of each other. But that is not a concern of your other two TDD personas, and it is definitely not a strict or even necessarily realistic hard requirement of your tests. Basically: Don't throw out your common sense feelings about code quality to try to fulfill a requirement that nobody is asking of you.
add a comment |
In general (even if not using TDD) you should strive to write tests as much as possible while pretending you don't know how it is implemented.
If you're actually doing TDD that should already be the case. Your tests are an executable specification of the program.
How the call graph looks underneath the tests is irrelevant, as long as the tests themselves are sensible and well maintained.
I think your problem is your understanding of TDD.
Your problem in my opinion is that you are "mixing" your TDD personas. Your "test", "code", and "refactor" personas operate completely independently of each other, ideally.
In particular your coding and refactoring personas have no obligations to the tests other than to make/keep them running green.
Sure, in principle, it would be best if all tests were orthogonal and independent of each other. But that is not a concern of your other two TDD personas, and it is definitely not a strict or even necessarily realistic hard requirement of your tests. Basically: Don't throw out your common sense feelings about code quality to try to fulfill a requirement that nobody is asking of you.
add a comment |
In general (even if not using TDD) you should strive to write tests as much as possible while pretending you don't know how it is implemented.
If you're actually doing TDD that should already be the case. Your tests are an executable specification of the program.
How the call graph looks underneath the tests is irrelevant, as long as the tests themselves are sensible and well maintained.
I think your problem is your understanding of TDD.
Your problem in my opinion is that you are "mixing" your TDD personas. Your "test", "code", and "refactor" personas operate completely independently of each other, ideally.
In particular your coding and refactoring personas have no obligations to the tests other than to make/keep them running green.
Sure, in principle, it would be best if all tests were orthogonal and independent of each other. But that is not a concern of your other two TDD personas, and it is definitely not a strict or even necessarily realistic hard requirement of your tests. Basically: Don't throw out your common sense feelings about code quality to try to fulfill a requirement that nobody is asking of you.
In general (even if not using TDD) you should strive to write tests as much as possible while pretending you don't know how it is implemented.
If you're actually doing TDD that should already be the case. Your tests are an executable specification of the program.
How the call graph looks underneath the tests is irrelevant, as long as the tests themselves are sensible and well maintained.
I think your problem is your understanding of TDD.
Your problem in my opinion is that you are "mixing" your TDD personas. Your "test", "code", and "refactor" personas operate completely independently of each other, ideally.
In particular your coding and refactoring personas have no obligations to the tests other than to make/keep them running green.
Sure, in principle, it would be best if all tests were orthogonal and independent of each other. But that is not a concern of your other two TDD personas, and it is definitely not a strict or even necessarily realistic hard requirement of your tests. Basically: Don't throw out your common sense feelings about code quality to try to fulfill a requirement that nobody is asking of you.
edited Aug 2 at 21:10
answered Jul 30 at 6:46
Tim SeguineTim Seguine
1177 bronze badges
1177 bronze badges
add a comment |
add a comment |
protected by gnat Jul 30 at 11:25
Thank you for your interest in this question.
Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).
Would you like to answer one of these unanswered questions instead?
20
You are taking a too narrow view on the term "unit".
BitVectoris a perfectly fine unit size for unit testing and immediately resolves your issues that public members ofBitVectorneed each other to make meaningful tests.– Bart van Ingen Schenau
Jul 29 at 9:09
You know too much implementation details up front. Is your development really test-driven?
– herby
Jul 30 at 20:44
@herby No, that's why I'm practicing. Although that seems like a really unattainable standard. I don't thing I've ever programmed anything without a pretty clear mental approximation of what the implementation will entail.
– Alexander
Jul 30 at 20:50
@Alexander You should try to relax that, otherwise it will be test-first, but not test-driven. Just say vague "I'll do a bit vector with one 64bit int as a backing store" and that's it; from that point on do TDD red-green-refactor one after another. Implementation details, as well as API, should emerge from trying to make tests run (the former), and from writing those tests in the first place (the latter).
– herby
Jul 30 at 20:54