class_docstr: Base class for testing FXRateReader implementations.
  
  To test a new FXRateReader implementation, subclass from this base class
  and implement the ``reader`` property, returning an FXRateReader that uses
  the data stored in ``cls.fx_rates``.
code: def test_2d_lookup(self):
      rand = np.random.RandomState(42)
  
      dates = pd.date_range(
          self.FX_RATES_START_DATE - pd.Timedelta("2 days"),
          self.FX_RATES_END_DATE + pd.Timedelta("2 days"),
      )
      rates = self.FX_RATES_RATE_NAMES + [DEFAULT_FX_RATE]
      possible_quotes = self.FX_RATES_CURRENCIES
      possible_bases = self.FX_RATES_CURRENCIES + [None]
  
      # For every combination of rate name and quote currency...
      for rate, quote in itertools.product(rates, possible_quotes):
          # Choose N random distinct days...
          for ndays in 1, 2, 7, 20:
              dts_raw = rand.choice(dates, ndays, replace=False)
              dts = pd.DatetimeIndex(dts_raw, tz="utc").sort_values()
  
              # Choose M random possibly-non-distinct currencies...
              for nbases in 1, 2, 10, 200:
                  bases = rand.choice(
                      possible_bases, nbases, replace=True
                  ).astype(object)
  
              # ...And check that we get the expected result when querying
              # for those dates/currencies.
              result = self.reader.get_rates(rate, quote, bases, dts)
              expected = self.get_expected_fx_rates(
                  rate, quote, bases, dts
              )
  
              assert_equal(result, expected)
  
imports: import itertools
  import pandas
  import numpy
  from zipline.data.fx import DEFAULT_FX_RATE
  from zipline.testing.predicates import assert_equal
  import zipline.testing.fixtures
label_desc: Use image node_id 4 for calling the _FXReaderTestCase obj's underlying member method code with example usage: obj.test_2d_lookup() without return types