kgb 7.0 has been released with a handful of new features:
Python 3.10 support
Support for pytest, and other non-unittest-based test frameworks
Snake-case and standalone assertion methods
Workarounds for spying on methods with poorly-implemented decorators
It also drops official support for Python 2.6 and 3.4-3.5 (though these should still work for now, if you need them).
pytest
kgb now provides a spy_agency fixture for pytest unit tests. This will set up a SpyAgency for you, letting you spy on functions and assert calls. For example:
The SpyAgency will be managed for the lifetime of the test, removing spies upon completion.
Snake-case and standalone assertion methods
We’ve provided snake_case versions of all the assertion methods, making for more natural tests. For instance, you can use assert_spy_called_with() instead of assertSpyCalledWith(). The old camelCase versions will continue to exist, though.
You can also call assertion methods without needing to mix in a SpyAgency into your test. Just import from kgb.asserts. This is useful if you have a need to spy on methods and assert them within non-unit test code, or without access to a SpyAgency. For example:
from kgb.asserts import assert_spy_called_with
def check_connection_state(api_connection):
assert_spy_called_with(api_connection.make_request,
url='https://middleman.zyx',
method='POST')
Workarounds for bad decorators
Before, if you were trying to spy on a method (particularly unbound methods) wrapped in a decorator, and that decorator didn’t preserve the function’s original name, you’d hit an error looking up the method.
You can now work around this by passing the original function name when setting up a spy. For example:
kgb 6.1 has been released, featuring some new spy operations and enhancements to existing ones.
kgb is our Python unit test library for crafting function spies, making it easy to check on the calls made during a test or to override their behavior, in ways not possible with Python’s mock.
New spy operations
Two new spy operations, kgb.SpyOpReturnInOrder and kgb.SpyOpRaiseInOrder, have been added. These work like a combination of kgb.SpyOpReturn/kgb.SpyOpRaise and kgb.SpyOpMatchInOrder. Each takes a list of results, and each call will receive the next result in the list, making it really easy to build tests that, somewhere, involve repeated calls to some function.
The kgb.SpyOpMatchInOrder and kgb.SpyOpMatchAny operations can now nest other spy operations, which simplifies returning or raising values, and can even allow for more advanced interactions with a spy.
The call definitions passed now accept an 'op' key pointing to a spy operation instance, which will be invoked if a call matches.
If you’re a regular follower of ChangeLog, you’ll notice we’ve gone from weekly to semi-monthly, and may be wondering what’s going on. Don’t worry, we’ll return to our regularly-scheduled ChangeLog in time.
We’ve been focusing heavily on wrapping up Review Board 4.0 development, testing things internally, and helping many of our support customers get out from under a backlog of internal support requests within their companies.
And just taking care of ourselves during a global pandemic.
So here’s some of what we’ve been busy with lately:
Review Board 4.0 beta and RBTools 2.0 beta preparation
Wrapping up our semester with CANOSP students
Higher Power Pack/RBCommons Trial Lengths
We’ve increased the amount of time you have to give Power Pack or RBCommons a try. Now, when you download a Power Pack license, or sign up for a team on RBCommons, you have two full months to fully explore and use the products.
We’ve applied the new trial period to all existing RBCommons customers who are still in their trial.
If you’re a Power Pack user, and have a trial license, come talk to us for an extension.
New Releases
RBTools 1.0.3
Last month, we released RBTools 1.0.3, which was long overdue. We’re going to try to release RBTools releases more frequently going forward, and we have some good stuff prepared for 1.0.4 for Perforce users coming up soon.
We also have two new releases for some tools we use to help build Review Board: kgb, and introducing babel-plugin-django-gettext.
kgb 5.0
kgb is a Python module that helps with writing unit tests, adding support for function spies. This lets you spy on any function or method, whether in your own code or elsewhere, and track all calls made to the function and inspect the results of those calls.
It’s also used to override what happens when a function is called, mocking results or behavior. This goes far beyond the capabilities of Python’s own mock patching, and instead alters things at a bytecode level. Super useful when you want to fake results from urlopen, for example.
kgb 5.0 introduces support for:
Python 3.8
New spy assertion methods, providing detailed output when they fail
Support for spying on “slippery” functions (functions generated dynamically when referencing the function itself — common in some API-wrapping Python libraries, like Stripe)
babel-plugin-django-gettext 1.0
We use Babel to let us build modern JavaScript and export it to older browsers. Something Babel allows for is custom plugins to transform JavaScript, and we’ve introduced a new plugin to help us write better localized text.
When using the standard gettext support, lines are not allowed to wrap, meaning you end up with some very long lines of text to maintain, and if you want to include the contents of variables in the text, you have to wrap in this interpolate() call, which is a pain.
This plugin takes all the annoyance out of this. Instead of writing:
var s = interpolate(
gettext('This is localizated text, and we can freely wrap lines how we want, or include variables like %(foo)s.'),
{'foo': foo},
true);
We get to write:
const s = _`
This is localizated text, and we can freely wrap
lines how we want, or include variables like ${foo}.
`;
Better, right?
If you use Babel and Django, give this plugin a try.
We’ll be releasing a new version soon with even better support for ngettext (used for strings that are based on singular/plural values) and combining with other tagged templates (like dedent).
Review Board 3.0.18 Release Prep
We’re getting close to a new Review Board 3.0.18 release. There’s a lot going into this one, but some highlights will include:
Preparation for GitHub and Bitbucket API/feature deprecations
Compatibility fixes for GitLab, Subversion, and Perforce
Improved API support for working with repositories
Faster SSH communication
Faster condensediffs for large MySQL databases
Lots of bug fixes
Expect 3.0.18 within the next two weeks.
Review Board 4.0 Release Prep
Work continues. We’ve had some people test 4.0 early, and found some regressions that pertain to extensions. We don’t want to release with those regressions in place, so we’re still iterating, but the good news is that the core product is looking pretty good now.
Remember, this release is a major architectural rewrite of the product, with equally major dependency updates, so there’s a lot to get right.
Meanwhile, we’re getting RBTools 2.0 ready for beta. This is meant to be used with Review Board 4.0, and features all the multi-commit review support, from posting changes to landing them. We’ll be shipping both at the same time.
CANOSP Student Wrap-Ups
We’ve talked before about the CANOSP student program we work with in Canada. Well, we’ve wrapped up our semester, and I can speak for the team when I say we’re going to miss working with this group.
By the way, if you’re looking to hire some strong developers coming out of college, we have plenty we can refer.
To wrap up their semester, they’ve put together some final demos of the work they’ve done, and we’d like to show them off.
Hannah Lin
Hannah worked this semester on a prototype for a new first-time setup guide for administrators, and some keyboard accessibility improvements in the diff viewer and modal dialogs, amongst other improvements. She’s also continuing on after the semester, working on a formatting toolbar for input fields.
Katherine Patenio
Katherine worked away on RBTools for most of the semester, fixing some bugs that shipped in RBTools 1.0.3, and completely reworking the rbt setup-repo experience (which we hope to ship in RBTools 2.0).
She also did a lot of work on investigating improvements to supporting users with different kinds of color-blindness, which she covers in this demo.
Monica Bui
Monica focused primarily this semester on keyboard navigation improvements in the New Review Request page (part of a big effort toward improved accessibility), and prototyping new guidance for filling in fields on a blank review request. We think that will pair nicely with work planned for Review Board 5.0.
Xiaohui Liu
Xiaohui worked on standardizing how we handle keyboard shortcuts, introducing a new registry on the page that anything can plug into to register shortcuts. This even offers a handy help screen, giving users an overview of all the keys can happily press to get their work done faster.
Xiaole Zeng
Xiaole’s projects covered help and accessibility improvements, such as adding a new Help menu to the top-right of every page (which could provide access to useful, relevant documentation), and making the review request infoboxes on the Dashboard less annoying and more keyboard-friendly. We’re looking to ship some of this in 4.0.
And that’s it for the moment
We’ll be back to a weekly format once we’ve gotten some of these releases wrapped up, and of course any time we have something pretty exciting to talk about.
Today, we’ve released kgb 4.0, the latest in our handy Python module for creating function spies in unit tests.
For those new to kgb, function spies allow unit tests to track when functions or methods are called (how many times, and with what parameters and results), and allow functions or methods to be overridden (for instance, to simulate an HTTP result using urllib2). JavaScript developers have Jasmine, and Python developers have kgb. See how documentation for more info there.
So what’s new in kgb 4.0?
Calling a spy’s original function
When spying on a function, a caller (or the spy’s replacement function) can now invoke the original behavior for the function. Unlike a call to the spy’s version, this call will not be logged. It’s really useful for keeping the original functionality intact but adding some parameter manipulation or additional tracking.
For example:
stored_results = []
def my_fake_function(*args, **kwargs):
kwargs['bar'] = 'baz'
result = obj.function.call_original(*args, **kwargs)
stored_results.append(result)
return result
agency.spy_on(obj.function, call_fake=my_fake_function)
Better Python 3 support
We’ve steadily been improving Python 3 support. It works well, but kgb 3.x would trigger some deprecation warnings when setting up a spy. We’ve fixed this up, future-proofed things some.
To learn more about kgb…
Visit the documentation to see all the way that kgb spies can work for you.
We’ve just released a new major version of kgb, a Python library for creating function spies in unit tests. This is a very handy tool for helping craft unit tests in Python applications.
kgb 2.0 introduces support for Python 3.6, improves argument checking, and removes the need for a special .spy attribute on standard functions.
What are function spies?
Function spies allow you to listen for when functions are called, what parameters they were passed, what value they returned or exception they raised, and allow you to disable the function’s normal behavior and optionally replace it with your own. This is a popular feature of Jasmine, a testing framework for JavaScript.
They’re particularly useful when working with third-party libraries whose behavior you cannot normally change. For example, your project might call a function in a library that in turn calls out to a HTTP server, which might be problematic for your unit test. With kgb, you can simply spy on urllib2.urlopen and return a custom result.
For example:
import logging
from unittest import TestCase
from urllib2 import urlopen
from kgb import SpyAgency
class MyTests(SpyAgency, TestCase):
def test_http_request(self):
def _fake_urlopen(opener, *args, **kwargs):
self.assertEqual(url.get_full_url(),
'https://example.com/123/')
class FakeResult(object):
def read(self):
return 'Your fake payload goes here!'
return _FakeResult()
self.spy_on(urlopen, call_fake=_fake_urlopen)
# Imagine that this function makes an HTTP request to
# https://example.com/123/ and logs a message.
some_function_that_does_urlopen()
self.assertTrue(urlopen.called)
self.assertTrue(logging.info.called_with(
'Fetching something from an API'))
What’s new in kgb 2.0?
Better, more consistent spies
We removed the distinction between spying on standard functions and methods on classes. This used to be treated differently. Previously, you could call spy functions like .called_with() and access attributes like .last_call directly on the method, but for functions, you had to use .spy.called_with() and .spy.last_call. We also kept plain functions mostly intact, but replaced methods on a class with a method-like object designed to intercept calls and mimic the method’s signature. That meant things were different depending on what you were spying on.
We now keep the methods where they are, bringing the spy functions and attributes onto the spied functions directly. We also use a special bytecode injection process for all spying operations (it’s very complicated, but awesome).
What does this ultimately mean? Well, it means if you had code from older versions that looked like this:
self.assertTrue(my_func.spy.called)
You can trim off the .spy part:
self.assertTrue(my_func.called)
It also means that we’re spying at a lower level than before for methods on classes, helping to prevent problems with code that’s sensitive to methods being replaced.
And it gives us Python 3.6 support.
Python 3.6 and PyPy support
See, there it is!
kgb 2.0 now fully works with Python 3.6. And as a bonus, PyPy as well.
More flexible and descriptive argument checks
called_with() now lets you check positional arguments by specifying their names as keyword arguments, and doesn’t require that you check for all arguments passed to the function. For example, if you have this method:
def my_func(a, b, c=123):
pass
you can inspect the calls with:
self.assertTrue(my_func.called_with(b=True))
Hand-holding when things go wrong
If something goes wrong in your test suite and your spy fails to unregister at the end of the test, you could get some pretty confusing assertion errors in older versions of kgb. Same if you accidentally try spying on the same function twice in your tests.
In kgb 2.0, we check for this and present a very helpful error showing you exactly where the spy was originally set so you don’t have to hunt it down yourself.
Learn more about kgb
We’re biased, but this is a really nifty library, and has made our lives so much easier. We have full documentation up on GitHub showing all the ways you can work with spies, along with a FAQ.
Installation is easy. Just run:
$ pip install kgb
kgb supports Python 2.6 through 2.7 and 3.4 through 3.6, along with new, experimental support for PyPy.