Usecase: unfinished new feature
(trunk-based development)¶
Scenario¶
As a programmer, you want to add a new feature. Because your team works trunk-based, you are merging constantly to the master branch. This leads to following problems:
- Your feature is not finished yet, and the usage of it might be problematic
- Your tests of the feature are failing, therefore the tests of other programmers are failing
- You do not know, if the implementation of the feature like this is the best way - neither do the other developer
- The other developer do not want your feature be active directly - due to possible untested side-effects
To avoid this problem, the new feature can be flagged from the beginning.
Flagging the unittest(s)¶
import ipytest
ipytest.autoconfig()
from fastfeatureflag.feature_flag import feature_flag
def test_check_fizz():
fizz__buzz = FizzBuzz()
assert fizz__buzz.fizz(3) == True
assert fizz__buzz.fizz(4) == False
# Here starts the first test of the new feature
@feature_flag(name="check_buzz").pytest()
def test_check_buzz():
fizz_buzz = FizzBuzz()
assert fizz_buzz.buzz(5) == True
assert fizz_buzz.buzz(6) == True
Now lets also take a look on the class implementation.
class FizzBuzz:
def fizz(self, value: int) -> bool:
if value % 3 == 0:
return True
return False
@feature_flag(name="check_buzz")
def buzz(self, value: int) -> bool:
raise ValueError("Feature not finished, can't work with value.")
ipytest.run()
.. [100%] 2 passed in 0.01s
<ExitCode.OK: 0>
The tests are passed, even when the feature is not implemented correctly (yet).
Now, we would like to actually run and test the feature locally. We can easily activate
the feature flag directly:
# Because we run the feature flags within one jupyter instance, we have to clean/unregister old features.
# The following lines are doing exactly that.
from fastfeatureflag.feature_flag_configuration import FeatureFlagConfiguration
FeatureFlagConfiguration.clean()
import ipytest
ipytest.autoconfig()
from fastfeatureflag.feature_flag import feature_flag
def test_check_fizz():
fizz__buzz = FizzBuzz()
assert fizz__buzz.fizz(3) == True
assert fizz__buzz.fizz(4) == False
# Here starts the first test of the new feature
@feature_flag("on", name="check_buzz").pytest()
def test_check_buzz():
fizz_buzz = FizzBuzz()
assert fizz_buzz.buzz(value=5) == True
assert fizz_buzz.buzz(value=6) == True
class FizzBuzz:
def fizz(self, value: int) -> bool:
if value % 3 == 0:
return True
return False
@feature_flag(name="check_buzz")
def buzz(self, value: int) -> bool:
raise ValueError("Feature not finished, can't work with value.")
ipytest.run()
.F [100%] ============================================= FAILURES ============================================= _________________________________________ test_check_buzz __________________________________________ @feature_flag("on", name="check_buzz").pytest() def test_check_buzz(): fizz_buzz = FizzBuzz() > assert fizz_buzz.buzz(value=5) == True /tmp/ipykernel_2349/1722941283.py:15: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ /opt/hostedtoolcache/Python/3.12.0/x64/lib/python3.12/site-packages/fastfeatureflag/feature_flag_configuration.py:62: in __call__ return self._decorated_function(*args, **kwargs) /opt/hostedtoolcache/Python/3.12.0/x64/lib/python3.12/site-packages/fastfeatureflag/feature_flag_configuration.py:209: in _decorated_function return self.feature.func(*args, **self._options) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = <__main__.FizzBuzz object at 0x7f0e38f6ab40>, value = 5 @feature_flag(name="check_buzz") def buzz(self, value: int) -> bool: > raise ValueError("Feature not finished, can't work with value.") E ValueError: Feature not finished, can't work with value. /tmp/ipykernel_2349/1722941283.py:27: ValueError ===================================== short test summary info ====================================== FAILED t_b24013ed662a49acadba5a0607242377.py::test_check_buzz - ValueError: Feature not finished, can't work with value. 1 failed, 1 passed in 0.17s
<ExitCode.TESTS_FAILED: 1>
Now the test fails, because the original test and the original feature have been activated.
Make your debugging/testing life easier¶
With these feature_flag
s, you can easily enable/disable the feature. However, that might get annoying with time and is prone to errors. You might forget to switch the feature off, etc. Instead, you could use a configuration file. Even better, you can specify environment variables within that configuration file - together with an .env
file, you can easily test and debug your feature and others never run into the problem to use/test an unfinished feature.
Configuration file content:
[check_buzz]
activation=CHECK_BUZZ
.env
file content:
CHECK_BUZZ="on"
Different .env
files for e.g. testing stages or other CI/CD pipelines can help you test the software with different scenarios.
To make your life easier with enabling/disabling features, see: Configuration
Created: October 28, 2023