| Index: components/ntp_snippets/user_classifier_unittest.cc
|
| diff --git a/components/ntp_snippets/user_classifier_unittest.cc b/components/ntp_snippets/user_classifier_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..f8f3a43a1aa06500c17dde7bbfca66f12f6114e5
|
| --- /dev/null
|
| +++ b/components/ntp_snippets/user_classifier_unittest.cc
|
| @@ -0,0 +1,317 @@
|
| +// Copyright 2017 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "components/ntp_snippets/user_classifier.h"
|
| +
|
| +#include <memory>
|
| +#include <string>
|
| +#include <utility>
|
| +
|
| +#include "base/memory/ptr_util.h"
|
| +#include "base/test/histogram_tester.h"
|
| +#include "base/test/simple_test_clock.h"
|
| +#include "base/time/time.h"
|
| +#include "components/ntp_snippets/features.h"
|
| +#include "components/ntp_snippets/ntp_snippets_constants.h"
|
| +#include "components/prefs/pref_registry_simple.h"
|
| +#include "components/prefs/testing_pref_service.h"
|
| +#include "components/variations/variations_params_manager.h"
|
| +#include "testing/gmock/include/gmock/gmock.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +using testing::DoubleNear;
|
| +using testing::Eq;
|
| +using testing::Gt;
|
| +using testing::Lt;
|
| +using testing::SizeIs;
|
| +
|
| +namespace ntp_snippets {
|
| +namespace {
|
| +
|
| +char kNowString[] = "2017-03-01 10:45";
|
| +
|
| +class UserClassifierTest : public testing::Test {
|
| + public:
|
| + UserClassifierTest() {
|
| + UserClassifier::RegisterProfilePrefs(test_prefs_.registry());
|
| + }
|
| +
|
| + UserClassifier* CreateUserClassifier() {
|
| + auto test_clock = base::MakeUnique<base::SimpleTestClock>();
|
| + test_clock_ = test_clock.get();
|
| +
|
| + base::Time now;
|
| + CHECK(base::Time::FromUTCString(kNowString, &now));
|
| + test_clock_->SetNow(now);
|
| +
|
| + user_classifier_ =
|
| + base::MakeUnique<UserClassifier>(&test_prefs_, std::move(test_clock));
|
| + return user_classifier_.get();
|
| + }
|
| +
|
| + base::SimpleTestClock* test_clock() { return test_clock_; }
|
| +
|
| + private:
|
| + TestingPrefServiceSimple test_prefs_;
|
| + std::unique_ptr<UserClassifier> user_classifier_;
|
| +
|
| + // Owned by the UserClassifier.
|
| + base::SimpleTestClock* test_clock_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(UserClassifierTest);
|
| +};
|
| +
|
| +TEST_F(UserClassifierTest, ShouldBeActiveNtpUserInitially) {
|
| + UserClassifier* user_classifier = CreateUserClassifier();
|
| + EXPECT_THAT(user_classifier->GetUserClass(),
|
| + Eq(UserClassifier::UserClass::ACTIVE_NTP_USER));
|
| +}
|
| +
|
| +TEST_F(UserClassifierTest,
|
| + ShouldBecomeActiveSuggestionsConsumerByClickingOften) {
|
| + UserClassifier* user_classifier = CreateUserClassifier();
|
| +
|
| + // After one click still only an active user.
|
| + user_classifier->OnEvent(UserClassifier::Metric::SUGGESTIONS_USED);
|
| + EXPECT_THAT(user_classifier->GetUserClass(),
|
| + Eq(UserClassifier::UserClass::ACTIVE_NTP_USER));
|
| +
|
| + // After a few more clicks, become an active consumer.
|
| + for (int i = 0; i < 5; i++) {
|
| + test_clock()->Advance(base::TimeDelta::FromHours(1));
|
| + user_classifier->OnEvent(UserClassifier::Metric::SUGGESTIONS_USED);
|
| + }
|
| + EXPECT_THAT(user_classifier->GetUserClass(),
|
| + Eq(UserClassifier::UserClass::ACTIVE_SUGGESTIONS_CONSUMER));
|
| +}
|
| +
|
| +TEST_F(UserClassifierTest,
|
| + ShouldBecomeActiveSuggestionsConsumerByClickingOftenWithDecreasedParam) {
|
| + // Increase the param to one half.
|
| + variations::testing::VariationParamsManager variation_params(
|
| + kStudyName,
|
| + {{"user_classifier_active_consumer_clicks_at_least_once_per_hours",
|
| + "36"}},
|
| + {kArticleSuggestionsFeature.name});
|
| + UserClassifier* user_classifier = CreateUserClassifier();
|
| +
|
| + // After two clicks still only an active user.
|
| + user_classifier->OnEvent(UserClassifier::Metric::SUGGESTIONS_USED);
|
| + test_clock()->Advance(base::TimeDelta::FromHours(1));
|
| + user_classifier->OnEvent(UserClassifier::Metric::SUGGESTIONS_USED);
|
| + EXPECT_THAT(user_classifier->GetUserClass(),
|
| + Eq(UserClassifier::UserClass::ACTIVE_NTP_USER));
|
| +
|
| + // One more click to become an active consumer.
|
| + test_clock()->Advance(base::TimeDelta::FromHours(1));
|
| + user_classifier->OnEvent(UserClassifier::Metric::SUGGESTIONS_USED);
|
| + EXPECT_THAT(user_classifier->GetUserClass(),
|
| + Eq(UserClassifier::UserClass::ACTIVE_SUGGESTIONS_CONSUMER));
|
| +}
|
| +
|
| +TEST_F(UserClassifierTest, ShouldBecomeRareNtpUserByNoActivity) {
|
| + UserClassifier* user_classifier = CreateUserClassifier();
|
| +
|
| + // After two days of waiting still an active user.
|
| + test_clock()->Advance(base::TimeDelta::FromDays(2));
|
| + EXPECT_THAT(user_classifier->GetUserClass(),
|
| + Eq(UserClassifier::UserClass::ACTIVE_NTP_USER));
|
| +
|
| + // Two more days to become a rare user.
|
| + test_clock()->Advance(base::TimeDelta::FromDays(2));
|
| + EXPECT_THAT(user_classifier->GetUserClass(),
|
| + Eq(UserClassifier::UserClass::RARE_NTP_USER));
|
| +}
|
| +
|
| +TEST_F(UserClassifierTest,
|
| + ShouldBecomeRareNtpUserByNoActivityWithDecreasedParam) {
|
| + // Decrease the param to one half.
|
| + variations::testing::VariationParamsManager variation_params(
|
| + kStudyName,
|
| + {{"user_classifier_rare_user_opens_ntp_at_most_once_per_hours", "48"}},
|
| + {kArticleSuggestionsFeature.name});
|
| + UserClassifier* user_classifier = CreateUserClassifier();
|
| +
|
| + // After one days of waiting still an active user.
|
| + test_clock()->Advance(base::TimeDelta::FromDays(1));
|
| + EXPECT_THAT(user_classifier->GetUserClass(),
|
| + Eq(UserClassifier::UserClass::ACTIVE_NTP_USER));
|
| +
|
| + // One more day to become a rare user.
|
| + test_clock()->Advance(base::TimeDelta::FromDays(1));
|
| + EXPECT_THAT(user_classifier->GetUserClass(),
|
| + Eq(UserClassifier::UserClass::RARE_NTP_USER));
|
| +}
|
| +
|
| +class UserClassifierMetricTest
|
| + : public UserClassifierTest,
|
| + public ::testing::WithParamInterface<
|
| + std::pair<UserClassifier::Metric, std::string>> {
|
| + public:
|
| + UserClassifierMetricTest() : UserClassifierTest() {}
|
| +
|
| + private:
|
| + DISALLOW_COPY_AND_ASSIGN(UserClassifierMetricTest);
|
| +};
|
| +
|
| +TEST_P(UserClassifierMetricTest, ShouldDecreaseEstimateAfterEvent) {
|
| + UserClassifier::Metric metric = GetParam().first;
|
| + UserClassifier* user_classifier = CreateUserClassifier();
|
| +
|
| + // The initial event does not decrease the estimate.
|
| + user_classifier->OnEvent(metric);
|
| +
|
| + for (int i = 0; i < 10; i++) {
|
| + test_clock()->Advance(base::TimeDelta::FromHours(1));
|
| + double old_metric = user_classifier->GetEstimatedAvgTime(metric);
|
| + user_classifier->OnEvent(metric);
|
| + EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric), Lt(old_metric));
|
| + }
|
| +}
|
| +
|
| +TEST_P(UserClassifierMetricTest, ShouldReportToUmaOnEvent) {
|
| + UserClassifier::Metric metric = GetParam().first;
|
| + const std::string& histogram_name = GetParam().second;
|
| + base::HistogramTester histogram_tester;
|
| + UserClassifier* user_classifier = CreateUserClassifier();
|
| +
|
| + user_classifier->OnEvent(metric);
|
| + EXPECT_THAT(histogram_tester.GetAllSamples(histogram_name), SizeIs(1));
|
| +}
|
| +
|
| +TEST_P(UserClassifierMetricTest, ShouldConvergeTowardsPattern) {
|
| + UserClassifier::Metric metric = GetParam().first;
|
| + UserClassifier* user_classifier = CreateUserClassifier();
|
| +
|
| + // Have the pattern of an event every five hours and start changing it towards
|
| + // an event every 10 hours.
|
| + for (int i = 0; i < 100; i++) {
|
| + test_clock()->Advance(base::TimeDelta::FromHours(5));
|
| + user_classifier->OnEvent(metric);
|
| + }
|
| + EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric),
|
| + DoubleNear(5.0, 0.1));
|
| + for (int i = 0; i < 3; i++) {
|
| + test_clock()->Advance(base::TimeDelta::FromHours(10));
|
| + user_classifier->OnEvent(metric);
|
| + }
|
| + EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric), Gt(5.5));
|
| + for (int i = 0; i < 100; i++) {
|
| + test_clock()->Advance(base::TimeDelta::FromHours(10));
|
| + user_classifier->OnEvent(metric);
|
| + }
|
| + EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric),
|
| + DoubleNear(10.0, 0.1));
|
| +}
|
| +
|
| +TEST_P(UserClassifierMetricTest, ShouldIgnoreSubsequentEventsForHalfAnHour) {
|
| + UserClassifier::Metric metric = GetParam().first;
|
| + UserClassifier* user_classifier = CreateUserClassifier();
|
| +
|
| + // The initial event
|
| + user_classifier->OnEvent(metric);
|
| + // Subsequent events get ignored for the next 30 minutes.
|
| + for (int i = 0; i < 5; i++) {
|
| + test_clock()->Advance(base::TimeDelta::FromMinutes(5));
|
| + double old_metric = user_classifier->GetEstimatedAvgTime(metric);
|
| + user_classifier->OnEvent(metric);
|
| + EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric), Eq(old_metric));
|
| + }
|
| + // An event 30 minutes after the initial event is finally not ignored.
|
| + test_clock()->Advance(base::TimeDelta::FromMinutes(5));
|
| + double old_metric = user_classifier->GetEstimatedAvgTime(metric);
|
| + user_classifier->OnEvent(metric);
|
| + EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric), Lt(old_metric));
|
| +}
|
| +
|
| +TEST_P(UserClassifierMetricTest,
|
| + ShouldIgnoreSubsequentEventsWithIncreasedLimit) {
|
| + UserClassifier::Metric metric = GetParam().first;
|
| + // Increase the min_hours to 1.0, i.e. 60 minutes.
|
| + variations::testing::VariationParamsManager variation_params(
|
| + kStudyName, {{"user_classifier_min_hours", "1.0"}},
|
| + {kArticleSuggestionsFeature.name});
|
| + UserClassifier* user_classifier = CreateUserClassifier();
|
| +
|
| + // The initial event
|
| + user_classifier->OnEvent(metric);
|
| + // Subsequent events get ignored for the next 60 minutes.
|
| + for (int i = 0; i < 11; i++) {
|
| + test_clock()->Advance(base::TimeDelta::FromMinutes(5));
|
| + double old_metric = user_classifier->GetEstimatedAvgTime(metric);
|
| + user_classifier->OnEvent(metric);
|
| + EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric), Eq(old_metric));
|
| + }
|
| + // An event 60 minutes after the initial event is finally not ignored.
|
| + test_clock()->Advance(base::TimeDelta::FromMinutes(5));
|
| + double old_metric = user_classifier->GetEstimatedAvgTime(metric);
|
| + user_classifier->OnEvent(metric);
|
| + EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric), Lt(old_metric));
|
| +}
|
| +
|
| +TEST_P(UserClassifierMetricTest, ShouldCapDelayBetweenEvents) {
|
| + UserClassifier::Metric metric = GetParam().first;
|
| + UserClassifier* user_classifier = CreateUserClassifier();
|
| +
|
| + // The initial event
|
| + user_classifier->OnEvent(metric);
|
| + // Wait for an insane amount of time
|
| + test_clock()->Advance(base::TimeDelta::FromDays(365));
|
| + user_classifier->OnEvent(metric);
|
| + double metric_after_a_year = user_classifier->GetEstimatedAvgTime(metric);
|
| +
|
| + // Now repeat the same with s/one year/one week.
|
| + user_classifier->ClearClassificationForDebugging();
|
| + user_classifier->OnEvent(metric);
|
| + test_clock()->Advance(base::TimeDelta::FromDays(7));
|
| + user_classifier->OnEvent(metric);
|
| +
|
| + // The results should be the same.
|
| + EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric),
|
| + Eq(metric_after_a_year));
|
| +}
|
| +
|
| +TEST_P(UserClassifierMetricTest,
|
| + ShouldCapDelayBetweenEventsWithDecreasedLimit) {
|
| + UserClassifier::Metric metric = GetParam().first;
|
| + // Decrease the max_hours to 72, i.e. 3 days.
|
| + variations::testing::VariationParamsManager variation_params(
|
| + kStudyName, {{"user_classifier_max_hours", "72"}},
|
| + {kArticleSuggestionsFeature.name});
|
| + UserClassifier* user_classifier = CreateUserClassifier();
|
| +
|
| + // The initial event
|
| + user_classifier->OnEvent(metric);
|
| + // Wait for an insane amount of time
|
| + test_clock()->Advance(base::TimeDelta::FromDays(365));
|
| + user_classifier->OnEvent(metric);
|
| + double metric_after_a_year = user_classifier->GetEstimatedAvgTime(metric);
|
| +
|
| + // Now repeat the same with s/one year/two days.
|
| + user_classifier->ClearClassificationForDebugging();
|
| + user_classifier->OnEvent(metric);
|
| + test_clock()->Advance(base::TimeDelta::FromDays(3));
|
| + user_classifier->OnEvent(metric);
|
| +
|
| + // The results should be the same.
|
| + EXPECT_THAT(user_classifier->GetEstimatedAvgTime(metric),
|
| + Eq(metric_after_a_year));
|
| +}
|
| +
|
| +INSTANTIATE_TEST_CASE_P(
|
| + , // An empty prefix for the parametrized tests names (no need to
|
| + // distinguish the only instance we make here).
|
| + UserClassifierMetricTest,
|
| + testing::Values(
|
| + std::make_pair(UserClassifier::Metric::NTP_OPENED,
|
| + "NewTabPage.UserClassifier.AverageHoursToOpenNTP"),
|
| + std::make_pair(
|
| + UserClassifier::Metric::SUGGESTIONS_SHOWN,
|
| + "NewTabPage.UserClassifier.AverageHoursToShowSuggestions"),
|
| + std::make_pair(
|
| + UserClassifier::Metric::SUGGESTIONS_USED,
|
| + "NewTabPage.UserClassifier.AverageHoursToUseSuggestions")));
|
| +
|
| +} // namespace
|
| +} // namespace ntp_snippets
|
|
|