Ask HN: What's your favorite software testing framework and why?

53 points by lucgagan ↗ HN
Seeing so many different testing frameworks in every programming language. Would love to understand how this happened, when they all seemingly do the same thing. Would love to know which is your favorite and what makes it different than whatever else popular framework in your language?

72 comments

[ 3.8 ms ] story [ 131 ms ] thread
I'm a fan of RSpec and any BDD (Behavioral Driven Development) type of framework. You are correct that they all do the same thing, but for me everything boils down to which one provides the quickest understanding of the code by the reader.

There's another testing library in Ruby that is called minitest. The syntax is different, for example you would write "assert_equal num1, numb2". My problem with this is that you don't know which is the expected vs the actual values. Is it the first or second argument? On the otherhand, my framework of choice, RSpec would write this as "expect(num1).to eq(numb2)". The syntactic sugar alone makes this easier to understand.

After years of development, RSpec is what made me stop testing the code manually. Rspec is one of those things that made me a better developer.
> ...Would love to understand how this happened...

Well, why are we using cut-down Chrome with a bunch of js scripts and pretend it's a native app or a web server?

My context: Backend Java/Kotlin dev. Don't really need any fancy test framework, JUnit 5 is absolutely fine for executing the tests. But I do use a bunch of additional tools and libraries.

testcontainers so I can write proper integration tests instead of mocking dependencies away.

AssertJ for fancy assertions with great failure messages.

Mockito for the rare mocking/spy usage.

Awaitility for the rare async/timing based test.

When there is a Frontend (templated or SPA, doesn't matter), I like writing the tests with Selenide assertions and using its Selenium integration to run against a large array of browsers + variants (Chrome, FF, Mobile etc) launched via testcontainers.

Pytest is pretty nice, especially when you get it configured with a bunch of plugins for the libraries/framework you're using, and write your own stack of injectors to test your application in different ways. The very lightweight dependency injection is a joy to use once you get used to it. Add in nice output formatting from the built in asserts, and plugins like pytest-randomly that encourage good test writing, and it's really a great setup for almost all Python testing needs. I'd even suggest that it's a great setup for non-Python testing if you want automation around running binaries that you're treating as a black box.
I really like the fixture system where fixtures can "expand" to multiple values. This is a great way to test combinations of configurations.
Exactly. I love writing tests that generate themselves. Things like expanding every value in an enum into a new test case so that new enum values are tested automatically.

This stuff isn't a silver bullet, but it can go a long way to commoditising certain kinds of code change such that the dev resource necessary for them mostly disappears. At my previous place of work we had quite a few non-engineers semi-regularly contributing actual Python code. It was mostly things like adding new enum values, or tweaking properties on classes, but we had robust testing that verified all of those sorts of changes in generic ways and automatically generated tests for new pieces, and it was basically hands-off from engineering.

I love pytest, and use it with Hypothesis[0] for property based testing. I also enjoy using pytest-bdd[1]

[0] - https://hypothesis.readthedocs.io/en/latest/

[1] - https://github.com/pytest-dev/pytest-bdd

If you like pytest and BDD I'd encourage you to check out my pytest library: https://github.com/hitchdev/hitchstory

With it you can write integration tests with typesafe YAML which double as user friendly documentation.

It also has inheritance (which gherkin doesnt) - e.g. a "buy golf clubs" story can inherit from "put golf clubs in shopping basket" story which can inherit from a "log in" story, etc.

Thank you. Your library seems nice.

Unfortunately, I have to say it... I hate YAML with a passion, k8s, github actions/gitlab ci, ansible, etc... When I'm doing ops jobs, I feel like I'm coding in YAML.

Btw, this hate for YAML birthed https://linkdd.github.io/tricorder/ :P

I understand the hate. I created StrictYAML, which this tool uses, because of it (see the Norway problem - that was me). Type safety is paramount and regular YAML does not have any type safety.

I also understand the hate from the perspective of tools which misuse YAML - putting loops, variables, conditionals, etc. in like ansible or CI tools do, like your tool complains about. I hate that too and haven't done it. I was somewhat inspired by ansible in the beginning but Ive come to understand that it was the wrong approach for configuration management and that a turing complete language is a better approach (i.e. like your rust tool).

I dont want to throw the baby out with the bathwater though. The syntax is a very clean way of displaying hierarchical data, which high level stories need. Nothing else matches - Gherkin is awful at that.

> I created StrictYAML, which this tool uses, because of it

I'll look more closely into it then :)

> Gherkin is awful at that.

When I use BDD, it's because I'm handed down scenarii written by non-tech people. And it's always in the Gherkin format, then I chose the right Gherkin engine for the right tech stack. That might be cucumber, or another.

>When I use BDD, it's because I'm handed down scenarii written by non-tech people. And it's always in the Gherkin format

Thats interesting. You're the first person I've heard of who actually did that rather than just talking about it.

I was under the impression that this worked ok for simple scenarios but with complex scenarios I think the syntax buckles - either you end up with very long, repetitive scenarios or vague scenarios. Or even both.

> you end up with very long, repetitive scenarios or vague scenarios. Or even both.

Almost correct :D

The BDD scenarii also act as "user stories" in a Scrum/Agile workflow. It was a nice way to re-use what the product owner requested.

my favorite is the one that is most ergonomic for the tools i'm building with
Haskell’s Tasty framework.

It lets me combine HSpec for unit tests and Hedgehog for property tests in the same test files with test auto-discover. For failed properties, it prints the seed.

And it has really pretty, readable output.

I’m using Rust’s tests currently, and the output is just a lot less obvious. E.g. green text when 0 tests are run.

I generally like rspec because:

* The setup pattern with "just in time" variables is amazing.

* It offers some _extremely_ terse tests

* It offers a huge library of plugins.

I tend to use anything that offers property-testing, since tests are much shorter to write and uncover lots more hidden assumptions.

My go-to choices per language are:

- Python: Hypothesis https://hypothesis.readthedocs.io/en/latest (also compatible with PyTest)

- Scala: ScalaCheck https://scalacheck.org (also compatible with ScalaTest)

- Javascript/Typescript: JSVerify https://jsverify.github.io

- Haskell: LazySmallCheck2012 https://github.com/UoYCS-plasma/LazySmallCheck2012/blob/mast...

- When I wrote PHP (over a decade ago) there was no decent property-based test framework, so I cobbled one together https://github.com/Warbo/php-easycheck

All of the above use the same basic setup: tests can make universally-quantified statements (e.g. "for all (x: Int), foo(x) == foo(foo(x))"), then the framework checks that statement for a bunch of different inputs.

Most property-checking frameworks generate data randomly (with more or less sophistication). The Haskell ecosystem is more interesting:

- QuickCheck was one of the first property-testing frameworks, using random genrators.

- SmallCheck came later, which enumerates data instead (e.g. testing a Float might use 0, 1, -1, 2, -2, 0.5, -0.5, etc.). That's cute, but QuickCheck tends to exercise more code paths with each input.

- LazySmallCheck builds up test data on-demand, using Haskell's pervasive laziness. Tests are run with an error as input: if they pass, we're done; if they fail, we're done; if they trigger the error, they're run again with slightly more-defined inputs. For example, if the input is supposed to be a list, we try again with the two forms of list: empty and "cons" (the arguments to cons are both errors, to begin with). This exercises even more code paths for each input.

- LazySmallCheck2012 is a more versatile "update" to LazySmallCheck; in particular, it's able to generate functions.

Not really a framework, but I really like how golang made testing part of the language/stdlib/tooling. Is it perfect? No. Is it pretty good? Yeah.

In terms of frameworks, I am a big fan of testify. [0] Unfortunately it doesn't seem like the testify maintainers want to incorporate generics. [1] I'm going to be releasing a library soon to address that.

I'm also going to be releasing a golang+python+typescript library for doing super cheap/fast database-backed tests. In my last job I found it incredibly useful, it essentially made it ~0 cost to write tests that exercised database-related codepaths and logic, which for most business apps is everything important.

[0] https://github.com/stretchr/testify

[1] https://github.com/stretchr/testify/issues/1147

I'm curious what benefits you would gain in the testify API by adding generics?
It's not that large of a benefit, but when performing tests comparing two objects, I sometimes pass the wrong object type in. Most of the times this is happening, I'm refactoring existing code and updating tests as I go. It would be nice to see all the errors at compile time (and therefore in my editor's problems/quickfix view) rather than having to run the full test suite to find all the tests I forgot to update.

I think it's possible to keep the implementation of testify almost (or exactly) the same, but update the type signatures of the methods to use generics, and shift-left a lot of errors to compile time. Just a nice-to-have.

It's a bit context dependent. Generally prefer tools that allow running multiple tests in parallel because I hate waiting. Unfortunately I'm a few years out of date on JavaScript testing frameworks so I'll abstain from naming specific tools. If it's easy to switch between test frameworks and there's no big differentiating features I would try em all out and use whichever runs fastest.

I think Test Anything Protocol [0] [1] is neat. Anything that takes inspiration from TAP is probably gonna be mostly structurally similar anyway.

It's easier to point out things I dislike in a testing framework. In general, anything that gets too fancy or magical, or assertion frameworks with lots of object chaining to read like natural language.

[0] https://en.wikipedia.org/wiki/Test_Anything_Protocol

[1] https://testanything.org/

You might want to give ava a spin:

https://github.com/avajs/ava/

It has a TAP reporter, but more importantly, as opposed to the more popular solutions, like Jest, the way it achieves parallelism is explained in the docs and won't change anytime soon, thus preventing wonky, hard to debug errors which occur when this part is abstracted away.

The problem with ava (which is great! But it does have problems) is that its concurrency story is deeply tightly coupled to internals you can’t get at, and it misreports timings pretty significantly as a result. It might report single digit millisecond test runs but take about a second to run a full suite anyway, because its concurrency solution has a ton of (unreported) overhead.
I'll take that any day over Jest's "we won't tell you how we do parallelism because no" and errors which don't reproduce with --runInBand that plagued just about every project I've been to using this framework.

Perhaps it's an Angular thing - I don't understand why people keep pushing this combination.

I like the idea of TAP, but one annoyance I found is that most tooling expects the total number of tests to be output up-front, so we can't just "run everything", e.g. looping through all shell scripts in a test/ folder, or just writing a dumb, top-to-bottom script that outputs pass/fail messages.

Even though it's small, the extra structure and effort needed to count tests up-front is basically the same as switching to a "proper" test framework (most of which don't output TAP :( )

i prefer not writing tests and just saying sorry if i ship a bug
(comment deleted)
We had a guy do that. Cost the company $300k (penalty) and they had to eat the cost of the rework (another $50k or so in labor, maybe more). He's not very popular right now as a result.
Your company doesn’t have a QA team?
Do you imagine QA catches everything?
(comment deleted)
It does. But the person responsible for evaluating it was on their way out (had gotten a new job, hadn't put in their notice yet though) and just said, "All good!" to a bunch of things (previously trustworthy and good work, but their work that last month or two was shit).

So to some extent the dev got unlucky, his terrible code (that he didn't test) may have been caught through the normal QA process which the QA willfully lied about doing. But that doesn't absolve him for not testing (even basic testing) his own work.

I’ve found the datadriven[1] testing approach in go to be quite effective. The idea is that you leverage a standardized file structure to construct a little DSL for testing your code. This allows you to write expressive tests that print the state of the code and then look at it. Rewrite is also very powerful.

This is all inspired by the sqllite logic test framework.

[1]: https://github.com/cockroachdb/datadriven

How do I actually try this out / use it? Are there docs or examples anywhere?
Racket's (module+ test ...) by far.
I like Rust unit tests, simply because you don't need to set anything up to use them. Also they can go in the module you're testing, so writing tests for things that are not public is simple.
+1, being able to write tests that access private fields and functions means you never need to expose behavior or data purely for testability
I do like how Rust’s test system ships with Cargo and how tests are local to a private submodule.

I wish the test report output was more comprehensible, and that there was a property test framework that felt more like in Haskell, e.g printing the PRNG seed when a test fails and letting you run with that seed again; it seems like proptest is the best there is, and it’s doing its own thing, kind of like how property tests work in OCaml.

Yeah, it would be nice if you could easily hook into the test runner/reporting stuff.
Vitest - https://vitest.dev/ - for JavaScript

I worked with Jasmine and Jest as well, but Vitest was created recently and to me it seems to have made good improvements over both.

Came here to suggest the same.. great fast running all the things you want a front end text to be

Also can run coverage reports that are usable in ci (I think it exports to clover format but whatever it is there is a Jenkins plugin that works with it)

I’ve worked on countless codebases with thousands of unit tests, integration tests, synthetic tests, automated acceptance tests, automated UI tests, etc.

The only real “test” is whether it works in production. Everything else is a poor substitute and gives you false confidence. It’s absurd the amount of time and effort we spend on writing tests and we still have as many, if not more bugs in our software than we did before writing tests became a religion.

> The only real “test” is whether it works in production. Everything else is a poor substitute and gives you false confidence.

Tests are like bloom filters[1], either they fail and you have a bug (either in code or in the test itself), or they pass and you possibly still have bugs.

As such they only give you false confidence if you're overly naive.

If our tests pass then we know what we know. Doesn't mean we pretend there can't be unknown unknowns, so we still need diligence before rolling out a new version into production.

[1]: https://en.wikipedia.org/wiki/Bloom_filter

If tests are naming you less rather than more productive, you should rethink how you use tests.

(I've seen MANY examples of unproductive tests, they're definitely a very real problem - but it doesn't have to be that way.)

For end to end and frontend tests, Playwright.
What's your biggest pain point when using Playwright?

We've been trying to adopt Playwright but keep having issues with flaky tests.

Yeah flaky tests are a pain, not sure if cypress is any better but other things are so much worse. To deal with flakiness I wait for page loads or network responses or locators before executing actions that might depend on these things.

I think my biggest pain point is just the slow page loads and servers at my company. Also depending on third party services and integrating with them makes it impossible to have a reproducible environment, I can't self-host our salesforce integration in testing environment.

It's been about a year or so now or less for me, but I really enjoyed working with Go's built-in tests. I had not experienced anything else like that before. It was truly incredible to see it as a first-class language CLI feature, but also that it worked alongside your file tree in a designated way.

Now, anything less than such functionality is second or third-rate to me. I can give older languages a pass, but anything not having this moving forward is disappointing now.

For Scala, Specs 2. Just overall well designed, useful, yet flexible API.

I'm looking for something for Node.js. I've used jest, but it's very heavy and does all sorts of magic and is a whole build system. Whereas I just want a test runner with utilities and reporting.

You should check out uvu
A good recommendation on the opposite end of the magic spectrum. It’s also very fast, because it’s so minimal. If you find yourself needing more, ava is a good compromise but it definitely trends into magic territory more rapidly than I’d prefer.
pytest. The way it handles both fixtures and parameterization (and the fact that it doesn't force classes on me) genuinely results in me enjoying writing tests.

I'd love to find a JavaScript testing framework that's as pleasant to work with.

I wrote a bit about those two features here: https://simonwillison.net/2018/Jul/28/documentation-unit-tes... - about half way down.

My favorite was the SUnitToo package for VisualWorks Smalltalk. It was integrated right into the IDE. The code browser was a natural fit for really intuitive feedback for running tests and getting feedback.

The Smalltalk language and SUnit library had a really great signal ratio without a lot of boilerplate that goes with a lot of other xTest frameworks.

Shameless plug — I was the author of said tool.