(I'm one of the authors of icontract-hypothesis.)
I find contracts personally super useful as I can express a lot of relationships in the code trivially and have them automatically verified. For example, when this input is None, then that output needs to be positive. Or if you dele the item it should not exist in this and that registry, and some related other items should also not exist any more.
My email is in the commits of the repository, feel free to contact me and we can have a chat if you are interested in a larger picture and more details.
For new code, not so much. The generated tests wouldn't prevent bugs by themselves, but they may uncover bugs if you see assertions that don't make sense. But you have to spend time reviewing the tests.
The tests also don't describe what they are testing in human terms so you have to refactor them before commiting.
They also would end up testing more than necessary. Sometimes the code behavior might be intentionally ambiguous, not expected to matter in the real world and behavior that will likely change in the future that you should not depend on today.
That is a bad thing and should be addressed. It’s code like this that will inevitably cause you pain later and very likely become significant code debt.
From what I gather from the paper, they frame the problem of test generation as a search problem. An evolutionary algorithm randomly mutates a randomly generated test suite. The evolutionary algorithm optimizes for greater branch coverage.
Excerpt from the abstract:
"Our experiments confirm that evolutionary algorithms can outperform random test generation also in the context of Python, and can even alleviate the problem of absent type information to some degree. However, our results demonstrate that dynamic typing nevertheless poses a fundamental issue for test generation, suggesting future work on integrating type inference"
The point of tests for me is to express intent for what the software should do. The code then expresses the details of how we do it.
I could see a use for evolutionary algorithms to probe code for hidden bugs. But even there it seems limited. I definitely see a use for things like Hypothesis, which makes test expression more powerful: https://hypothesis.readthedocs.io/en/latest/
It makes sense to me that this comes from an academic perspective, and not from people who actually make software for a living.
This can be very useful if you are dealing with legacy code bases or just don’t want to write tests yourself.
I agree that if your are going to write the tests, using hypothesis is a very wise decision.
Consider a case when you have a code that takes about 100 input parameters and makes a decision on whether something should or should not happen. The logic that determines the decision is about 1,000 LOC that doesn't divide easily into smaller chunks. The code consists of a lot of different conditions and calculations based on the input parameters and some constants.
Nobody knows for sure or can explain how every single line works or why is it there, but it seems that everything works correctly based on the decision.
Writing tests manually might be feasible, but is very impractical considering that you don't know whether the code is correct or not in the first place. The code is being modified on regular basis: new inputs are added and logic is changed. Any such change would break the manual test. In my experience, nobody will spend an hour studying why the test broke and do the "right" fix. It will most likely be the easiest fix that makes the test pass.
Whilst an automated test suite can at least check if the code may have broken - you'll never know if the break is significant or just an inconsequential side effect of a new piece of data. And if both the code and tests aren't understood, as you say the "right" fix won't be done and something will be added quickly to fix the test.
If no one is able/willing to refactor the code into understandable and testable bits, there's not much left to guarantee the correctness of it.
If, for example, someone intends to refactor the code into something more maintainable and wants tests to prevent any accidental changes in functionality, then this is useful.
If the goal is to catch bugs or to facilitate future development or to meet some test coverage standard then I argue you might be better off not having tests rather than having auto-generated tests that have not been carefully reviewed.
Used that to create a test suite that we could then maintain normally.
Much better than random.
- you don't know whether the code is correct or not
- code is being modified on regular basis... and logic is changed
If someone knows enough about the code to regularly change it, then they should be able to provide some documentation? How else are they able to modify a "correct" function and know that it's still correct?
Can I guess that the code modifiers are quants, or something?
(Although I suppose if someone wanted to generate tests without ever executing them the warning would be relevant.)
But there are a lots of projects out there that don't, and write tests afterward.
I do.
It's usually much, much faster and easier for me to write the tests after I figure out how to make what I want to work. I sometimes need to change the API a little to be more testable, but it's still faster.
In fact, if I write a test first, I have to write it with an abstract idea on how my code will work and need to understand the problem perfectly, which I rarely do. My understanding of the problem and solution grows as I write the code. It helps me ask the right questions, it reveals issues I didn't think about, I shows part of the API I didn't know by heart and above all, it cleans my initial idea of the workflow for this part of the code.
So if I write the test first, it will take a lot of time to think it through, and I will realize later I got it wrong and have to rewrite it anyway. You could argue I need to better define my problem or get better specs, but my experience is that they are always wrong as long as you haven't written any code: "no plan survive the contact with the enemy".
Not to mention you may not have written the untested code in the first place.
So I'd love a tool that can output test boiler plate for me.
> tests should be written from requirements
TDD might be too big of a debate to fit in an HN comment thread :)
Those two checks alone can take you far, specially when compared to the alternative "I tried it once and it worked".
So an automated tool may be the only path forward as far as testing goes. Whether this one adds value or not is hard to say as their documentation is extremely sparse (probably why they made the tool in the first place).
A majority of test suites require knowledge of code structure (and thus to write the tests).
To write the code before tests you either need to dictate some strict form of input/output, and test based on those only; OR, use a test suite that is very flexible.
BDD seems to cheat by writing "tests" that aren't fully executable until you fill in the functional specifics later.
Sometimes you have to honor bugs as part of the (undocumented/evolved/inferred) interface. It could help to discover these, walk through them and see where they can be fixed or where they need to be honored.
It could also help prevent the "rewrite from scratch because I don't understand it" problem. Heck, I've even done that to myself picking up a codebase I haven't touched in 3+ years, sometimes validly, sometimes notsomuch.
I'll have to see how it works next time this happens to me, because this is pure conjecture.
And I'm also wondering, whether there is a return based automatic test generation, that start from the return value, and resolve all variables used and gather all possible return values with its constraint, and feed those to z3 to generate inputs to cover. It seems like it will help with branch explosion by eliminating unused branch, and only focus on branch that is being used.
Edit: it looks like CrossHair[0] is a similar tool that uses Z3 to find counter examples for predicates.
In my experience with unit testing, the process of writing the unit tests is where the most value is derived from. It forces developers to more careful examine use cases and potential inputs for the unit under test (the latter being especially important for Python). Therefore I would never consider something like this for testing new code.
The rest of the value of unit tests mostly comes from validating the scope of a change's impact. If a unit tests which once passed fails after I make a change, it could mean my change affected some other part of the code in a way I did not expect. But in my experience it usually just means I overlooked something in the test suite - not that I broke something in the software itself. Auto-generated unit tests might be useful for validating the scope of a change's impact on old code, but I'd expect most failures to boil down to issues with the tests not getting updated correctly.
Where I imagine auto-generated unit tests being particularly useful is for ensuring functionality is not affected when working on bug fixes, security patches, optimizations, cruft busting, or simple refactoring.
That sounds ominous.
Why do they execute the generated tests though? I mean I'd expect this to go only as far as generating the tests. Also that means that at least someone needs to review the tests.
Based on the input types, it creates a hyperdimensional surface which is then searched for possible output values.
So, it runs your code, and runs it a lot. If your code deletes /, it will delete it a lot.
Is that really a thing Java developers use? I am one, I remember doing a PoC with Jtest (now Parasoft) in 2008, and it was utterly useless, never come across any such since, I'd be genuinely interested to learn more (I'll go duckduckgo now).
https://pynguin.readthedocs.io/en/latest/user/quickstart.htm...
The example is a function which reports the kind of triangle. The generated tests include one that tests that a triangle with sides 12, 12 and... er... None is "isosceles".
Nice wink to Penguin Adventure, brings back very old memories.
- Python forces the `py` affix in words: scrapy, scapy, rpyc
- Python does wordplay on standard library modules' names: pickle => dill
- Emacs and Julia append the file extension to the end of the name: restclient.el, HTTP.jl, JSON.jl
- Emacs does wordplay on the package the extension is based on: git => magit
These are the ones I was most familiar with. If the replies to your comment provide more insights, I may be able to extend that part of the blog post :)
https://www.obeythetestinggoat.com/pages/book.html#toc
If you feel that TDD is too much there is a book about pytest, tho never really focused on it.
Hope it helps,
PS: Ty Harry :)
I disagree with this premise. Testing your code is a great way to (among other benefits) think about the external interface to your software components, and so can help identify whether you have a quality abstraction or not.
(I don't advocate for TDD, even though I get that it might be a workflow that helps some people. But you don't need TDD to get the benefits of writing your own tests.)
Of course, testing is something you have to get "good at", too...