Moved all blog posts to http://aespinosa.github.io/blog/. I’ll be posting new content there from now on.
Chefspec 3.2.0 introduced berkshelf integration lately. However this will decrease the test speed as chefspec will setup and teardown berkshelf all the time.
Below is a snipped to similar chefspec#242 . But in addition, I removed the vendoring of the cookbook in development. Here is my rake task to do that build:
task :build do
File.open("chefignore", "a").write("*aspnet_skeleton*")
berksfile.install path: "vendor/cookbooks"
FileUtils.rmdir "vendor/cookbooks/aspnet_skeleton"
end
This approach has several advantages: [1] chefspec doesn’t need to build the vendored directory everytime rspec is invoked, [2] you can run the test against the source code directory for a fast TDD feedback cycle, and [3] build and test phases for your CI pipeline can be separated.
Here’s how chefspec consumes the vendored cookbook_path and cookbook-in-development simultaenously:
RSpec.configure do |config| config.cookbook_path = %w(vendor/cookbooks ../) end
I like spec-style tests as you can describe the scenarios of a test in a more structured manner. However, I love the xUnit family’s assertion calls. Here’s a small helper to create the describe => context synonym:
def context(*args, &block) describe(*args, &block) end
Testing recipe definitions with chefspec
Published 2013 5月 6 Soft Hacks Leave a Commentタグ: chef, devops, tdd
Last time [1], we wrote chefspec tests to cover the behavior of the nginx::default recipe. Another component provided by the recipe, is the nginx_site definition. Similar to the previous approach we cover the definition’s resources for each possible track the recipe can go through. The following is the output when the specs succeed.
nginx::default #nginx_site (definition) #enable => true (default) should execute command "/usr/sbin/nxensite foo" should notify "service[nginx]" and "reload" #enable => false should execute command "/usr/sbin/nxdissite foo" should notify "service[nginx]" and "reload"
To go about covering the behavior, we start driving TDD by creating failing specs staring with the default case:
context "#nginx_site (definition)" do
context "#enable => true (default)" do
it { expect(chef_run).to execute_command "/usr/sbin/nxensite foo" }
it do
expect(chef_run.execute("nxensite foo"))
.to notify("service[nginx]", "reload")
end
end
end
The above specs will fail because nginx::default doesn’t to make a nginx_site(“foo”) call. In minitest or test-kitchen, the approach was to create a test recipe that will conduct this integration test. However, for isolated chefspecs, this is too heavy weight. Code diving into the Chef 11.4.4 documentation and code [2], every recipe file is loaded by an instance_eval call on Recipe objects. Hence we can make this approach to inject a fake recipe. Below, we created a recipe called “nginx_spec::default” with using the existing run context from the “nginx::default” run.
def fake_recipe(run, &block)
recipe = Chef::Recipe.new("nginx_spec", "default", run.run_context)
recipe.instance_eval(&block)
end
# call to the spec example
recipe = fake_recipe(chef_run) do
nginx_site "foo" do
enable enabled
end
end
Next, we create a new ChefSpec::ChefRunner instance by appending our internally-created “nginx_spec::default” recipe to the existing “nginx::default” run. For this we monkey-patch chefspec to converge the added recipe:
class ChefSpec::ChefRunner def append(recipe) runner = Chef::Runner.new(recipe.run_context) runner.converge self end end # usage in our examples: new_run = chef_run.append(recipe)
Now we can change our specs to use this new runner context instead to make our expectations. Below is the whole context that makes the spec pass:
context "#enable => true (default)" do
let(:run) do
recipe = fake_recipe(chef_run) do
nginx_site "foo"
end
chef_run.append(recipe)
end
it { expect(run).to execute_command "/usr/sbin/nxensite foo" }
it do
expect(run.execute("nxensite foo"))
.to notify("service[nginx]", "reload")
end
end
And now we have the nginx_site definition covered. I made commits on the changes in my fork [3] of the opscode cookbook. Although the approach is useful, this intrusive monkey-patching to chefspec (which is itself a monkey-patch on chef) shows why folks at Opscode recommend to use LWRPS into new recipe development as you can monitor the state of the new resource itself. With definitions, you have to track the state of the resources made inside the definition action and provide the necessary spec. This also has implications when you are driving the recipes via TDD to use the nginx_site definition. I will cover testing that in another post.
creating fast spec coverage on legacy recipes
Published 2013 5月 1 Uncategorized 1 Commentタグ: chef, devops
As techniques in testing Chef recipes are still evolving, most of use inherit large untested cookbook codebases from eons ago (translates to a few months in Chef’s community speed). After watching Chefconf 2013 livestreams, I decided to give chefspec [3] a try. However, most of the write-ups about chefspec cover pretty basic. Here, I will write how I covered our legacy chef repository with fast tests. For this example, I will be writing coverage for opscode’s nginx recipe [1] .
First we begin by covering examples for all resources that gets created by default.
describe 'nginx::default' do it "loads the ohai plugin" it "starts the service" end
Then, we create contexts for each test case in the recipe logic.
describe 'nginx::default' do
it "loads the ohai plugin"
it "builds from source when specified"
context "install method is by package" do
context "when the platform is redhat-based" do
it "includes the yum::epel recipe if the source is epel"
it "includes the nginx::repo recipe if the source is not epel"
end
it "installs the package"
it "enables the service"
it "includes common configurations"
end
it "starts the service"
end
Now we have a general idea of what tests to write from the rspec documentation run:
rspec spec/default_spec.rb nginx::default loads the ohai plugin (PENDING: Not yet implemented) builds from source when specified (PENDING: Not yet implemented) starts the service (PENDING: Not yet implemented) install method is by package installs the package (PENDING: Not yet implemented) enables the service (PENDING: Not yet implemented) includes common configurations (PENDING: Not yet implemented) when the platform is redhat-based includes the yum::epel recipe if the source is epel (PENDING: Not yet implemented) includes the nginx::repo recipe if the source is not epel (PENDING: Not yet implemented) Pending: nginx::default loads the ohai plugin # Not yet implemented # ./spec/default_spec.rb:4 nginx::default builds from source when specified # Not yet implemented # ./spec/default_spec.rb:6 nginx::default starts the service # Not yet implemented # ./spec/default_spec.rb:18 nginx::default install method is by package installs the package # Not yet implemented # ./spec/default_spec.rb:13 nginx::default install method is by package enables the service # Not yet implemented # ./spec/default_spec.rb:14 nginx::default install method is by package includes common configurations # Not yet implemented # ./spec/default_spec.rb:15 nginx::default install method is by package when the platform is redhat-based includes the yum::epel recipe if the source is epel # Not yet implemented # ./spec/default_spec.rb:10 nginx::default install method is by package when the platform is redhat-based includes the nginx::repo recipe if the source is not epel # Not yet implemented # ./spec/default_spec.rb:11 Finished in 0.00977 seconds 8 examples, 0 failures, 8 pending
Progress can be found in [2] where i tested everything. Next I will be writing on how I tested the nginx nginx_site definitions.
- http://community.opscode.com/cookbooks/nginx
- https://github.com/aespinosa/cookbook-nginx
- https://github.com/acrmp/chefspec
- https://www.destroyallsoftware.com/screencasts/catalog/untested-code-part-1-introduction
Disclaimer: I came from an xUnit-testing background so I maybe interchanging “test cases” with “examples” and other purist stuff. Also I may need to proof read on how my spec examples speak.
Recent Comments