0
\$\begingroup\$

I have a section of a script that is pretty simple and works but I am wondering the best way to rewrite it to be testable.

        var stats = other.gameObject.GetComponent<ItemStats>();
        var itemReturn = other.gameObject.GetComponent<ReturnItemToPositionIfDroppedOrStolen>();
        
        var prefrenceName = "";
        
        switch (stats.itemNumber)
        {
            case 0:
                prefrenceName = Constant.Tree_Bark_Upgrade_One_Unlocked;
                break;
            case 1:
                prefrenceName = Constant.Tree_Bark_Upgrade_Two_Unlocked;
                break;
            case 2:
                prefrenceName = Constant.Tree_Bark_Upgrade_Three_Unlocked;
                break;
            case 3:
                prefrenceName = Constant.Extra_Coconut_Upgrade_One_Unlocked;
                break;
            case 4:
                prefrenceName = Constant.Extra_Coconut_Upgrade_Two_Unlocked;
                break;
            case 5:
                prefrenceName = Constant.Extra_Coconut_Upgrade_Three_Unlocked;
                break;
            case 6:
                prefrenceName = Constant.Grandpaps_Cane_Unlocked;
                break;
            case 7:
                prefrenceName = Constant.Whackin_Bat_Unlocked;
                break;
            case 8:
                prefrenceName = Constant.Whackin_Golf_Club_Unlocked;
                break;
            case 9:
                prefrenceName = Constant.Bamboo_Staff_Unlocked;
                break;
            case 10:
                prefrenceName = Constant.Whackin_Mallet_Unlocked;
                break;
            case 11:
                prefrenceName = Constant.Whackin_Hammer_Unlocked;
                break;
            case 12:
                prefrenceName = Constant.Crabby_Bomb_Unlocked;
                break;
            case 13:
                prefrenceName = Constant.CraboPolt_Unlocked;
                break;
            case 14:
                prefrenceName = Constant.Big_Bomb_Unlocked;
                break;
            case 15:
                prefrenceName = Constant.Sap_Bomb_Unlocked;
                break;
            case 16:
                prefrenceName = Constant.Boombox_Unlocked;
                break;
            case 17:
                prefrenceName = Constant.Inflatable_Palm_Tree_Unlocked;
                break;
        }

        var hasItem = PlayerPrefs.GetInt(prefrenceName, 0);

I could set up an individual test to make sure the correct pref string is returned but wondering if there is a less breakable way

public class PreferenceHelper
{

public string getItemPreferenceByNumber(int num)
{
    switch (num)
    {
        case 0:
            return Constant.Tree_Bark_Upgrade_One_Unlocked;
        case 1:
            return Constant.Tree_Bark_Upgrade_Two_Unlocked;
        case 2:
            return Constant.Tree_Bark_Upgrade_Three_Unlocked;
        case 3:
            return Constant.Extra_Coconut_Upgrade_One_Unlocked;
        case 4:
            return Constant.Extra_Coconut_Upgrade_Two_Unlocked;
        case 5:
            return Constant.Extra_Coconut_Upgrade_Three_Unlocked;
        case 6:
            return Constant.Grandpaps_Cane_Unlocked;
        case 7:
            return Constant.Bamboo_Staff_Unlocked;
        case 8:
            return Constant.Whackin_Bat_Unlocked;
        case 9:
            return Constant.Whackin_Mallet_Unlocked;
        case 10:
            return Constant.Whackin_Golf_Club_Unlocked;
        case 11:
            return Constant.Whackin_Hammer_Unlocked;
        case 12:
            return Constant.Sap_Bomb_Unlocked;
        case 13:
            return Constant.Crabby_Bomb_Unlocked;
        case 14:
            return Constant.Boombox_Unlocked;
        case 15:
            return Constant.CraboPolt_Unlocked;
        case 16:
            return Constant.Inflatable_Palm_Tree_Unlocked;
        case 17:
            return Constant.Big_Bomb_Unlocked;
        default:
            return "";
    }
  }
}

Test :

    [UnityTest]
    public IEnumerator UserGetsTreeBarkUpgrade()
    {
        int num = 0;
        String pref = _preferenceHelper.getItemPreferenceByNumber(num);
        Assert.AreEqual(pref, Constant.Tree_Bark_Upgrade_One_Unlocked);
        yield return null;
    }
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

This looks like a job for an enumeration type!

public enum ItemId {
      TreeBarkUpgradeOne,
      TreeBarkUpgradeTwo,
      TreeBarkUpgradeThree,
      //...
      InflatablePalmTree
}

Now instead of a public int itemNumber, your ItemStats component has a public ItemId id.

Inspector Example

Bonus: this gives you a drop-down in the Inspector that's a lot more legible than a bunch of magic numbers, and it still fits in the same int worth of storage and serializes/deserializes fine, because an enum value is still just a number under the hood.

You can then use these names directly as your player prefs IDs:

public static string GetPreferenceName(ItemId id) => id.ToString();

Or you can modify the name:

public static string GetPreferenceName(ItemId id) => $"{id}_Unlocked";

Or if you need names that are very different than the internal names of your enum members, and not in a consistent way you can boil down to a formula, you can store those in an array and look them up by exploiting the fact that an enum value is an integer:

static readonly string[] ITEM_PREFS_IDS = new string[] {
    "Tree Bark Lv 1",
    "Tree Bark Lv 2",
    //...
    "Inflatable Palm"
};

public static string GetPreferenceName(ItemId id) =>ITEM_PREFS_IDS[(int)id];

With any of these, your prefs string lookup is just a one-liner instead of a massive switch statement.

For the first two, you may not even need a test for this at all, since you're guaranteed that the names the designer sees in the inspector when configuring the item will always map 1:1 with the prefs names generated from them.

The last option with the array gives you more flexibility to work with two different nomenclatures, but you do introduce an extra point of failure where the array and the enum could get out of sync (say you reorder items, or insert/remove items in one place but not the other).

For that, you're basically back to your switch statement to have a second, independent check. But you can write that more concisely with a little helper:

static void CheckPrefsIdMatch(ItemId id) {
    string expected = id switch
    {
        ItemId.TreeBarkUpgradeOne => "Tree Bark Lv 1",
        ItemId.TreeBarkUpgradeTwo => "Tree Bark Lv 2",
        //...
        ItemId.InflatablePalmTree => "Inflatable Palm",
        _ => "Unknown ID!"
    };
    string observed = GetPreferenceName(id);
    UnityEngine.Assertions.Assert.AreEqual(expected, observed, 
         $"Mismatched prefs id for Item Id {id}: expected {expected} but found {observed} instead."
    );
}

Then your tests can be simpler and less repetitive:

[UnityTest]
public IEnumerator UserGetsTreeBarkUpgrade()
{
    CheckPrefsIdMatch(ItemId.TreeBarkUpgradeOne);
    yield return null;
}

But the obvious downside is that now if you want to change your item IDs, you have to change them in three places: the enum, the array, and the test. So for that reason, I'd generally lean toward the options that use the enum names/values directly, without any secondary name storage anywhere that could go out of sync. Minimize surface area for potential bugs. 😉

\$\endgroup\$
1
  • \$\begingroup\$ Yeah I was thinking of doing an enum but I was worried about the list and ID''s getting out of sync. I am going to go ahead and do the enum/names directly though because the switch has the same issue and the enum would be way cleaner :). Thanks for the post :) ! \$\endgroup\$
    – Doug Ray
    Mar 13, 2022 at 19:23
0
\$\begingroup\$

using switch and case this way doesn't seem to be convenient.

Why not create a list? then you can just write MyList[num]

And for testing, you can use a loop:

for (int i = 0; i < MyList.Length; i++)
{
    print (getItemPreferenceByNumber(i));
}

or you can completely remove getItemPreferenceByNumber():

for (int i = 0; i < MyList.Length; i++)
{
    print (MyList[i]);
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Instead of adding them to a list, you could as well make a powerup enum. You can loop over the enums directly and can assign the same indexes too. \$\endgroup\$
    – Zibelas
    Mar 12, 2022 at 9:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .