Found the culprit and it has nothing to do with the test framework: `Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin` which adds commit callbacks for saving and deleting products.

If an exception is thrown in a **commit callback**, `rollback()` is called **additionally** to `commit()`

    private function addCommitCallback(ResourceProduct $productResource, \Closure $proceed, AbstractModel $product)
    {
        try {
            $productResource->beginTransaction();
            $result = $proceed($product);
            $productResource->addCommitCallback(function () use ($product) {
                $this->reindexRow($product->getEntityId());
            });
            $productResource->commit();
        } catch (\Exception $e) {
            $productResource->rollBack();
            throw $e;
        }

        return $result;
    }

In `Magento\Framework\Model\ResourceModel\AbstractResource`, exceptions from the commit callbacks are simply rethrown:

    public function commit()
    {
        $this->getConnection()->commit();
        /**
         * Process after commit callbacks
         */
        if ($this->getConnection()->getTransactionLevel() === 0) {
            $callbacks = CallbackPool::get(spl_object_hash($this->getConnection()));
            try {
                foreach ($callbacks as $callback) {
                    call_user_func($callback);
                }
            } catch (\Exception $e) {
                throw $e;
            }
        }
        return $this;
    }

The two methods together lead to this nasty bug. To fix it, either exceptions from `commit()` or exceptions from the callbacks should probably be wrapped into a different exception type that can be handled differently by the core plugin.

I created an issue on GitHub: https://github.com/magento/magento2/issues/6497

Until this is fixed, "just" make sure that commit callbacks never throw exceptions...