Add tests to poll stats
While we already had "one test to rule all stats", testing each method individually makes reading, adding and changing tests easier. Note we need to make all methods being tested public. We could also test them using methods like `stats.generate[:total_valid_votes]` instead of `stats.total_valid_votes`, but then the tests would be more difficult to read.
This commit is contained in:
@@ -12,32 +12,49 @@ module Statisticable
|
|||||||
self.class.stats_methods.map { |stat_name| [stat_name, send(stat_name)] }.to_h
|
self.class.stats_methods.map { |stat_name| [stat_name, send(stat_name)] }.to_h
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def total_male_participants
|
||||||
|
participants.where(gender: "male").count
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_female_participants
|
||||||
|
participants.where(gender: "female").count
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_unknown_gender_or_age
|
||||||
|
participants.where("gender IS NULL OR date_of_birth is NULL").uniq.count
|
||||||
|
end
|
||||||
|
|
||||||
|
def male_percentage
|
||||||
|
calculate_percentage(total_male_participants, total_participants_with_gender)
|
||||||
|
end
|
||||||
|
|
||||||
|
def female_percentage
|
||||||
|
calculate_percentage(total_female_participants, total_participants_with_gender)
|
||||||
|
end
|
||||||
|
|
||||||
|
def participants_by_age
|
||||||
|
age_groups.map do |start, finish|
|
||||||
|
users = participants.where("date_of_birth > ? AND date_of_birth < ?",
|
||||||
|
finish.years.ago.beginning_of_year,
|
||||||
|
start.years.ago.end_of_year)
|
||||||
|
|
||||||
|
[
|
||||||
|
"#{start} - #{finish}",
|
||||||
|
{
|
||||||
|
range: range_description(start, finish),
|
||||||
|
count: users.count,
|
||||||
|
percentage: calculate_percentage(users.count, total_participants)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end.to_h
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def total_participants_with_gender
|
def total_participants_with_gender
|
||||||
participants.where.not(gender: nil).distinct.count
|
participants.where.not(gender: nil).distinct.count
|
||||||
end
|
end
|
||||||
|
|
||||||
def total_male_participants
|
|
||||||
participants.where(gender: "male").count
|
|
||||||
end
|
|
||||||
|
|
||||||
def total_female_participants
|
|
||||||
participants.where(gender: "female").count
|
|
||||||
end
|
|
||||||
|
|
||||||
def total_unknown_gender_or_age
|
|
||||||
participants.where("gender IS NULL OR date_of_birth is NULL").uniq.count
|
|
||||||
end
|
|
||||||
|
|
||||||
def male_percentage
|
|
||||||
calculate_percentage(total_male_participants, total_participants_with_gender)
|
|
||||||
end
|
|
||||||
|
|
||||||
def female_percentage
|
|
||||||
calculate_percentage(total_female_participants, total_participants_with_gender)
|
|
||||||
end
|
|
||||||
|
|
||||||
def age_groups
|
def age_groups
|
||||||
[[16, 19],
|
[[16, 19],
|
||||||
[20, 24],
|
[20, 24],
|
||||||
@@ -58,23 +75,6 @@ module Statisticable
|
|||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
def participants_by_age
|
|
||||||
age_groups.map do |start, finish|
|
|
||||||
users = participants.where("date_of_birth > ? AND date_of_birth < ?",
|
|
||||||
finish.years.ago.beginning_of_year,
|
|
||||||
start.years.ago.end_of_year)
|
|
||||||
|
|
||||||
[
|
|
||||||
"#{start} - #{finish}",
|
|
||||||
{
|
|
||||||
range: range_description(start, finish),
|
|
||||||
count: users.count,
|
|
||||||
percentage: calculate_percentage(users.count, total_participants)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
end.to_h
|
|
||||||
end
|
|
||||||
|
|
||||||
def calculate_percentage(fraction, total)
|
def calculate_percentage(fraction, total)
|
||||||
return 0.0 if total.zero?
|
return 0.0 if total.zero?
|
||||||
|
|
||||||
|
|||||||
@@ -13,77 +13,78 @@ class Poll::Stats
|
|||||||
null_percentage_web null_percentage_booth total_null_percentage]
|
null_percentage_web null_percentage_booth total_null_percentage]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def total_participants
|
||||||
|
total_participants_web + total_participants_booth
|
||||||
|
end
|
||||||
|
|
||||||
|
%i[web booth].each do |channel|
|
||||||
|
define_method :"total_participants_#{channel}" do
|
||||||
|
send(:"total_#{channel}_valid") +
|
||||||
|
send(:"total_#{channel}_white") +
|
||||||
|
send(:"total_#{channel}_null")
|
||||||
|
end
|
||||||
|
|
||||||
|
define_method :"total_participants_#{channel}_percentage" do
|
||||||
|
calculate_percentage(send(:"total_participants_#{channel}"), total_participants)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_web_valid
|
||||||
|
voters.where(origin: "web").count - total_web_white
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_web_white
|
||||||
|
0
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_web_null
|
||||||
|
0
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_booth_valid
|
||||||
|
recounts.sum(:total_amount)
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_booth_white
|
||||||
|
recounts.sum(:white_amount)
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_booth_null
|
||||||
|
recounts.sum(:null_amount)
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_percentage_web
|
||||||
|
calculate_percentage(total_web_valid, total_valid_votes)
|
||||||
|
end
|
||||||
|
|
||||||
|
def white_percentage_web
|
||||||
|
calculate_percentage(total_web_white, total_white_votes)
|
||||||
|
end
|
||||||
|
|
||||||
|
def null_percentage_web
|
||||||
|
calculate_percentage(total_web_null, total_null_votes)
|
||||||
|
end
|
||||||
|
|
||||||
|
%i[valid white null].each do |type|
|
||||||
|
define_method :"#{type}_percentage_booth" do
|
||||||
|
calculate_percentage(send(:"total_booth_#{type}"), send(:"total_#{type}_votes"))
|
||||||
|
end
|
||||||
|
|
||||||
|
define_method :"total_#{type}_votes" do
|
||||||
|
send(:"total_web_#{type}") + send(:"total_booth_#{type}")
|
||||||
|
end
|
||||||
|
|
||||||
|
define_method :"total_#{type}_percentage" do
|
||||||
|
calculate_percentage(send(:"total_#{type}_votes"), total_participants)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def participants
|
def participants
|
||||||
User.where(id: voters.pluck(:user_id))
|
User.where(id: voters.pluck(:user_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
def total_participants
|
|
||||||
total_participants_web + total_participants_booth
|
|
||||||
end
|
|
||||||
|
|
||||||
%i[web booth].each do |channel|
|
|
||||||
define_method :"total_participants_#{channel}" do
|
|
||||||
send(:"total_#{channel}_valid") +
|
|
||||||
send(:"total_#{channel}_white") +
|
|
||||||
send(:"total_#{channel}_null")
|
|
||||||
end
|
|
||||||
|
|
||||||
define_method :"total_participants_#{channel}_percentage" do
|
|
||||||
calculate_percentage(send(:"total_participants_#{channel}"), total_participants)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def total_web_valid
|
|
||||||
voters.where(origin: "web").count - total_web_white
|
|
||||||
end
|
|
||||||
|
|
||||||
def total_web_white
|
|
||||||
0
|
|
||||||
end
|
|
||||||
|
|
||||||
def total_web_null
|
|
||||||
0
|
|
||||||
end
|
|
||||||
|
|
||||||
def total_booth_valid
|
|
||||||
recounts.sum(:total_amount)
|
|
||||||
end
|
|
||||||
|
|
||||||
def total_booth_white
|
|
||||||
recounts.sum(:white_amount)
|
|
||||||
end
|
|
||||||
|
|
||||||
def total_booth_null
|
|
||||||
recounts.sum(:null_amount)
|
|
||||||
end
|
|
||||||
|
|
||||||
def valid_percentage_web
|
|
||||||
calculate_percentage(total_web_valid, total_valid_votes)
|
|
||||||
end
|
|
||||||
|
|
||||||
def white_percentage_web
|
|
||||||
calculate_percentage(total_web_white, total_white_votes)
|
|
||||||
end
|
|
||||||
|
|
||||||
def null_percentage_web
|
|
||||||
calculate_percentage(total_web_null, total_null_votes)
|
|
||||||
end
|
|
||||||
|
|
||||||
%i[valid white null].each do |type|
|
|
||||||
define_method :"#{type}_percentage_booth" do
|
|
||||||
calculate_percentage(send(:"total_booth_#{type}"), send(:"total_#{type}_votes"))
|
|
||||||
end
|
|
||||||
|
|
||||||
define_method :"total_#{type}_votes" do
|
|
||||||
send(:"total_web_#{type}") + send(:"total_booth_#{type}")
|
|
||||||
end
|
|
||||||
|
|
||||||
define_method :"total_#{type}_percentage" do
|
|
||||||
calculate_percentage(send(:"total_#{type}_votes"), total_participants)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def voters
|
def voters
|
||||||
poll.voters
|
poll.voters
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,160 @@
|
|||||||
require "rails_helper"
|
require "rails_helper"
|
||||||
|
|
||||||
describe Poll::Stats do
|
describe Poll::Stats do
|
||||||
|
let(:poll) { create(:poll) }
|
||||||
|
let(:stats) { Poll::Stats.new(poll) }
|
||||||
|
|
||||||
|
describe "total participants" do
|
||||||
|
before { allow(stats).to receive(:total_web_white).and_return(1) }
|
||||||
|
|
||||||
|
it "supports every channel" do
|
||||||
|
3.times { create(:poll_voter, :from_web, poll: poll) }
|
||||||
|
create(:poll_recount, :from_booth, poll: poll,
|
||||||
|
total_amount: 8, white_amount: 4, null_amount: 1)
|
||||||
|
|
||||||
|
expect(stats.total_participants_web).to eq(3)
|
||||||
|
expect(stats.total_participants_booth).to eq(13)
|
||||||
|
expect(stats.total_participants).to eq(16)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#total_participants_booth" do
|
||||||
|
it "uses recounts even if there are discrepancies when recounting" do
|
||||||
|
create(:poll_recount, :from_booth, poll: poll, total_amount: 1)
|
||||||
|
2.times { create(:poll_voter, :from_booth, poll: poll) }
|
||||||
|
|
||||||
|
expect(stats.total_participants_booth).to eq(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "total participants percentage by channel" do
|
||||||
|
it "is relative to the total amount of participants" do
|
||||||
|
create(:poll_voter, :from_web, poll: poll)
|
||||||
|
create(:poll_recount, :from_booth, poll: poll, total_amount: 5)
|
||||||
|
|
||||||
|
expect(stats.total_participants_web_percentage).to eq(16.667)
|
||||||
|
expect(stats.total_participants_booth_percentage).to eq(83.333)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#total_web_valid" do
|
||||||
|
before { allow(stats).to receive(:total_web_white).and_return(1) }
|
||||||
|
|
||||||
|
it "returns only valid votes" do
|
||||||
|
3.times { create(:poll_voter, :from_web, poll: poll) }
|
||||||
|
|
||||||
|
expect(stats.total_web_valid).to eq(2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#total_web_white" do
|
||||||
|
pending "Too complex to test"
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#total_web_null" do
|
||||||
|
it "returns 0" do
|
||||||
|
expect(stats.total_web_null).to eq(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#total_booth_valid" do
|
||||||
|
it "sums the total amounts in the recounts" do
|
||||||
|
create(:poll_recount, :from_booth, poll: poll, total_amount: 3, white_amount: 1)
|
||||||
|
create(:poll_recount, :from_booth, poll: poll, total_amount: 4, null_amount: 2)
|
||||||
|
|
||||||
|
expect(stats.total_booth_valid).to eq(7)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#total_booth_white" do
|
||||||
|
it "sums the white amounts in the recounts" do
|
||||||
|
create(:poll_recount, :from_booth, poll: poll, white_amount: 120, total_amount: 3)
|
||||||
|
create(:poll_recount, :from_booth, poll: poll, white_amount: 203, null_amount: 5)
|
||||||
|
|
||||||
|
expect(stats.total_booth_white).to eq(323)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#total_booth_null" do
|
||||||
|
it "sums the null amounts in the recounts" do
|
||||||
|
create(:poll_recount, :from_booth, poll: poll, null_amount: 125, total_amount: 3)
|
||||||
|
create(:poll_recount, :from_booth, poll: poll, null_amount: 34, white_amount: 5)
|
||||||
|
|
||||||
|
expect(stats.total_booth_null).to eq(159)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "valid percentage by channel" do
|
||||||
|
it "is relative to the total amount of valid votes" do
|
||||||
|
create(:poll_recount, :from_booth, poll: poll, total_amount: 2)
|
||||||
|
create(:poll_voter, :from_web, poll: poll)
|
||||||
|
|
||||||
|
expect(stats.valid_percentage_web).to eq(33.333)
|
||||||
|
expect(stats.valid_percentage_booth).to eq(66.667)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "white percentage by channel" do
|
||||||
|
before { allow(stats).to receive(:total_web_white).and_return(10) }
|
||||||
|
|
||||||
|
it "is relative to the total amount of white votes" do
|
||||||
|
create(:poll_recount, :from_booth, poll: poll, white_amount: 70)
|
||||||
|
|
||||||
|
expect(stats.white_percentage_web).to eq(12.5)
|
||||||
|
expect(stats.white_percentage_booth).to eq(87.5)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "null percentage by channel" do
|
||||||
|
it "only accepts null votes from booth" do
|
||||||
|
create(:poll_recount, :from_booth, poll: poll, null_amount: 70)
|
||||||
|
|
||||||
|
expect(stats.null_percentage_web).to eq(0)
|
||||||
|
expect(stats.null_percentage_booth).to eq(100)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#total_valid_votes" do
|
||||||
|
it "counts valid votes from every channel" do
|
||||||
|
2.times { create(:poll_voter, :from_web, poll: poll) }
|
||||||
|
create(:poll_recount, :from_booth, poll: poll, total_amount: 3, white_amount: 10)
|
||||||
|
create(:poll_recount, :from_booth, poll: poll, total_amount: 4, null_amount: 20)
|
||||||
|
|
||||||
|
expect(stats.total_valid_votes).to eq(9)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#total_white_votes" do
|
||||||
|
before { allow(stats).to receive(:total_web_white).and_return(9) }
|
||||||
|
|
||||||
|
it "counts white votes on every channel" do
|
||||||
|
create(:poll_recount, :from_booth, poll: poll, white_amount: 12)
|
||||||
|
|
||||||
|
expect(stats.total_white_votes).to eq(21)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#total_null_votes" do
|
||||||
|
it "only accepts null votes from booth" do
|
||||||
|
create(:poll_recount, :from_booth, poll: poll, null_amount: 32)
|
||||||
|
|
||||||
|
expect(stats.total_null_votes).to eq(32)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "total percentage by type" do
|
||||||
|
before { allow(stats).to receive(:total_web_white).and_return(1) }
|
||||||
|
|
||||||
|
it "is relative to the total amount of votes" do
|
||||||
|
3.times { create(:poll_voter, :from_web, poll: poll) }
|
||||||
|
create(:poll_recount, :from_booth, poll: poll,
|
||||||
|
total_amount: 8, white_amount: 5, null_amount: 4)
|
||||||
|
|
||||||
|
expect(stats.total_valid_percentage).to eq(50)
|
||||||
|
expect(stats.total_white_percentage).to eq(30)
|
||||||
|
expect(stats.total_null_percentage).to eq(20)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "#generate" do
|
describe "#generate" do
|
||||||
it "generates the correct stats" do
|
it "generates the correct stats" do
|
||||||
|
|||||||
Reference in New Issue
Block a user