博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
shoulda 简介
阅读量:7256 次
发布时间:2019-06-29

本文共 14636 字,大约阅读时间需要 48 分钟。

  hot3.png

翻翻 shoulda 的源代码,其实它只是个空架子,本身并没有料,如下所示:

require 'shoulda/version'require 'shoulda-matchers'require 'shoulda-context'

我们先来到 shoulda-matchers 看看

首先,看这

if defined?(RSpec)  require 'shoulda/matchers/integrations/rspec'else  require 'shoulda/matchers/integrations/test_unit'end

可知,它主要是为了 RSpec 或者 Test::Unit 服务。

再往下看:由其目录结构可以看出它为

action_controller    action_mailer  active_model  active_record

它细分为这四者 ‘服务’, 胃口还挺大的。一个个来看看...

算了,我现在对 active_model 和 active_record 比较感兴趣,还是先看看它们吧。

从 active_model (重点)

allow_mass_assignment_of_matcher.rb 	   	allow_value_matcher.rb 		ensure_exclusion_of_matcher.rb 	 	ensure_inclusion_of_matcher.rb 		ensure_length_of_matcher.rb 		validate_acceptance_of_matcher.rb 		validate_format_of_matcher.rb 	 	validate_numericality_of_matcher.rb 	 	validate_presence_of_matcher.rb 	 	validate_uniqueness_of_matcher.rb

可见,它为我们提供了 3 类,共 10 个方法,基本上 满足了 我们对 validates 的所有要求。确实很方便...(注意是 validate, 不是 validates)

再来看看 active_record (重点)

association_matcher.rb 	 	have_db_column_matcher.rb 	 	have_db_index_matcher.rb 	 	have_readonly_attribute_matcher.rb

比较 吸引 眼球的是 association 还有 readonly (一个常用,而一个则为...) 值得一说的是,在 association 中,它用的是 have_xxx, 和 belong_to 前面的单词都是 ‘单数’, 使用的时候不要搞错了。

shoulda-context 则对 Assertions 进行了扩展, 并且提供了 context (重点)

assert_same_elements(a1, a2, msg = nil)   assert_contains(collection, x, extra_msg = "")   assert_does_not_contain(collection, x, extra_msg = "")  assert_accepts(matcher, target, options = {})   assert_rejects(matcher, target, options = {})

上面是 5 个新的 assert , 而下面的则是 context.rb 源代码 (非重点)

module Shoulda  module Context    class << self      attr_accessor :contexts      def contexts # :nodoc:        @contexts ||= []      end      def current_context # :nodoc:        self.contexts.last      end      def add_context(context) # :nodoc:        self.contexts.push(context)      end      def remove_context # :nodoc:        self.contexts.pop      end    end    module ClassMethods      # == Should statements      #      # Should statements are just syntactic sugar over normal Test::Unit test      # methods. A should block contains all the normal code and assertions      # you're used to seeing, with the added benefit that they can be wrapped      # inside context blocks (see below).      #      # === Example:      #      # class UserTest < Test::Unit::TestCase      #      # def setup      # @user = User.new("John", "Doe")      # end      #      # should "return its full name"      # assert_equal 'John Doe', @user.full_name      # end      #      # end      #      # ...will produce the following test:      # * "test: User should return its full name. "      #      # Note: The part before should in the test name is gleamed from the name of the Test::Unit class.      #      # Should statements can also take a Proc as a :before option. This proc runs after any      # parent context's setups but before the current context's setup.      #      # === Example:      #      # context "Some context" do      # setup { puts("I run after the :before proc") }      #      # should "run a :before proc", :before => lambda { puts("I run before the setup") } do      # assert true      # end      # end      #      # Should statements can also wrap matchers, making virtually any matcher      # usable in a macro style. The matcher's description is used to generate a      # test name and failure message, and the test will pass if the matcher      # matches the subject.      #      # === Example:      #      # should validate_presence_of(:first_name).with_message(/gotta be there/)      #      def should(name_or_matcher, options = {}, &blk)        if Shoulda::Context.current_context          Shoulda::Context.current_context.should(name_or_matcher, options, &blk)        else          context_name = self.name.gsub(/Test/, "") if self.name          context = Shoulda::Context::Context.new(context_name, self) do            should(name_or_matcher, options, &blk)          end          context.build        end      end      # Allows negative tests using matchers. The matcher's description is used      # to generate a test name and negative failure message, and the test will      # pass unless the matcher matches the subject.      #      # === Example:      #      # should_not set_the_flash      def should_not(matcher)        if Shoulda::Context.current_context          Shoulda::Context.current_context.should_not(matcher)        else          context_name = self.name.gsub(/Test/, "") if self.name          context = Shoulda::Context::Context.new(context_name, self) do            should_not(matcher)          end          context.build        end      end      # == Before statements      #      # Before statements are should statements that run before the current      # context's setup. These are especially useful when setting expectations.      #      # === Example:      #      # class UserControllerTest < Test::Unit::TestCase      # context "the index action" do      # setup do      # @users = [Factory(:user)]      # User.stubs(:find).returns(@users)      # end      #      # context "on GET" do      # setup { get :index }      #      # should respond_with(:success)      #      # # runs before "get :index"      # before_should "find all users" do      # User.expects(:find).with(:all).returns(@users)      # end      # end      # end      # end      def before_should(name, &blk)        should(name, :before => blk) { assert true }      end      # Just like should, but never runs, and instead prints an 'X' in the Test::Unit output.      def should_eventually(name, options = {}, &blk)        context_name = self.name.gsub(/Test/, "")        context = Shoulda::Context::Context.new(context_name, self) do          should_eventually(name, &blk)        end        context.build      end      # == Contexts      #      # A context block groups should statements under a common set of setup/teardown methods.      # Context blocks can be arbitrarily nested, and can do wonders for improving the maintainability      # and readability of your test code.      #      # A context block can contain setup, should, should_eventually, and teardown blocks.      #      # class UserTest < Test::Unit::TestCase      # context "A User instance" do      # setup do      # @user = User.find(:first)      # end      #      # should "return its full name"      # assert_equal 'John Doe', @user.full_name      # end      # end      # end      #      # This code will produce the method "test: A User instance should return its full name. ".      #      # Contexts may be nested. Nested contexts run their setup blocks from out to in before each      # should statement. They then run their teardown blocks from in to out after each should statement.      #      # class UserTest < Test::Unit::TestCase      # context "A User instance" do      # setup do      # @user = User.find(:first)      # end      #      # should "return its full name"      # assert_equal 'John Doe', @user.full_name      # end      #      # context "with a profile" do      # setup do      # @user.profile = Profile.find(:first)      # end      #      # should "return true when sent :has_profile?"      # assert @user.has_profile?      # end      # end      # end      # end      #      # This code will produce the following methods      # * "test: A User instance should return its full name. "      # * "test: A User instance with a profile should return true when sent :has_profile?. "      #      # Just like should statements, a context block can exist next to normal def test_the_old_way; end      # tests. This means you do not have to fully commit to the context/should syntax in a test file.      def context(name, &blk)        if Shoulda::Context.current_context          Shoulda::Context.current_context.context(name, &blk)        else          context = Shoulda::Context::Context.new(name, self, &blk)          context.build        end      end      # Returns the class being tested, as determined by the test class name.      #      # class UserTest; described_type; end      # # => User      def described_type        @described_type ||= self.name.          gsub(/Test$/, '').          split('::').          inject(Object) { |parent, local_name| parent.const_get(local_name) }      end      # Sets the return value of the subject instance method:      #      # class UserTest < Test::Unit::TestCase      # subject { User.first }      #      # # uses the existing user      # should validate_uniqueness_of(:email)      # end      def subject(&block)        @subject_block = block      end      def subject_block # :nodoc:        @subject_block      end    end    module InstanceMethods      # Returns an instance of the class under test.      #      # class UserTest      # should "be a user" do      # assert_kind_of User, subject # passes      # end      # end      #      # The subject can be explicitly set using the subject class method:      #      # class UserTest      # subject { User.first }      # should "be an existing user" do      # assert !subject.new_record? # uses the first user      # end      # end      #      # The subject is used by all macros that require an instance of the class      # being tested.      def subject        @shoulda_subject ||= construct_subject      end      def subject_block # :nodoc:        (@shoulda_context && @shoulda_context.subject_block) || self.class.subject_block      end      def get_instance_of(object_or_klass) # :nodoc:        if object_or_klass.is_a?(Class)          object_or_klass.new        else          object_or_klass        end      end      def instance_variable_name_for(klass) # :nodoc:        klass.to_s.split('::').last.underscore      end      private      def construct_subject        if subject_block          instance_eval(&subject_block)        else          get_instance_of(self.class.described_type)        end      end    end    class Context # :nodoc:      attr_accessor :name # my name      attr_accessor :parent # may be another context, or the original test::unit class.      attr_accessor :subcontexts # array of contexts nested under myself      attr_accessor :setup_blocks # blocks given via setup methods      attr_accessor :teardown_blocks # blocks given via teardown methods      attr_accessor :shoulds # array of hashes representing the should statements      attr_accessor :should_eventuallys # array of hashes representing the should eventually statements      attr_accessor :subject_block      def initialize(name, parent, &blk)        Shoulda::Context.add_context(self)        self.name = name        self.parent = parent        self.setup_blocks = []        self.teardown_blocks = []        self.shoulds = []        self.should_eventuallys = []        self.subcontexts = []        if block_given?          merge_block(&blk)        else          merge_block { warn " * WARNING: Block missing for context '#{full_name}'" }        end        Shoulda::Context.remove_context      end      def merge_block(&blk)        blk.bind(self).call      end      def context(name, &blk)        self.subcontexts << Context.new(name, self, &blk)      end      def setup(&blk)        self.setup_blocks << blk      end      def teardown(&blk)        self.teardown_blocks << blk      end      def should(name_or_matcher, options = {}, &blk)        if name_or_matcher.respond_to?(:description) && name_or_matcher.respond_to?(:matches?)          name = name_or_matcher.description          blk = lambda { assert_accepts name_or_matcher, subject }        else          name = name_or_matcher        end        if blk          self.shoulds << { :name => name, :before => options[:before], :block => blk }        else         self.should_eventuallys << { :name => name }       end      end      def should_not(matcher)        name = matcher.description        blk = lambda { assert_rejects matcher, subject }        self.shoulds << { :name => "not #{name}", :block => blk }      end      def should_eventually(name, &blk)        self.should_eventuallys << { :name => name, :block => blk }      end      def subject(&block)        self.subject_block = block      end      def subject_block        return @subject_block if @subject_block        parent.subject_block      end      def full_name        parent_name = parent.full_name if am_subcontext?        return [parent_name, name].join(" ").strip      end      def am_subcontext?        parent.is_a?(self.class) # my parent is the same class as myself.      end      def test_unit_class        am_subcontext? ? parent.test_unit_class : parent      end      def test_methods        @test_methods ||= Hash.new { |h,k|          h[k] = Hash[k.instance_methods.map { |n| [n, true] }]        }      end      def create_test_from_should_hash(should)        test_name = ["test:", full_name, "should", "#{should[:name]}. "].flatten.join(' ').to_sym        if test_methods[test_unit_class][test_name.to_s] then          warn " * WARNING: '#{test_name}' is already defined"        end        test_methods[test_unit_class][test_name.to_s] = true        context = self        test_unit_class.send(:define_method, test_name) do          @shoulda_context = context          begin            context.run_parent_setup_blocks(self)            should[:before].bind(self).call if should[:before]            context.run_current_setup_blocks(self)            should[:block].bind(self).call          ensure            context.run_all_teardown_blocks(self)          end        end      end      def run_all_setup_blocks(binding)        run_parent_setup_blocks(binding)        run_current_setup_blocks(binding)      end      def run_parent_setup_blocks(binding)        self.parent.run_all_setup_blocks(binding) if am_subcontext?      end      def run_current_setup_blocks(binding)        setup_blocks.each do |setup_block|          setup_block.bind(binding).call        end      end      def run_all_teardown_blocks(binding)        teardown_blocks.reverse.each do |teardown_block|          teardown_block.bind(binding).call        end        self.parent.run_all_teardown_blocks(binding) if am_subcontext?      end      def print_should_eventuallys        should_eventuallys.each do |should|          test_name = [full_name, "should", "#{should[:name]}. "].flatten.join(' ')          puts " * DEFERRED: " + test_name        end      end      def build        shoulds.each do |should|          create_test_from_should_hash(should)        end        subcontexts.each { |context| context.build }        print_should_eventuallys      end      def method_missing(method, *args, &blk)        test_unit_class.send(method, *args, &blk)      end    end  endend

转载于:https://my.oschina.net/kelby/blog/193048

你可能感兴趣的文章
李洪强iOS开发之OC语言description方法和sel
查看>>
调停者(Mediator)模式
查看>>
tr DEMO
查看>>
Windows下如何修改php.ini的访问路径?
查看>>
ASP.NET MVC 教程汇总
查看>>
c++中,size_typt, size_t, ptrdiff_t 简介
查看>>
【转载】计算机如何识别二进制
查看>>
MySQL binlog中的事件类型
查看>>
制作自己的MVC框架(二)——启动
查看>>
linux内核栈用户栈切换【转】
查看>>
离线安装PM2
查看>>
LR 两种html与url录制
查看>>
学习SpringMVC——你们要的REST风格的CRUD来了
查看>>
java中获取比毫秒更为精确的时间
查看>>
将数据导出到Excel2007格式。
查看>>
【SpringMVC学习07】SpringMVC中的统一异常处理
查看>>
C++语言出现的BUG
查看>>
java 集合
查看>>
【转载】ODBC, OLEDB, ADO, ADO.Net的演化简史
查看>>
Android 设置图片透明度
查看>>